设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分别为创建型模式、结构型模式和行为型模式。
创建型模式(Creational Patterns):主要用于对象的创建,包括多个不同的模式,如工厂方法模式、抽象工厂模式、建造者模式、单例模式和原型模式等。这些模式都有助于降低系统耦合度,并提高代码的可重用性和可扩展性。
参考文章:
设计模式——设计模式介绍和单例设计模式
设计模式——工厂模式
设计模式——原型模式
【设计模式】结合StringBuilder源码,探析建造者模式的特性和应用场景
单例模式
介绍
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
比如 Hibernate 的 SessionFactory,它充当数据存储源的代理,并负责创建 Session 对象。SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式
优点:
节省资源:单例模式实例只有一个,可以避免重复创建对象,从而节省了资源,提高了系统性能。
 
管理全局变量:单例模式可以用于管理全局状态和变量,方便在整个系统中共享数据。
 
简化系统架构:使用单例模式可以简化系统架构,减少类的数量和接口的复杂度。
 
缺点:
- 可能引发并发问题:单例模式在多线程中使用时,需要保证线程安全,否则可能会引发并发问题。
 
- 可能增加系统复杂性:过度使用单例模式可能会增加系统复杂性,导致代码难以维护。
 
- 难以调试:由于单例模式全局共享状态,可能会导致调试过程中的问题难以定位和测试。
 
注意事项和使用场景
- 单例模式保证了系统内存中该类只存在一个对象,节省系统资源,对一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
 
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
 
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多但又经常用到的对象(即:重量级对象)、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
 
八种单例模式的创建方式
- 饿汉式(静态常量):线程安全,没用到会浪费内存。
 
- 饿汉式(静态代码块):线程安全,没用到会浪费内存。
 
- 懒汉式(线程不安全):懒加载,线程不安全。即用到时候再实例化,多线程时可能创建多个实例。不要用这种方式。
 
- 懒汉式(线程安全,同步方法):线程安全,但效率低(每次获取实例都要加锁),不推荐。
 
- 懒汉式(线程不安全,同步代码块):线程不安全,不要用这种方式。
 
- 双重检查
 
- 静态内部类
 
- 枚举
 
饿汉式(静态常量)
线程安全,可用,但如果没用到会浪费内存。
步骤示例:
1 2 3 4 5 6 7 8 9 10
   | public class Singleton {          private Singleton() {}          private static final Singleton instance = new Singleton();          public static Singleton getInstance() {         return instance;     } }
  | 
 
优缺点
- 优点:写法简单,就是在类装载的时候就完成实例化(类变量在JVM类加载的准备、初始化阶段会赋值)。避免了线程同步问题
 
- 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。若从始至终从未使用过这个实例,则会造成内存的浪费。
 
这种方式基于 classloder 机制避免了多线程的同步问题。不过,instance 在类装载时就实例化,在单例模式中大多数都是调用getlnstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 Lazy loading 的效果。
饿汉式(静态代码块)
线程安全,可用,但是可能造成内存浪费
步骤示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class Singleton {          private Singleton() {}          private static Singleton instance;          static {         instance = new Singleton();     }          public static Singleton getInstance() {         return instance;     } }
  | 
 
优缺点:这种方式和上面静态常量的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
懒汉式(线程不安全)
懒加载,线程不安全。即用到时候再实例化,多线程时可能创建多个实例。不要用这种方式。
步骤示例:
1 2 3 4 5 6 7 8 9 10 11
   |  private Singleton() {}
  private static Singleton instance;
  public static Singleton getInstance() {     if (instance == null) {         instance = new Singleton();     }     return instance; }
 
  | 
 
优缺点
- 起到了 Lazy Loading 的效果,但是只能在单线程下使用
 
- 如果在多线程下,一个线程进入了判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,便会产生多个实例
 
懒汉式(线程安全,同步方法)
线程安全,但效率低(每次获取实例都要加锁),不推荐。
步骤示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class Singleton {          private Singleton() {}          private static Singleton instance;               public static synchronized Singleton getInstance() {         if (instance == null) {             instance = new Singleton();         }         return instance;     } }
  | 
 
优缺点
- 解决了线程不安全问题
 
- 效率太低了,每个线程在想获得类的实例时候,执行
getlnstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低 
懒汉式(线程不安全,同步代码块)
线程不安全,在实际开发中,不能使用这种方式
步骤示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | public class Singleton {          private Singleton() {}          private static Singleton instance;          public static Singleton getInstance() {         if (instance == null) {                              synchronized (Singleton.class) {                 instance = new Singleton();             }         }         return instance;     } }
  | 
 
优缺点
- 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
 
- 但这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
 
双重检查(推荐,线程安全、懒加载)
在实际开发中,推荐使用这种单例设计模式
- 构造器私有化
 
- 类的内部创建对象引用,同时用volatile关键字修饰
 
- 向外暴露一个静态的公共方法,加入同步处理的代码块,并进行双重判断,解决线程安全问题
 
