【设计模式】学习笔记(二)——创建型设计模式


目录

  • 一、创建型设计模式介绍
  • 二、工厂模式
    • 2.1 简单工厂
    • 2.2 工厂方法
    • 2.3 抽象工厂
    • 2.4 扩展:万能工厂
  • 三、单例模式
    • 3.1 单例模式介绍
    • 3.2 单例模式实现
      • 3.2.1 饿汉模式
      • 3.2.2 懒汉模式
      • 3.2.3 懒汉和懒汉的区别
  • 四、原型模式
    • 4.1 原型模式介绍
    • 4.2 原型模式实现
  • 五、创建者模式
    • 5.1 创建者模式介绍
    • 5.2 创建者模式实现
    • 5.3 创建者模式扩展
    • 5.4 工厂方法模式VS建造者模式
    • 5.5 抽象工厂模式VS建造者模式

一、创建型设计模式介绍

概念:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

分类:

  • 工厂模式(Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

二、工厂模式

概念:它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

  • 共有三种不同的工厂模式:
    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式

而三种不同的工厂模式也描述不同的应用场景


静态常量

  • 静态代码块
  • 2)懒汉模式

      • 线程不安全
      • 线程安全
      • 同步方法块
      • 双重检查
      • 静态内部类
      • 枚举

    标红为推荐使用的方式


    3.2.1 饿汉模式

    饿汉模式是指在类的加载的时候就创建一个实例。饿汉模式又分两种实现方式:

    1)通过静态常量实现

    优点:写法比较简单,就是在类转载的时候就完成实例化。避免了线程同步问题。

    缺点:是在类转载的时候就完成实例化,如果从头到尾未使用过这个实例,则会照成内存的浪费。

    结论:这种单例模式可用,可能造成资源浪费,所以一般在确定一定会用到这个类时采用。

    代码示例

    步骤如下:
    1)构造器私有化(防止 new)
    2)类的内部创建对象
    3)向外暴露一个静态的公共方法。getInstance

    class Singleton{
        //1.构造器私有化,外部不能new
        private Singleton(){
    
        }
    
        //2.本类内部创建对象实例
        private final static Singleton instance = new Singleton();
    
        //3.提供一个公有的静态方法,放回实例对象
        public static Singleton getInstance(){
            return instance;
        }
    }
    

    2)通过静态代码块实现
    这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也就是类转载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面一样

    结论:这种单例模式可用,但是会造成资源浪费

    代码示例

    1)构造器私有化(防止 new)
    2)类的内部声名静态对象
    3)在静态代码块中,创建单例对象
    4)提供一个公有的静态方法,放回实例对象

    class Singleton{
    
        private static Singleton instance;
    
        private Singleton(){
    
        }
    
        static {// 在静态代码块中,创建单例对象
            instance = new Singleton();
        }
    
        public static Singleton getInstance(){
            return instance;
        }
    }
    

    3.2.2 懒汉模式

    懒汉模式是指在调用方法的时候进行实例化的工作。实现方式有六种:

    1)线程不安全

    优点:起到了懒加载的效果,但是只能在单线程下使用。

    缺点:如果在多线程下,一个线程进入了if(singletion == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

    结论:在实际开发中,不要使用这种方式(多线程环境下,线程不安全)

    代码示例

    实现步骤
    1)构造器私有化(防止 new)
    2)类的内部声名静态对象
    3)提供一个静态的公有方法,当使用到该方法时,才去创建 instance

    class Singleton{
    
        private static Singleton instance;
    
        private Singleton(){
    
        }
    
        //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
        //即懒汉式
        public static Singleton getInstance(){
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    2)线程安全,同步方法

    优点:解决了线程不安全问题

    缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想要获得该类实例,直接return就行了。

    结论:在实际开发中,不推荐使用这种方法(方法进行同步效率太低

    代码示例

    实现步骤
    1)构造器私有化(防止 new)
    2)类的内部声名静态对象
    3)

    class Singleton{
    
        private static Singleton instance;
    
        private Singleton(){
    
        }
    
        //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
        //即懒汉式
        public static synchronized Singleton getInstance(){
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    3)线程安全,同步代码块
    这种方法,本意是对上一种实现方法的改进,因为前面方法效率太低,改为同步产生实例化的代码块。但这种同步方法并不能起到线程同步的作用。跟第一种实现方法情形一致,假如一个线程进入了if(singletion == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

    结论:在实际开发中,不能使用这种方式

    代码示例

    实现步骤
    1)构造器私有化(防止 new)
    2)类的内部声名静态对象
    3)提供一个静态的公有方法,在代码块中加入同步处理的代码(synchronized)

    class Singleton{
        private static Singleton singleton;
    
        private Singleton(){
    
        }
    
        //提供一个静态的公有方法,在代码块中加入同步处理的代码(synchronized)
        //即懒汉式
        public static Singleton getInstance(){
            if(singleton == null){
                synchronized(Singleton.class){
                    singleton = new Singleton();
                }
            }
            return singleton;
        }
    }
    

    4)双重检查

    优点:线程安全,延迟加载,效率较高

    缺点:无!!!

    结论:在实际开发中,推荐使用这种单例设计模式

    代码示例

    实现步骤
    1)构造器私有化(防止 new)
    2)类的内部声名静态对象并用volatile(用来确保将变量的更新操作通知到其他线程)关键字修饰
    3)提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题

    class Singleton{
        private static volatile Singleton instance;
    
        private Singleton(){
    
        }
    
        //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
        public static synchronized Singleton getInstance(){
            if(instance == null){
                synchronized(Singleton.class){//当第一个线程执行完对象实例化操作后
                   if(instance == null) {//后面的线程来到这里时判断
                       instance = new Singleton();
                   }
                   //else 到下面返回 instance
                }
            }
            //而当再后面的线程来时在最上方的判断语句中就直接来到这里返回instance
            return instance;
        }
    }
    

    5)静态内部类

    优点:

    • 线程安全(在类进行初始化时,别的线程是无法进来的。)
    • 延迟加载(外部类的装载不会导致静态内部类的装载)
    • 效率高

    缺点:无 !!!

    结论:实际开发中,推荐使用这种单例设计模式

    代码示例

    实现步骤
    1)构造器私有化(防止 new)
    2)类的内部声名静态对象
    3)写一个静态内部类,该类中有一个静态属性外部类的静态常量
    4)提供一个静态的公有方法,直接返回静态内部类常量属性

    class Singleton{
        private static Singleton instance;
    
        private Singleton(){
    
        }
    
        //写一个静态内部类,该类中有一个静态属性 Singleton
        private static class SingletonInstance{
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
        public static synchronized Singleton getInstance(){
    
            return SingletonInstance.INSTANCE;
        }
    }
    

    6)枚举
    这是借助JDK1.5中添加的枚举来实现单例模式。

    优点:保证了线程安全、防止了反序列化重新创建新的对象

    缺点:无 !!!

    结论:推荐使用

    代码示例
    //使用枚举,可以实现单例,推荐
    enum Singleton{
        INSTANCE;//属性
        public void sayOK(){
            System.out.println("ok~");
        }
    }
    

    3.2.3 懒汉和饿汉的区别

    区别 饿汉 懒汉
    线程 安全 不安全(不过有解决方案)
    资源加载 占据一定的内存相应的在调用时速度也会更快 第一次掉用时要初始化如果要做的工作比较多性能上会有些延迟

    四、原型模式

    4.1 原型模式简介

    用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

    结构:

    • 抽象原型类:规定了具体原型对象必须实现的 clone() 方法。
    • 具体原型类:实现抽象原型类的 clone() 方法,它是被复刻的对象。
    • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

    适用场景:

    • 对象的创建非常复制,可以适用原型模式快捷的创建对象。
    • 性能和安全要求比较高。

    4.2 原型模式实现

    原型模式的克隆分为浅克隆和深克隆

    浅克隆:只克隆原对象中的基本类型数据,非基本数据类型数据的指向的还是原堆中地址

    深克隆:将对象完全克隆,属性中引用的其他对象不再指向原有对象地址

    Java中的Object类中提供了 clone() 方法来实现浅克隆。Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable 接口的子类实现就是具体的原型类。

    浅克隆

    实现步骤:

    // 1.需要克隆的类实现 Cloneable 接口
    // 2.重新Clone方法
    public class Realizetype implements Cloneable{
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        //测试一下
        public static void main(String[] arge) throws CloneNotSupportedException {
            Realizetype realizetyp = new Realizetype();
            Realizetype clone = (Realizetype) realizetyp.clone();
        }
    }
    
    深克隆

    实现深克隆的方法有很多种,这里我们使用的是序列化+反序列化来实现深克隆

    import java.io.*;
    
    public class Realizetype implements Cloneable{
    
        public static void main(String[] arge) throws Exception {
            //创建原型对象
            Student student = new Student();
            //创建对象输出流对象
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:/xxx.txt"));
            //写对象
            oos.writeObject(student);
            //释放资源
            oos.close();
    
    
            //创建对象输入流对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:/xxx.txt"));
            //读取对象
            Student student1 = (Student) ois.readObject();
            //释放资源
            oos.close();
    
        }
    }
    
    //实现序列号接口
    class Student implements Serializable {
    
    }
    

    五、创建者模式

    5.1 创建者模式介绍

    概念:将一个复杂对象的构建与分离,使得同样的构建过程可以创建不同的表示。

    结构:

    • 抽象创建者(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。

    • 具体创建者(ConcreteBuilder):实现 Burider 接口,完成复杂产品的各个部分的具体创建方法。在构建过程后,提供产品的实例。

    • 产品类(Product):要创建的复杂对象

    • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

    优点:

    • 封装性好 : 创建 和 使用 分离 ;
    • 扩展性好 : 建造类之间 相互独立 , 在 一定程度上解耦 ;

    缺点:

    • 增加类数量 : 产生多余的 Builder 对象 ;
    • 内部修改困难 : 如果 产品内部发生变化 , 建造者也要相应修改 ;

    适用场景:

    • 结构复杂 : 对象 有 非常复杂的内部结构 , 有很多属性 ;
    • 分离创建和使用 : 想把 复杂对象 的 创建 和 使用 分离 ;
    • 当创造一个对象 需要很多步骤时 , 适合使用建造者模式 ;
    • 当创造一个对象 只需要一个简单的方法就可以完成 , 适合使用工厂模式

    5.2 创建者模式实现

    假设要创建一个双层牛肉鸡腿堡??,这个肉(meat)和配料(dosing)非常复杂,采用创建者方式。

    点击查看代码
    /**
     * 汉堡类
     */
    public class Hamburger {
        // 配料部件,这里使用字符串作为部件,真正使用是传入对象作为部件
        private String meat;//肉
        private String dosing;//配料
    
        public Hamburger(String meat, String dosing) {
            this.meat = meat;
            this.dosing = dosing;
        }
    
        public Hamburger() {
    
        }
    
        public String getMeat() {
            return meat;
        }
    
        public void setMeat(String meat) {
            this.meat = meat;
        }
    
        public String getDosing() {
            return dosing;
        }
    
        public void setDosing(String dosing) {
            this.dosing = dosing;
        }
    
        @Override
        public String toString() {
            return "Hamburger{" +
                    "meat='" + meat + '\'' +
                    ", dosing='" + dosing + '\'' +
                    '}';
        }
    
        public static class Beefburger {
        }
    }
    
    /**
     * 抽象创建者
     */
    public abstract class Builder {
        // 将hamburger对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
        Hamburger hamburger = new Hamburger();
        abstract void setmeat();
        abstract void setdosing();
        abstract Hamburger createHamburger();
    }
    
    /**
     * 牛肉堡创造者类
     */
    public class Beefburger extends Builder{
        @Override
        void setbeef() {
            hamburger.setMeat("牛肉");
        }
    
        @Override
        void setdosing() {
            hamburger.setDosing("烧烤酱");
        }
    
        @Override
        Hamburger createHamburger() {
            return hamburger;
        }
    }
    
    /**
     * 鸡腿堡创造者类
     */
    public class Chickenburger extends Builder{
    
        @Override
        void setbeef() {
            hamburger.setMeat("鸡肉");
        }
    
        @Override
        void setdosing() {
            hamburger.setDosing("沙拉酱");
        }
    
        @Override
        Hamburger createHamburger() {
            return hamburger;
        }
    }
    
    /**
     * 指挥者类
     */
    public class Director {
        private Builder builder;
    
        // 初始化创建者
        public Director(Builder builder){
            this.builder = builder;
        }
    
        // 构建方法,装配部件
        public Hamburger construct(){
            builder.setbeef();
            builder.setdosing();
            Hamburger hamburger = builder.createHamburger();
            return hamburger;
        }
    }
    
    /**
     * 测试类
     */
    public class Client {
        public static void main(String[] args) {
            // 鸡腿堡创建
            Chickenburger chickenburger = new Chickenburger();
            Director director = new Director(chickenburger);
            Hamburger hamburger = director.construct();
    
            // 牛肉堡创建
            Beefburger beefburger = new Beefburger();
            Director director1 = new Director(beefburger);
            Hamburger hamburger1 = director1.construct();
    
            System.out.println(hamburger);
            System.out.println(hamburger1);
        }
    }
    
    /*测试结果
    Hamburger{meat='鸡肉', dosing='沙拉酱'}
    Hamburger{meat='牛肉', dosing='烧烤酱'}*/
    

    可以看出,指挥者类对于产品的创建有着至关重要的作用,Builder只是提供了部件的创建,而真正决定怎么创建的,需要由指挥者类(Director)来决定


    当部件比较少的时候,可以将指挥者类合并到抽象创建者类里面,但这样就违背了设计原则中的单一职责原则

    /**
     * 抽象创建者
     */
    public abstract class Builder {
        // 将hamburger对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
        Hamburger hamburger = new Hamburger();
        abstract void setmeat();
        abstract void setdosing();
        abstract Hamburger createHamburger();
        public Hamburger construct(){
            this.setmeat();
            this.setdosing();
            Hamburger hamburger = this.createHamburger();
            return hamburger;
        }
    }
    

    5.3 创建者模式扩展

    有时候我们还会遇到这样的问题

    //当我们对象属性定义过多时,我们可以使用创建者模式进行链式赋值
    //增加其代码的可读性。操作简单,重构前:
    public class Hamburger {
        private String meat;
        private String dosing;
        private String vegetable;
        private String bread;
    
        public String getMeat() {
            return meat;
        }
    
        public void setMeat(String meat) {
            this.meat = meat;
        }
    
        public String getDosing() {
            return dosing;
        }
    
        public void setDosing(String dosing) {
            this.dosing = dosing;
        }
    
        public String getBread() {
            return bread;
        }
    
        public void setBread(String bread) {
            this.bread = bread;
        }
    
        public String getVegetable() {
            return vegetable;
        }
    
        public void setVegetable(String vegetable) {
            this.vegetable = vegetable;
        }
    
        public Hamburger(String meat, String dosing, String vegetable, String bread) {
            this.meat = meat;
            this.dosing = dosing;
            this.vegetable = vegetable;
            this.bread = bread;
        }
    
        @Override
        public String toString() {
            return "Hamburger{" +
                    "meat='" + meat + '\'' +
                    ", dosing='" + dosing + '\'' +
                    ", vegetable='" + vegetable + '\'' +
                    ", bread='" + bread + '\'' +
                    '}';
        }
    }
    
    /**
     * 测试类
     */
    public class Client{
        public static void main(String[] args) {
            Hamburger hamburger=new Hamburger("牛肉","烧烤酱","青菜","普通面包");
            System.out.println(hamburger);
        }
    }
    //测试结果
    //Hamburger{meat='牛肉', dosing='烧烤酱', vegetable='青菜', bread='普通面包'}
    

    重构后
    /**
     * 静态内部创建者类改进后
     */
    public class Hamburger {
        private String meat;
        private String dosing;
        private String vegetable;
        private String bread;
    
        // 将构造方法私有化,采用创建者实现构建
        private Hamburger(Builder builder){
            this.meat = builder.meat;
            this.dosing = builder.dosing;
            this.vegetable = builder.vegetable;
            this.bread = builder.bread;
        }
    
        // 创建一个静态内部创建者类
        public static class Builder{
            private String meat;
            private String dosing;
            private String vegetable;
            private String bread;
            // 分别创建属性
            public Builder setMeat(String val) {
                meat = val;
                return this;
            }
    
            public Builder setDosing(String val) {
                dosing = val;
                return this;
            }
    
            public Builder setVegetable(String val) {
                vegetable = val;
                return this;
            }
    
            public Builder setBread(String val) {
                bread = val;
                return this;
            }
            // 外部类的构造方式已经私有了,所以通过内部类方法返回一个Phone对象
            public Hamburger build(){
                // 内部类可以直接调用外部类的私有构造器
                // 再将内部类这个对象传给私有构造器,让他来进行构造
                return new Hamburger(this);
            }
        }
        @Override
        public String toString() {
            return "Builder{" +
                    "meat='" + meat + '\'' +
                    ", dosing='" + dosing + '\'' +
                    ", vegetable='" + vegetable + '\'' +
                    ", bread='" + bread + '\'' +
                    '}';
        }
    }
    

    通过改进后,我们就可以通过调用静态内部类的方法,来实现构造器方法
    由于内部类每设置一个属性就返回 Builder 所有可以连接赋值
    并且还可以选择性的构造,不易造成错误


    5.4 工厂方法模式VS建造者模式

    工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

    我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

    5.5 抽象工厂模式VS建造者模式

    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

    建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

    如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。


    此文章只作为学习过程记录!!!