创建型设计模式

113 阅读13分钟

创建者模式的主要关注点是怎么创建对象,他的主要特点是将对象的创建与使用分离。这样可以降低系统的耦合度。

工厂模式

简单工厂模式

结构

  • 抽象产品:定义产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现或者继承抽象产品的子类。
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品。

优缺点

  • 优点: 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户端代码,如果要实现新产品直接修改工厂类,而不需要在源代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
  • 缺点: 增加新产品时还是需要修改工厂类的代码,违背了开闭原则

静态工厂类:将工厂类的方法定义为static类型的。

工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。(类似spring中的controller、service层)

结构

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。(service接口)
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。(serviceImpl是实现类)
  • 抽象产品:定义产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

优缺点

  • 优点: 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程。在系统增加新的产品时只需要添加具体的产品类和对应的具体工厂类,无需对原工厂进行任何修改,满足了开闭原则。
  • 缺点: 每增加一个产品就要增加一个具体的产品类和对应的具体工厂类,增加了系统的复杂度。

抽象工厂模式

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只能生产一个等级的产品,而抽象工厂方法模式可生产多个等级的产品。

结构

  • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品:定义产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间多对一的关系。

优缺点

  • 优点: 当一个产品族中的多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的对象。
  • 缺点: 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

模式扩展