步骤示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | public class Singleton {          private Singleton() {}                 private static volatile Singleton instance;            public static Singleton getInstance() {         if (instance == null) {                                          synchronized (Singleton.class) {                     if (instance == null) {                            instance = new Singleton();                 }             }         }         return instance;     } }
  | 
 
优缺点
- Double-Check 概念是多线程开发中常使用到的,我们进行了两次检查,这样就可以保证线程安全了
 
- 这样实例化代码只用执行一次,后面再次访问时直接 return 实例化对象,也避免的反复进行方法同步
 
- 线程安全;延迟加载;效率较高
 
静态内部类(推荐)
线程安全、延迟加载、效率高,推荐使用。 
步骤: 
1)构造器私有化
 
2)定义一个静态内部类,内部定义当前类的静态属性
 
3)向外暴露一个静态的公共方法
 
知识加油站:
类的加载机制是延迟加载的,也就是说,只有在需要使用到某个类时才会进行加载。
 
类加载过程中会加载其所有静态成员到内存中,包括静态变量、静态成员方法和静态内部类。
 
类加载包括加载、链接(验证、准备(为类变量分配内存并赋零值)、解析)、初始化(类变量赋初值、执行静态语句块)。
 
步骤示例:
1 2 3 4 5 6 7 8 9 10 11 12
   | public class Singleton {          private Singleton() {}          private static class SingletonInstance {         private static final Singleton instance = new Singleton();     }          public static Singleton getInstance() {         return SingletonInstance.instance;     } }
  | 
 
优缺点
- 这种方式采用了类装载的机制,来保证初始化实例时只有一个线程
 
- 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,才会装载Singletonlnstance 类,从而完成 Singleton 的实例化。
 
- 类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
 
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
 
枚举(推荐)
推荐,线程安全,延迟加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | public enum Singleton {     INSTANCE;     public void sayHello() {         System.out.println("Hello World");     } }
  public class SingletonTest {     public static void main(String[] args){         Singleton instance = Singleton.INSTANCE;         Singleton instance2 = Singleton.INSTANCE;         System.out.println(instance == instance2);             System.out.println(instance.hashCode());         System.out.println(instance2.hashCode());     }     public enum Singleton {         INSTANCE;         public void sayHello() {             System.out.println("Hello World");         }     } }
  | 
 
优缺点
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
 
- 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
 
JDK 源码里单例模式分析
1 2 3 4 5 6
   | public class Runtime {     private static Runtime currentRuntime = new Runtime();     public static Runtime getRuntime(){ return currentRuntime; }          private Runtime(){} }
  | 
 
工厂模式
工厂模式介绍
以提高复杂性为代价,提高可维护性和复用性。 
工厂模式是一种创建型设计模式(跟创建对象有关),它主要解决了对象的创建过程中的灵活性和可维护性问题。工厂模式允许在不暴露对象创建逻辑的情况下,统一由工厂类负责创建对象并返回,从而降低了代码的耦合性。
简单工厂模式 
简单工厂模式是通过工厂类(抽象或非抽象)的静态方法来创建对象实例,并将实例作为方法的返回值。在使用时,只需要调用该静态方法来创建对象,而无需创建工厂类的实例。静态工厂方法也是简单工厂模式的一种。
 
工厂方法模式
工厂方法模式是在抽象工厂类里定义创建抽象产品对象的抽象方法,由具体工厂类决定要实例化的产品类。抽象工厂类的构造器里调用“创建抽象产品对象”的抽象方法,具体工厂类重写抽象方法,按情景实例化具体产品类。使用时直接创建具体工厂对象,将执行抽象工程类构造器调用重写后的创建方法,创建具体产品对象。
一个抽象工厂类能派生多个具体工厂类。每个具体工厂类只能建立一个具体产品类的实例。一个抽象产品类能派生多个具体产品类。 
 
抽象工厂模式
抽象工厂模式是抽象工厂接口里定义创建抽象产品对象的抽象方法,各具体工厂实现类根据情景创建具体产品对象。
一个抽象工厂类能够派生出多个具体工厂类。每一个具体工厂类能够建立多个具体产品类的实例。多个抽象产品类,每个抽象产品类能够派生出多个具体产品类。
 
优点:
- 可以避免直接使用new关键字创建对象带来的耦合性,提高了代码的可维护性;
 
- 可以将对象的创建逻辑封装到一个工厂类中,提高了代码的复用性;
 
- 可以对对象的创建逻辑进行统一管理,方便代码的维护和升级。
 
缺点:
- 增加了代码的复杂度,需要创建工厂类,会增加代码规模;
 
- 如果产品类发生变化,需要修改工厂类,可能会影响到其他代码的功能。
 
综上所述,工厂模式是一种常用的创建型设计模式,可以提高代码的可维护性、复用性和灵活性。但是,在使用时需要权衡利弊,避免过度使用,增加代码的复杂度。
工厂模式的意义:
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
设计模式的依赖抽象原则:
- 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中并返回。有的书上说,变量不要直接持有具体类的引用
 
- 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
 
- 不要覆盖基类中已经实现的方法
 
披萨项目需求
披萨项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
 
- 披萨的制作有 prepare、bake、cut、box
 
- 完成披萨店订购功能
 
传统方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   | public abstract class AbstractPizza {     protected String name;     public void setName(String name) {         this.name = name;     }          public abstract void prepare();     public void bake() {         System.out.println(name + " baking...");     }     public void cut() {         System.out.println(name + " cutting...");     }     public void box() {         System.out.println(name + " boxing...");     } }
  public class GreekPizza extends Pizza {     @Override     public void prepare() {         setName("GreekPizza");         System.out.println(name + " preparing...");     } }
  public class CheesePizza extends Pizza {     @Override     public void prepare() {         setName("CheesePizza");         System.out.println(name + " preparing...");     } }  
 
  public class OrderPizza {     public OrderPizza() {         Pizza pizza = null;         String orderType;         do {                          orderType = getType();             if ("cheese".equals(orderType)) {                 pizza = new CheesePizza();             } else if ("greek".equals(orderType)) {                 pizza = new GreekPizza();             } else {                 System.out.println("输入类型错误,程序退出");                 break;             }             pizza.prepare();             pizza.bake();             pizza.cut();             pizza.box();         } while (true);     }     private String getType() {         System.out.println("请输入披萨类型:");         BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));         try {             return reader.readLine();         } catch (IOException e) {             e.printStackTrace();             return "";         }     } }
 
  public class PizzaStore {     public static void main(String[] args) {         new stubOrderPizza(); }
  | 
 
传统方式优缺点:
改进的思路分析:
分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着也需要修改。而创建Pizza的代码,往往有多处
思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza 对象的代码就不需要修改了 ==> 简单工厂模式
非静态简单工厂模式
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
 
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
 
- 在软件开发中,如果要用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式
 
简单工厂模式优化披萨项目
创建一个披萨工厂类,用于根据披萨类别创建披萨对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   |  public class PizzaFactory {     public Pizza createPizza(String orderType) {         Pizza pizza = null;         switch (orderType) {             case "cheese": pizza = new CheesePizza();break;             case "greek":  pizza = new GreekPizza(); break;             case "pepper": pizza = new PepperPizza(); break;             default: break;         }         return pizza;     } }
  public class OrderPizza {     private PizzaFactory pizzaFactory;     public OrderPizza(PizzaFactory pizzaFactory) {         this.pizzaFactory = pizzaFactory;         orderPizza();     }     public void orderPizza() {         Pizza pizza = null;         do {             pizza = pizzaFactory.createPizza(getType());             if (pizza == null) {                 System.out.println("Failed to Order Pizza");             } else {                 pizza.prepare();                 pizza.bake();                 pizza.cut();                 pizza.box();             }         } while (true);             }      }
 
  | 
 
静态简单工厂模式
静态工厂模式也是简单工厂模式的一种,只是将工厂方法改为静态方法
静态工厂模式通过工厂类的静态方法来创建对象实例,并将实例作为方法的返回值。在使用时,只需要调用该静态方法来创建对象,而无需创建工厂类的实例。
静态工厂模式的优点在于可以简化代码实现,无需创建工厂对象的实例,提高代码的简洁性;对比普通工厂模式的优点在于更方便扩展和修改,如果需要新增一个产品线,只需要添加具体的产品类和对应的工厂类即可,无需修改已有的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  public class PizzaFactory {     public static Pizza createPizza2(String orderType) {              } }
  public class OrderPizza {     public OrderPizza() {         Pizza pizza;         do {                          pizza = PizzaFactory.createPizza(getType());                          } while (true);     } }
 
  | 
 
工厂方法模式
工厂方法模式:定义创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
实现方式:抽象工厂类的构造器里调用“创建抽象产品对象”的抽象方法,具体工厂类重写抽象方法,按情景实例化具体产品类。
一个抽象产品能够派生出多个具体产品类。 一个抽象工厂类能够派生出多个具体工厂类。每一个具体工厂类只能建立一个具体产品类的实例。 
工厂方法模式包含以下几个角色:
抽象产品类(Product):定义了产品的抽象接口,具体产品将按照抽象产品类所定义的接口来实现。
 
具体产品类(Concrete Product):是抽象产品类的一个具体实现,定义了具体产品的实现方法。
 
抽象工厂类(Factory):是工厂方法模式的核心,定义了工厂类需要实现的接口,用于创建产品对象。
 
具体工厂类(Concrete Factory):是抽象工厂类的一个具体实现,实现了工厂方法创建具体对象实例的逻辑。
 
披萨项目新的需求:客户可以点不同口味的披萨,如北京奶酪 Pizza、北京胡椒 Pizza 或者是伦敦奶酪 Pizza、伦敦胡椒 Pizza
思路1:使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaFactory、LDPizzaFactory 等等。从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好
思路2:工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   |  public class PizzaStore{     public static void main(String[] args) {         String loc = "bj";         if (loc.equals("bj")) {                             new BJOrderPizza();         } else {             new LDOrderPizza();                      }     } }
  public abstract class AbstractOrderPizzaFactory{          public void OrderPizza() {         Pizza pizza = null;         do {             pizza = createPizza(getType());             if (pizza == null) {                 System.out.println("Failed to Order Pizza");             } else {                 pizza.prepare();                 pizza.bake();                 pizza.cut();                 pizza.box();             }         } while (true);     }           public abstract Pizza createPizza(String orderType);      }
  public class LDOrderPizza extends AbstractOrderPizzaFactory{     @Override     public Pizza createPizza(String orderType) {         Pizza pizza = null;         switch (orderType) {             case "cheese": pizza = new LDCheesePizza(); break;             case "pepper": pizza = new LDPepperPizza(); break;             default: break;         }         return pizza;     } }
  public class BJOrderPizza extends AbstractOrderPizzaFactory {     @Override     public Pizza createPizza(String orderType) {         Pizza pizza = null;         switch (orderType) {             case "cheese": pizza = new BJCheesePizza(); break;             case "pepper": pizza = new BJPepperPizza(); break;             default: break;         }         return pizza;     } }
 
  | 
 
抽象工厂模式
抽象工厂接口里有创建抽象产品的方法,各具体工厂实现类根据情景创建具体产品对象。
- 抽象工厂模式:定义了一个接口用于创建相关或有依赖关系的对象簇,而无需指明具体的类
 
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合
 
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
 
- 将工厂抽象成两层,抽象工厂接口和具体工厂类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展
 
 抽象工厂接口里有创建抽象产品的方法,各具体工厂实现类根据情景创建具体产品对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   | public interface AbsPizzaFactory {     Pizza createPizza(String orderType); } public class BJPizzaFactory implements AbsPizzaFactory {     @Override     public Pizza createPizza(String orderType) {         Pizza pizza = null;         switch (orderType) {                      }         return pizza;     } } public class LDPizzaFactory implements AbsPizzaFactory {     @Override     public Pizza createPizza(String orderType) {         Pizza pizza = null;         switch (orderType) {                      }         return pizza;     } }
  public class OrderPizza {          public OrderPizza(AbsFactory factory) {         setFactory(factory);     }     public void orderPizza(AbsFactory factory) {         Pizza pizza = null;         String orderType = "";                 this.factory = factory;         do {                          pizza = factory.createPizza(orderType);             if (pizza != null) {                      pizza.prepare();                 pizza.bake();                 pizza.cut();                 pizza.box();             } else {                System.out.println("Failed to Order Pizza");                 break;             }         } while (true);                     } }
  | 
 
1 2
   |  new OrderPizza(new BJFactory());
 
  | 
 
JDK 源码分析
JDK 中的 Calendar 类中,就使用了静态简单工厂模式。
静态简单工厂模式:由一个工厂对象(可以是抽象类,可以是非抽象类)决定创建出哪种产品类的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
   |  public class Test {     public static void main(String[] args) {                  Calendar cal = Calendar.getInstance();                  System.out.println("年:" + cal.get(Calendar.YEAR));         System.out.println("月:" + (cal.get(Calendar.MONTH) + 1));         System.out.println("日:" + cal.get(Calendar.DAY_OF_MONTH));         System.out.println("时:" + cal.get(Calendar. HOUR_OF_DAY)) ;         System.out.println("分:" + cal.get(Calendar. MINUTE)) ;         System.out.println("秒:" + cal. get(Calendar.SECOND));     } }
 
  public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {          public static Calendar getInstance() {         Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);         return createCalendar(defaultTimeZone(aLocale), aLocale);     }          private static Calendar createCalendar(TimeZone zone, Locale aLocale){         CalendarProvider provider = LocaleProviderAdapter                                 .getAdapter(CalendarProvider.class, aLocale)                                  .getCalendarProvider();         if (provider != null) {             try {                 return provider.getInstance(zone, aLocale);             } catch (IllegalArgumentException iae) {                              }         }         Calendar cal = null;         if (aLocale.hasExtensions()) {             String caltype = aLocale.getUnicodeLocaleType("ca");             if (caltype != null) {                 switch (caltype) {                     case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break;                     case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break;                     case "gregory": cal = new GregorianCalendar(zone, aLocale); break;                 }             }         }         if (cal == null) {                                                                                                        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {                 cal = new BuddhistCalendar(zone, aLocale);             } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"                        && aLocale.getCountry() == "JP") {                 cal = new JapaneseImperialCalendar(zone, aLocale);             } else {                 cal = new GregorianCalendar(zone, aLocale);             }         }         return cal;     } }
 
  | 
 
原型模式
经典的克隆羊问题(复制10只属性相同的羊)
问题描述:现在有一只羊,姓名为 Tom,年龄为 1,颜色为白色,请编写程序创建和 Tom 羊属性完全相同的 10 只羊。
传统方案:循环new对象
实现方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   |  public class Sheep {     private String name;     private Integer age;       public Sheep(String name, Integer age) {         this.name = name;         this.age = age;     }      }
  public class Client {     public static void main(String[] args) {         for (int i = 0; i < 10; i++) {             Sheep sheep = new Sheep("Tom", 1, "白色");             System.out.println(sheep);         }     } }
 
  | 
 
优缺点和改进思路
优点:好理解,简单易操作
缺点:
- 每次获取再复制效率低:在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
 
- 不灵活:总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
 
改进的思路分析:Object 类的 clone() 方法
Object 类是所有类的根类,Object 类提供了一个 clone 方法,该方法可以将一个 Java 对象复制一份,但是对应的类必须实现Cloneable接口,该接口表示该类能够复制且具有复制的能力 ==> 原型模式
原型模式(Prototype模式)
基本介绍
原型模式(Prototype 模式):用原型实例指定创建对象种类,并通过拷贝原型创建新的对象
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
原理:将一个原型对象传给要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
创建型设计模式:关注如何有效地创建对象,以满足不同的需求和情境。
包括:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
原理及代码演示
- Prototype:原型类。包含一个用于复制对象的克隆方法。可以使用Cloneable接口作为原型接口。
 
- ConcretePrototype:具体原型类。实现原型接口、重写克隆方法clone()的具体类。
 
- Client:让一个原型对象克隆自己,创建一个属性相同的对象
 
原型接口: 可以是Cloneable接口也可以是自定义带clone()方法的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  interface Prototype extends Cloneable {     Prototype clone(); }
  class ConcretePrototype implements Prototype {     @Override     public Prototype clone() {         try {             return (Prototype) super.clone();              } catch (CloneNotSupportedException e) {             e.printStackTrace();             return null;         }     } }
  public class Client {     public static void main(String[] args) {         ConcretePrototype prototype = new ConcretePrototype();                              ConcretePrototype clonedObject = (ConcretePrototype) prototype.clone();      } }
 
  | 
 
原型模式解决克隆羊问题
问题回顾:
 现在有一只羊,姓名为 Tom,年龄为 1,颜色为白色,请编写程序创建和 Tom 羊属性完全相同的 10 只羊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | 
  @Data public class Sheep implements Cloneable {     private String name;     private Integer age;     private String color;     public Sheep(String name, Integer age, String color) {         this.name = name;         this.age = age;         this.color = color;     }     @Override     protected Object clone() {         Sheep sheep = null;         try {             sheep = (Sheep) super.clone();         } catch (Exception e) {             e.printStackTrace();         }         return sheep;     } }
  public class Client {     public static void main(String[] args) {         Sheep sheep = new Sheep("Tom", 1, "白色");         for (int i = 0; i < 10; i++) {             Sheep sheep1 = (Sheep) sheep.clone();             System.out.println(sheep1);         }     } }   
 
  | 
 
优缺点和使用场景
优点
构造方法复杂时开销小:如果构造函数的逻辑很复杂,此时通过new创建该对象会比较耗时,那么就可以尝试使用克隆来生成对象。
 
运行时动态创建对象:不用重新初始化对象,而是动态地获得对象运行时的状态
 
开闭原则(OCP原则):如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需更改客户端代
码。相反,如果使用new方式,就需要在客户端修改构造参数。这使得系统更加灵活和可维护。
 
对象封装性:原型模式可以帮助保护对象的封装性,因为客户端代码无需了解对象的内部结构,只需知道如何克隆对象。
 
多态性:原型模式支持多态性,因为克隆操作可以返回具体子类的对象,而客户端代码不需要关心对象的具体类。
 
缺点
构造方法简单时开销大:如果构造函数的逻辑很简单,原型模式的效率不如new,因为JVM对new做了相应的性能优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | public static void main(String[] args) {          long startTime = System.currentTimeMillis();     Sheep sheep = new Sheep("Tom", 1, "白色");          for (int i = 0; i < 100000; i++) {         sheep.clone();     }     long midTime = System.currentTimeMillis();          System.out.println("克隆生成对象耗费的时间:" + (midTime - startTime) + "ms");          for (int i = 0; i < 100000; i++) {         new Sheep();     }          System.out.println("new生成对象耗费的时间:" + (System.currentTimeMillis() - midTime) + "ms"); }
  | 
 
要注意深拷贝和浅拷贝问题:实现Cloneable接口时,如果具体原型类直接返回super.clone(),则是浅拷贝。克隆的对象里,引用类型变量只拷贝引用,依然指向旧的地址。
 
代码复杂性:在实现深拷贝的时候可能需要比较复杂的代码。设计模式一般都是以代码复杂性为代价,提高可扩展性、可读性。
 
适用场景
- 构造方法复杂:要创建的对象构造方法逻辑很复杂,即创建新的对象比较复杂时,使用原型模式会比直接new效率更高;
 
- 经常需要克隆:经常要创建一个和原对象属性相同的对象时,可以考虑原型模式。
 
Spring源码中的原型模式
Spring 框架Bean的生命周期中,ApplicationContext类的getBean()方法中,有用到原型模式。
获取Bean时会判断配置的Bean是单例还是原型,如果是原型,则用原型模式创建Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13
   |  if (mbd.issingleton()){...} else if (mbd.isPrototype()){          0bject prototypeInstance = null;     try {         beforePrototypeCreation(beanName);         prototypeInstance = createBean(beanName, mbd, args);     } finally {         afterPrototypeCreation(beanName);     }     beanInstance = getobjectForBeanInstance(prototypeInstance, name, beanName, mbd); }
 
  | 
 
验证:bean指定原型模式后,getBean()获取到的多个Bean是不同对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | @Component("id01") @Scope("prototype")  public class Monster {     private String name;     private int health;           public Monster() {}     public Monster(String name, int health) {         this.name = name;         this.health = health;     }      }
  public class ProtoType {     public static void main(String[] args) {                  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");                  Object bean = applicationContext.getBean("id01");         System.out.println("bean: " + bean);          Object bean2 = applicationContext.getBean("id01");         System.out.println("bean2: " + bean2);          System.out.println(bean == bean2);      } }
  | 
 
也可以用xml形式注册Bean:
1 2
   |  <bean id="id01" class="com.atquigu.spring.bean.Monster" scope="prototype"/>
 
  | 
 
浅拷贝和深拷贝
浅拷贝:引用类型变量拷贝引用
- 浅拷贝:拷贝后对象是新地址,基本类型变量拷贝值,引用类型变量拷贝引用。只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。
 
实现方案:具体原型类直接返回super.clone()
实现Cloneable 接口,重写 clone()方法, 直接返回super.clone()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | public class Person implements Cloneable {        public int age;                                 public Person(int age) {         this.age = age;     }     @Override     public Person clone() {         try {             return (Person) super.clone();         } catch (CloneNotSupportedException e) {             return null;         }     }     public static void main(String[] args) {         Person p1 = new Person(18);         Person p2 = p1.clone();                     p2.age = 20;         System.out.println(p1 == p2);              System.out.println(p1.age);          } }
  | 
 
深拷贝:引用类型变量拷贝值
深拷贝:拷贝后对象是新地址,基本类型变量拷贝值,引用类型变量拷贝克隆后的值。创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。反序列化创建对象是深拷贝。 
实现方案:具体原型类专门克隆引用类型变量
实现Cloneable 接口,重写 clone()方法, 给super.clone()的引用类型成员变量也clone()一下,然后再返回克隆的对象。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | public class Person implements Cloneable {     public int age;                                     public int[] arr = new int[] {1, 2, 3};     public Person(int age) {         this.age = age;     }     @Override     public Person clone() {         try {             Person person = (Person) super.clone();             person.arr = this.arr.clone();                      return person;         } catch (CloneNotSupportedException e) {             return null;         }     } }
  | 
 
建造者模式
经典的盖房子问题
问题描述:
- 建房子过程:打桩、砌墙、封顶。虽然建造过程一样,但实际造的房子有差别,因为房子有各种各样的,比如普通房,高楼,别墅。
 
- 需求:写代码能建造各类房子
 
传统方案盖房子
实现方案:产品和创建产品过程耦合
创建以下几个类:
- AbsHouse(抽象盖房类):包含打桩、砌墙、封顶三个方法;
 
- NormalRoom(普通房间类)、Villa(别墅类):继承抽象盖房类,根据情况重写三个方法;
 
抽象盖房类:包含打桩、砌墙、封顶三个方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   |  public abstract class AbsHouse {     protected abstract void piling();      protected abstract void walling();      protected abstract void capping();      public void build() {          piling();         walling();         capping();     } }
 
  public class NormalRoom extends AbsHouse {     @Override     protected void piling() {         System.out.println("普通房打桩...");     }     @Override     protected void walling() {         System.out.println("普通房砌墙...");     }     @Override     protected void capping() {         System.out.println("普通房封顶...");     } }
  public class HighRise extends AbsHouse {     @Override     protected void piling() { ... }     @Override     protected void walling() { ... }     @Override     protected void capping() { ... } }
  public class Villa extends AbsHouse {     @Override     protected void piling() { ... }     @Override     protected void walling() { ... }     @Override     protected void capping() { ... } }
 
  | 
 
优缺点和改进思路
优点:简单,好理解易操作
缺点:产品和创建产品过程耦合:没有设计缓存层对象,程序的扩展和维护不好。也就是说,这种设计方案把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性太高。
改进思路分析:使用建造者模式,将产品和产品建造过程解耦。
建造者模式/生成器模式
基本介绍
建造者模式(Builder Pattern):使用多个步骤来创建一个复杂对象,而不是在一个构造函数或工厂方法中直接返回该对象。它将产品和产品建造过程进行了解耦。
建造者模式又叫生成器模式,是一种创建型设计模式。
特点:
- 分步骤创建对象:对象的构建是分多个步骤进行的,而不是直接使用构造方法new对象,适用于需要分阶段构造的复杂对象。
 
- 同一个建造过程:同样的构建过程可以创建不同的对象表示(不同的配置组合)。
 
四个角色
Product(产品):一个包含多个部件的类。
每个部件是一个成员变量。例如房间包括地基、墙和屋顶等组件,又例如电脑包括CPU、内存条、主板等组件。
 
Builder(抽象建造者):一个包含产品变量及其所有部件建造方法的抽象类或接口
包含每个部件的建造方法,和一个返回产品的build()方法。例如房间的打桩、砌墙、封顶过程。
 
ConcreteBuilder(具体建造者):实现抽象建造者所有抽象建造方法
 
Director(指挥者):一个以抽象建造者为变量的、包含建造方法的类
- 包含一个抽象建造者变量,和一个建造并返回产品的build()方法。
 
- 实际建造产品时,先创建一个指挥者对象,然后设置它的建造者变量,最后调用它的build()方法返回产品。
 
 
优缺点和使用场景
优点:
- 耦合性降低:
- 产品与建造解耦:客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
 
- 具体建造者之间解耦:每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
 
 
- 代码可读性高:可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
 
- 开闭原则:增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”,即代码对修改不开放,而对扩展开放。
 
缺点:
- 代码复杂性:没了解过建造者模式的人阅读代码更困难,这也是设计模式的通用缺点。
 
- 过度设计风险:建造者模式只适合复杂产品对象,太简单的产品对象则没必要使用,或者建造方式只有一种的产品,都没必要使用。这也是设计模式的通用缺点。
 
使用场景:
- 复杂产品对象:部件比较多的产品,例如房屋有墙、屋顶、地基、横梁等等部件。
 
- 建造方式多样:例如房屋对于同一些材料,可以建成普通房屋、高楼等等。
 
- 建造产品共同点:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
 
不适用场景:
建造者模式和抽象工厂模式的区别
区别: 
- 抽象工厂模式:适用于不同产品的情况。用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类)。
 
- 建造者模式:适用于一个产品有多个部件的情况。用来创建一种类型的复杂对象,通过设置不同可选参数,“定制化”创建不同对象。
 
示例:顾客走进一家餐馆点餐
- 工厂模式:根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。
 
- 建造者模式:对于披萨来说,用户又有各种配料可以定制,我们通过建造者模式根据用户选择的不同配料来制作披萨。
 
建造者模式盖房子
产品类
产品类包含多个部件的对象。
每个部件是一个成员变量。例如房间包括地基、墙和屋顶等组件,又例如电脑包括CPU、内存条、主板等组件。
1 2 3 4 5 6 7 8 9
   |  @Data public class House {     private String pile;      private String wall;      private String roof; 
       }
 
  | 
 
抽象建造者
抽象建造者是个抽象类,包含每个部件的建造方法,和一个返回产品的build()方法
例如房间的打桩、砌墙、封顶过程。
1 2 3 4 5 6 7 8 9 10 11 12 13
   |  public abstract class HouseBuilder {     private House house = new House();           public abstract void piling();      public abstract void walling();      public abstract void capping(); 
           public House build() {         return house;     } }
 
  | 
 
具体建造者
具体建造者是抽象建造者的实现类,实现各个部件具体构建逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  public class NormalRoomBuilder extends HouseBuilder {     @Override     public void piling() { System.out.println("普通房打桩..."); }     @Override     public void walling() { System.out.println("普通房砌墙..."); }     @Override     public void capping() { System.out.println("普通房封顶..."); } } public class HighRiseBuilder extends HouseBuilder {     @Override     public void piling() { ... }     @Override     public void walling() { ... }     @Override     public void capping() { ... } }
 
  | 
 
指挥者
指挥类包含一个抽象建造者变量,和一个建造并返回产品的build()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  public class HouseDirector {          private HouseBuilder houseBuilder;     public HouseDirector() {}     public void setHouseBuilder(HouseBuilder houseBuilder) {         this.houseBuilder = houseBuilder;     }          public House buildHouse() {                  houseBuilder.piling();         houseBuilder.walling();         houseBuilder.capping();         return houseBuilder.build();     } }
 
  | 
 
测试类
步骤:
- 创建指挥者:创建一个指挥者对象
 
- 设置建造者:设置它的建造者变量为具体建造者对象
 
- 返回产品:调用它的build()方法返回产品。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class BuilderTest {     public static void main(String[] args) {                  HouseDirector houseDirector = new HouseDirector();         House house;                  houseDirector.setHouseBuilder(new NormalRoomBuilder());                  house = houseDirector.buildHouse();                  houseDirector.setHouseBuilder(new HighRiseBuilder());         house = houseDirector.buildHouse();     } }
  | 
 
StringBuilder 中的建造者模式
JDK源码中的建造者模式
StringBuilder不是严格的建造者模式,但是使用了建造者模式思想。
1 2 3 4 5 6
   | 
  List<String> ansList = list.stream().filter(item -> item.length() == 3).collect(Collectors.toList());
  LocalDate date = LocalDate.of(2023, Month.JULY, 4); LocalDateTime dateTime = LocalDateTime.of(2023, 7, 4, 10, 30);
 
  | 
 
角色分析
产品:char数组
char数组,数组可以包含多个元素,每个元素是一个部件。
1 2 3 4
   | abstract class AbstractStringBuilder implements Appendable, CharSequence {          char[] value; }
  | 
 
抽象建造者:AbstractStringBuilder
AbstractStringBuilder包含append()、delete()等方法,用来对产品(字符数组)里的部件(数组内每个元素)进行追加和删除。
部件组装方法:append() 、delete()
以append()为例:
核心流程:
- 判空:如果传入的字符串为null,则不追加
 
- 校验扩容:若新容量大于当前数组长度,则进行扩容
 
- 拷贝数组:校验数组越界后,调用System.arraycopy()拷贝数组
 
具体代码: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   |  abstract class AbstractStringBuilder implements Appendable, CharSequence {     char[] value;      int count;        
 
 
 
 
      public AbstractStringBuilder append(String str) {                  if (str == null)             return appendNull();         int len = str.length();                  ensureCapacityInternal(count + len);                  str.getChars(0, len, value, count);                  count += len;         return this;     }          private void ensureCapacityInternal(int minimumCapacity) {                  if (minimumCapacity - value.length > 0) {             value = Arrays.copyOf(value, newCapacity(minimumCapacity));         }     }     public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {                  if (srcBegin < 0) {             throw new StringIndexOutOfBoundsException(srcBegin);         }                  if (srcEnd > value.length) {             throw new StringIndexOutOfBoundsException(srcEnd);         }                  if (srcBegin > srcEnd) {             throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);         }                  System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);     }          }
 
  | 
 
具体建造者:重写append()返回类型
StringBuilder
具体建造者StringBuilder继承了AbstractStringBuilder,并重写了append()方法。
append()逻辑都是用父类的append()方法,主要重写的地方在于返回类型由父类AbstractStringBuilder改成了子类StringBuilder
重写规则:重写时
- 返回类可以是原返回类的子类。例如工厂方法设计模式里,抽象工厂类的createObject()方法返回值是抽象产品类,具体工厂类的createObject()方法返回类是具体产品类
 
- 访问权限不能比其父类更为严格
 
- 抛出异常不能比父类更广泛
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   |  public final class StringBuilder extends AbstractStringBuilder implements Serializable, CharSequence {     
 
 
 
      @Override     public java.lang.StringBuilder append(String str) {          super.append(str);         return this;     }      }
 
  | 
 
StringBuffer
相比StringBuilder的append()方法,主要修改了返回值,方法内第一步清空toStringCache变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   |  public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {     
 
 
 
      private transient char[] toStringCache;
      
 
 
 
 
 
 
      @Override     public synchronized java.lang.StringBuffer append(String str) {                  toStringCache = null;         super.append(str);         return this;     }     
 
 
 
      @Override     public synchronized int length() {         return count;     }     @Override     public synchronized int capacity() {         return value.length;     } }
 
  | 
 
扩展:String、StringBuffer、Stringbuilder有什么区别
得分点:是否可变、复用率、效率、线程安全问题
标准回答
**String:**不可变字符序列,效率低,但是复用率高、线程安全。
- 不可变:指String对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。
 
- 复用率高:指String类型对象创建出来后归常量池管,可以随时从常量池调用同一个String对象。StringBuffer和StringBuider在创建对象后一般要转化成String对象才调用。
 
StringBuffer和StringBuilder都是字符序列可变的字符串,方法也一样,有共同的父类AbstractStringBuilder。 
- StringBuilder:可变字符序列、效率最高、线程不安全
 
- StringBuffer:可变字符序列、效率较高(增删)、线程安全
 
扩展:为什么StringBuffer是线程安全的
点进源码后发现,有线程同步风险的方法(例如length()、append()、delete()等)都加了synchronized锁,锁的粒度是当前实例。
synchronized关键字作用于三个位置:
- 作用在静态方法上,则锁是当前类的Class对象。
 
- 作用在普通方法上,则锁是当前的实例(this)。
 
- 作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。
 
为什么StringBuffer效率低?
- 锁本身效率低:存在线程竞争时,加锁的代码块本身就比不加锁要慢很多,因为锁内的共享资源同一时刻只能一个线程访问。
 
- 锁粒度大:锁的粒度是当前实例,直接给整个方法加synchronized,而不是具体需要在有同步风险的代码块上加锁,性能低。这个方法内部分共享资源可能是线程安全的,并不需要加锁。
 
指挥者:StringBuilder
StringBuilder和StringBuffer自身既是具体建造者,也是指挥者。