可以通过简单工厂+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端若需要对象,直接进行获取即可。

  • 第一步:定义配置文件

     american=com.example.designPatterns.factory.entity.AmericanCoffee
     latte=com.example.designPatterns.factory.entity.LatteCoffee
    
  • 第二步:改进工厂类

     public class CoffeeFactory {
     ​
         //加载配置文件,获取配置文件中配置的全类名,并创建类的对象进行存储
         //定义容器对象存储咖啡对象
         private static Map<String, Coffee> map = new HashMap<>();
     ​
         //加载配置文件
         static {
             Properties properties = new Properties();
     ​
             try (InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties")) {
                 properties.load(is);
                 //从properties集合中获取全类名并创建对象
                 for (Object key : properties.keySet()) {
                     String className = properties.getProperty((String) key);
                     Class<?> clazz = Class.forName(className);
                     Coffee coffee = (Coffee) clazz.newInstance();
                     //将名称和对象存储到容器中
                     map.put((String) key, coffee);
                 }
     ​
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         }
     ​
         /**
          * 根据名称获取对象
          *
          * @param name 名字
          * @return {@link Coffee}
          */
         public static Coffee createCoffee(String name) {
             return map.get(name);
         }
     }
    

建造者模式

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

结构

建造者模式包含如下角色:

  • 抽象建造者类:这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  • 具体建造者类:实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类:要创建的复杂对象。
  • 指挥者类:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品信息,只负责保证对象各部分完整创建或按某种顺序创建。

优缺点

  • 优点:

    • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
    • 在建造者模式中,客户端不必知道产品组成的细节,将产品的本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 可以更加精细地控制产品的创建过程。将复杂的产品创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
  • 缺点: 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,若产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围收到了一定的限制。

使用场景

建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用:

  • 创建的对象较为复杂,有多个部件构成,各部件面临着复杂的变化,但构建间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
 @ToString
 public class Phone {
     private String cpu;
     private String screen;
     private String memory;
     private String mainBoard;
 ​
     private Phone(Builder builder) {
         this.cpu = builder.cpu;
         this.screen = builder.screen;
         this.memory = builder.memory;
         this.mainBoard = builder.mainBoard;
     }
 ​
     public static final class Builder {
         private String cpu;
         private String screen;
         private String memory;
         private String mainBoard;
 ​
         public Builder cpu(String cpu) {
             this.cpu = cpu;
             return this;
         }
 ​
         public Builder screen(String screen) {
             this.screen = screen;
             return this;
         }
 ​
         public Builder memory(String memory) {
             this.memory = memory;
             return this;
         }
 ​
         public Builder mainBoard(String mainBoard) {
             this.mainBoard = mainBoard;
             return this;
         }
 ​
         public Phone build(){
             return new Phone(this);
         }
     }
 }
 ​
 class Client {
     public static void main(String[] args) {
         Phone phone = new Phone.Builder()
                 .cpu("Intel")
                 .screen("三星")
                 .memory("金士顿")
                 .mainBoard("华硕")
                 .build();
         System.out.println(phone);
     }
 }

单例模式

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

结构

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

实现

单例模式分为两大类:

  • 饿汉式:类加载就会导致该单实例对象被创建。
  • 懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建。
  1. 饿汉式-方式1(静态变量方式)

     //单例类
     public class Sigleton {
         //1.私有构造方法
         private Singleton() {}
         //2.本类中创建该类对象
         private static Singleton instance = new Singleton();
         //3.提供一个公共方法访问方式,让外界获取该对象
         public static Singleton getInstance() {
             return instance;
         }
     }
     ​
     //访问类
     class Test {
         public static void main(String[] args) {
             //创建Siungleton对象
             Singleton instance = Singleton.getInstance();
             
             Singleton instance2 = Singleton.getInstance();
             
             System.out.println(instance == instance2); // true
         }
     }
    
  2. 饿汉式-方式2(静态代码块的方式)

     //单例类
     public class Sigleton {
         //1.私有构造方法
         private Singleton() {}
         //2.本类中创建该类对象
         private static Singleton instance;
         //3.在静态代码块中赋值
         static {
             instance = new Singleton();
         }
         //4.提供一个公共方法访问方式,让外界获取该对象
         public static Singleton getInstance() {
             return instance;
         }
     }
    
  3. 懒汉式-方式1(线程不安全)

     //单例类
     public class Sigleton {
         //1.私有构造方法
         private Singleton() {}
         //2.本类中创建该类对象
         private static Singleton instance;
         //3.提供一个公共方法访问方式
         public static Singleton getInstance() {
             if (instance == null) {
                 instance = new Instance();
             }
             return instance;
         }
     }
    
  4. 懒汉式-方式2(线程安全)

     //单例类
     public class Sigleton {
         //1.私有构造方法
         private Singleton() {}
         //2.本类中创建该类对象
         private static Singleton instance;
         //3.提供一个公共方法访问方式
         public static synchronized Singleton getInstance() {
             if (instance == null) {
                 instance = new Instance();
             }
             return instance;
         }
     }
    
  5. 懒汉式-方式3(双重检查锁)

     //单例类
     public class Singleton {
         //1.私有构造方法
         private Singleton() {}
         //2.本类中创建该类对象 volatile保证有序性、可见性
         private static volatile Singleton instance;
         //3.提供一个公共方法访问方式
         public static Singleton getInstance() {
             if (instance == null) {
                 synchronized (Singleton.class) {
                     if(instance == null) {
                         instance = new Singleton ();
                     }
                 }
             }
             return null;
         }
     }
    
  6. 懒汉式-方式4(静态内部类)

     //单例类
     public class Singleton {
         //1.私有构造方法
         private Singleton() {}
         //2.定义一个静态内部类
         private static class SingletonHolder {
             //声明并初始化外部类对象
             private static final Singleton INSTANCE = new Singleton();
         }
         //3.提供一个公共方法访问方式
         public static Singleton getInstance () {
             return SingletonHolder.INSTANCE;
         }
     }
    

    静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何锁的情况下,保证了多线程的安全,并且没有任何影响和空间的浪费。

  7. 枚举方式(饿汉式)

     public enum Singleton {
         INSTANCE;
     }
    

问题

使用上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

  • 序列化反序列化

     //单例类
     public class Singleton implements Serializable {
         //1.私有构造方法
         private Singleton() {}
         //2.定义一个静态内部类
         private static class SingletonHolder {
             //声明并初始化外部类对象
             private static final Singleton INSTANCE = new Singleton();
         }
         //3.提供一个公共方法访问方式
         public static Singleton getInstance () {
             return SingletonHolder.INSTANCE;
         }
         
         //4.当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
         //解决序列化破坏单例
         public Object readResolve() {
             return SingletonHolder.INSTANCE;
         }
     }
     ​
     class Main {
         public static void main(String[] args) throw Exception {
             //writeObject2File();
             //两次打印的instance为Singleton的地址值,不同说明破坏了单例
             readObjectFromFile(); 
             readObjectFromFile();
         }
         
         //从文件读取数据(对象)
         public static void readObjectFromFile() throw Exception {
             //1.创建对象输入流对象
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\Users\zzw\desktop\a.txt"));
             //2.读取对象
             Singleton instance = (Singleton) ois.readObject();
             System.out.println(instance);
             //3.释放资源
             ois.close();
         }
         
         //像文件中写数据(对象)
         public static void writeObject2File() throw Exception {
             //1.获取Singleton对象
             Singleton instance = Singleton.getInstance();
             //2.创建对象输出流对象
             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\Users\zzw\desktop\a.txt"));
             //3.写对象
             oos.writeObject(instance);
             //4.释放资源
             oos.close;
         }
     }
    
  • 反射

     //单例类
     public class Singleton {
         //1.私有构造方法
         private Singleton() {
             synchronized (Singleton.class) {
                 //解决反射破坏单例
                 if (instance != null) {
                     throw new RunTimeException("不能创建多个实例对象!");
                 }
             }
         }
         //2.本类中创建该类对象
         private static Singleton instance;
         //3.提供一个公共方法访问方式
         public static Singleton getInstance () {
             if (instance == null) {
                 instance = new Singleton();
             }
             return instance;
         }
     }
     ​
     class Main {
         public static void main(String[] args) throw Exception {
             //1.获取Singleton字节码对象
             Class clazz = Singleton.class;
             //2.获取无参构造方法对象
             Constructor cons = class.getDeclaredConstructor();
             //3.取消访问检查
             cons.setAccessible(true);
             //4.创建Singleton对象
             Singleton s1 = (Singleton) cons.newInstance();
             Singleton s2 = (Singleton) cons.newInstance();
             System.out.println(s1 == s2); //false,已破坏单例模式
         }
     }
    

原型模式

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

结构

原型模式包含如下角色:

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

实现

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

浅克隆: 创建一个新对象,新对象的属性和原对象完全相同,对于非基本类型的属性,仍指向原有属性所指向的对象的内存地址。

深克隆: 创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象的地址。

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

 public class RealizeType implements Cloneable{
     public RealizeType() {
         System.out.println("具体原型对象创建成功!");
     }
 ​
     @Override
     public RealizeType clone() {
         System.out.println("具体原型复制成功!");
         try {
             return (RealizeType) super.clone();
         } catch (CloneNotSupportedException e) {
             throw new AssertionError();
         }
     }
 }
 ​
 class Main {
     public static void main(String[] args) {
         //创建原型类对象
         RealizeType realizeType = new RealizeType();
         //调用原型类中的clone()进行对象克隆
         RealizeType clone = realizeType.clone();
         System.out.println(realizeType == clone); //false
     }
 }

案例

用原型模式生成“三好学生”奖状

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。

 @Data
 public class Citation implements Cloneable {
     private String name;
 ​
 ​
     @Override
     public Citation clone() {
         try {
             return (Citation) super.clone();
         } catch (CloneNotSupportedException e) {
             throw new AssertionError();
         }
     }
 ​
     public void show() {
         System.out.println(name + "同学:在2022学年第一学期中表现优秀,被评为三好学生。特发此奖!");
     }
 }
 ​
 class Test {
     public static void main(String[] args) {
         Citation citation = new Citation();
         Citation clone = citation.clone();
 ​
         citation.setName("张三");
         clone.setName("李四");
 ​
         citation.show();
         clone.show();
     }
 }

使用场景

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

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

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

eg:如果要造一个超人,使用工厂方法模式,直接产生出来的的是一个力大无穷、能够飞翔、内裤外穿的超人;而使用建造者模式,则需要组装手、头、脚、躯体等部分,然后再把内裤外穿,于是一个超人就诞生了。

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

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

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

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