(二)、创建者模式(5种)
创建者模式的主要关注点: “怎么创建对象”,它的主要特点是:“将对象的创建和使用分离
”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
1.单列模式
单列模式 是Java中最简单的设计模式之一。这种类型的设计属于创建型模式,他提供了一种创建对象的最佳方式。
这种模式会涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其他唯一的方式,可以直接访问,不需要实列化该类的对象。
(1).单列模式的结构
单列模式饿主要有以下角色:
- 单列模式。只能创建一个实列的类
- 访问类。使用单列类
(2).单列模式的实现
单列设计模式分类两种:
- 饿汉式:
类加载就会导致该单实列对象被创建
(加载的时候就创建) - 懒汉式: 类加载不会导致该单列对象被创建,而是
首次使用该对象时才会创建
。
(3).饿汉式的两种方式
- 饿汉式-方式一 (静态变量方式)
单列模式
package com.jsxs.pattern.singletion.demo1; /** * @Author Jsxs * @Date 2023/4/16 14:11 * @PackageName:com.jsxs.pattern.singletion.demo1 * @ClassName: Singletion * @Description: TODO 静态成员变量 * @Version 1.0 */ public class Singletion { // 1.私有构造方法: 外界不能创建这个对象也就是->new private Singletion() { } //2.在本类中创建该类的对象: private->外界不能直接访问。 static->设置为静态的变量,目的是为了让静态方法获取。 private static Singletion singletion=new Singletion(); //3.提供一个公共的访问方式,让外界获取该对象 public static Singletion getInstance(){ return singletion; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24
访问类
package com.jsxs.pattern.singletion.demo1; /** * @Author Jsxs * @Date 2023/4/16 14:21 * @PackageName:com.jsxs.pattern.singletion.demo1 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { //1.创建Singletion类的对象 Singletion instance = Singletion.getInstance(); Singletion instance1 = Singletion.getInstance(); // 2.判断创建得到这两个对象是否是一个对象 System.out.println(instance==instance1); // == 判断的时地址 } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
- 饿汉式-方式二 (静态代码块)
package com.jsxs.pattern.singletion.demo2; /** * @Author Jsxs * @Date 2023/4/16 14:30 * @PackageName:com.jsxs.pattern.singletion.demo2 * @ClassName: Singleton * @Description: TODO 饿汉式2- 静态代码块---- 静态变量和静态代码块谁先声明谁先执行。 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} //2.声明Singleton类型的变量 private static Singleton instance; // 并未初始化 //3.静态代码块中进行赋值 static { instance=new Singleton(); } //4.对外提供该类对象的方法 public static Singleton getInstance(){ return instance; } } • 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
package com.jsxs.pattern.singletion.demo2; /** * @Author Jsxs * @Date 2023/4/16 14:34 * @PackageName:com.jsxs.pattern.singletion.demo2 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance==instance1); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
说明: 该方式在成员位置声明Singleton类型的静态变量,而对象的创建时在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本一样,当然该方式也存在内存浪费的问题。
(4).懒汉式的七种方式
- 饿汉式-方式1 (静态方式-线程不安全)
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:43 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Singleton * @Description: TODO 懒汉式-1 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} // 2.声明Singleton类型的变量---*******并没有赋值的操作 private static Singleton instace; //3.对外提供访问方式 public static Singleton getInstance(){ if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。 instace=new Singleton(); } return instace; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:50 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { System.out.println( Singleton.getInstance().hashCode()); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19
我们发现在单线程的情况下: 哈希值是一样的也就是对象是一样的
问题: 多线程
的情况下: 会出现哈希值的不一致->对象不一致。
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:43 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Singleton * @Description: TODO 懒汉式-1 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} // 2.声明Singleton类型的变量---*******并没有赋值的操作 private static Singleton instace; //3.对外提供访问方式 public static Singleton getInstance(){ if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。 try { Thread.sleep(2000); //等待2秒 } catch (InterruptedException e) { e.printStackTrace(); } instace=new Singleton(); } return instace; } } • 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
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:50 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println( Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
- 懒汉式-2 (线程安全)
这里我们在创建对象的时候只是添加了 同步锁Synchronized
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:43 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Singleton * @Description: TODO 懒汉式-2 (线程安全) * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} // 2.声明Singleton类型的变量---*******并没有赋值的操作 private static Singleton instace; //3.对外提供访问方式 public static synchronized Singleton getInstance(){ // -**********添加了同步锁: "没有执行完毕,不释放资源" if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。 try { Thread.sleep(2000); //等待2秒 } catch (InterruptedException e) { e.printStackTrace(); } instace=new Singleton(); } return instace; } } • 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
package com.jsxs.pattern.singletion.demo3; /** * @Author Jsxs * @Date 2023/4/16 14:50 * @PackageName:com.jsxs.pattern.singletion.demo3 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println( Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
说明: 该方式实现了懒加载的效果,同时又解决了线程安全问题。但是在GetInstance()方法上添加Synchronized关键字,导致该方法的执行效率特别低。从上面的代码我们可以看出,其实就是在初始化inatance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
- 懒汉式-方式3 (双重检查锁)
再来讨论以下懒汉模式中加锁的问题,对于 getInstace()方法来说,绝大部分的操作时读的操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的模式: 双层检查锁模式
(提升效率)
类对象加锁
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:29 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Singleton * @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} //2.声明Singleton变量 private static Singleton instance; //3.对外提供公共的访问方式 public static Singleton getInstance(){ //1. 第一次判断 if (instance==null){ //2. 假如为空就进入,否则直接返回。 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //3.类对象加锁... synchronized (Singleton.class){ //4.第二次判断 if (instance==null){ instance=new Singleton(); } } } return instance; } } • 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
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:41 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println(Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
双重检查锁模式是一种非常好的单列实现模式,解决了单列、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实列化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来的空指针异常问题,只需要使用Volatile
关键字,Volatile
关键字可以保证可见性和有序性。
在变量上添加关键字Volatile
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:29 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Singleton * @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} //2.声明Singleton变量 private static volatile Singleton instance; //*******************在变量上添加关键字 //3.对外提供公共的访问方式 public static Singleton getInstance(){ //1. 第一次判断 if (instance==null){ //2. 假如为空就进入,否则直接返回。 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //3.类对象加锁... synchronized (Singleton.class){ //4.第二次判断 if (instance==null){ instance=new Singleton(); } } } return instance; } } • 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
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:41 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println(Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
小结: 添加Volatile
关键字之后双重检查锁模式是一种比较好的单列模式实现模式,能够保证在多线程的情况下安全且性能的问题。
- 懒汉式-方式4 (静态内部类方式)
静态内部类单列模式中实列由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的
,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实列化一次,并且严格保证实列化顺序。
package com.jsxs.pattern.singletion.demo5; /** * @Author Jsxs * @Date 2023/4/16 16:02 * @PackageName:com.jsxs.pattern.singletion.demo5 * @ClassName: Singleton * @Description: TODO 静态内部类的方式 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} // 2.定义一个静态内部类 private static class SingletonHolder{ //为了防止外部对其进行修改,我们添加一个 final 的关键字 //在内部类中声明并初始化外部类的对象 private final static Singleton instance=new Singleton(); } // 3.提供公共的访问方式 public static Singleton getInstance(){ return SingletonHolder.instance; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23
package com.jsxs.pattern.singletion.demo5; /** * @Author Jsxs * @Date 2023/4/16 16:10 * @PackageName:com.jsxs.pattern.singletion.demo5 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1==instance); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
这里不存在线程安全问题,因为JVM在加载外部类的同时,不会加载静态内部类,只要当静态内部类的属性或者方法被调用的时候才会加载静态内部类。因为是静态变量只能被实列化一次...
说明: 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance(),虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全
,也能保证 Singleton类的唯一性。
小结: 静态内部类单列模式是一种优秀的单列模式
,是开源项目中比较常用的一种单列模式。在没有任何加锁的情况下,保证了多线程的安全,并且没有任何影星性能影响和空间的浪费。
- 枚举方式
枚举类实现单列模式是极力推荐的单列模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用枚举的这个特性来实现单列模式,枚举的写法非常简单,而且枚举类型是所用单列实现中唯一一种不会被破坏的单列实现模式
。
package com.jsxs.pattern.singletion.demo6; /** * @Author Jsxs * @Date 2023/4/16 17:39 * @PackageName:com.jsxs.pattern.singletion.demo6 * @ClassName: Singleton * @Description: TODO 枚举实现方式 * @Version 1.0 */ public enum Singleton { INSTANCE; } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14
package com.jsxs.pattern.singletion.demo6; /** * @Author Jsxs * @Date 2023/4/16 17:40 * @PackageName:com.jsxs.pattern.singletion.demo6 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Singleton instance=Singleton.INSTANCE; Singleton instance1=Singleton.INSTANCE; System.out.println(instance1==instance); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
枚举方式属于饿汉式方式 -> 如果不考虑内存首选枚举
(5).存在问题-> 破坏单列模式2种
破坏单列模式:
使上面定义的单列类可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
。
- 序列化反序列化
设置一个静态内部类并且: 实现序列化
package com.jsxs.pattern.singletion.demo7; import java.io.Serializable; /** * @Author Jsxs * @Date 2023/4/16 17:47 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Singleton * @Description: TODO * @Version 1.0 */ public class Singleton implements Serializable { // 1.私有构造函数 private Singleton(){} // 2.静态内部类 private static class SingletonHard{ private static final Singleton instance=new Singleton(); } // 3.提供公共的对象 public static Singleton getInstance(){ return SingletonHard.instance; } } • 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
我们首先先写一个数据、然后读取两次
package com.jsxs.pattern.singletion.demo7; import java.io.*; /** * @Author Jsxs * @Date 2023/4/16 17:55 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws Exception { // writeObject2File(); ReadObjectFromFile(); ReadObjectFromFile(); } // 从文件读取数据(对象) public static void ReadObjectFromFile() throws Exception { //1. 创建对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt")); //2. 读取对象 Singleton singleton = (Singleton) ois.readObject(); System.out.println(singleton); //3. 释放资源 ois.close(); } // 向文件中写数据 (对象) public static void writeObject2File() throws Exception { //1. 获取Singleton对象 Singleton instance = Singleton.getInstance(); // 2.创建对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt")); // 3.写对象 oos.writeObject(instance); //4.释放资源 oos.close(); } } • 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
小结: 我们发现对象地址不一致-> 也就是破坏了单列模式。
- 反射 (破坏单列模式)
静态内部类
package com.jsxs.pattern.singletion.demo8; /** * @Author Jsxs * @Date 2023/4/16 18:22 * @PackageName:com.jsxs.pattern.singletion.demo8 * @ClassName: Singleton * @Description: TODO 反射破坏单列模式 * @Version 1.0 */ public class Singleton { // 1.定义私有化构造函数: 不让外部类创建对象 private Singleton(){} // 2.定义静态内部类 private static class SingletonHard{ private static final Singleton instance=new Singleton(); } // 3.开发端口 public static Singleton getInstance(){ return SingletonHard.instance; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24
客户端
package com.jsxs.pattern.singletion.demo8; import java.lang.reflect.Constructor; /** * @Author Jsxs * @Date 2023/4/16 18:25 * @PackageName:com.jsxs.pattern.singletion.demo8 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws Exception { // 1.获取字节码对象 Class clazz = Singleton.class; // 2.获取无参构造方法对象 Constructor cons = clazz.getDeclaredConstructor(); // 3.取消访问检查 cons.setAccessible(true); //4. 创建Singleton对象 Singleton o = (Singleton)cons.newInstance(); Singleton o2 = (Singleton)cons.newInstance(); System.out.println(o==o2); } } • 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
破坏了单列模式
(6).解决问题-> 解决单列模式2种
1. 序列化、反序列方式破坏单列模式的解决办法
在Singleton类中添加readResolve()
方法,在序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
这个名字是一个固定的变量名~。假如实体类被序列化了,那么会自动地实现这个代码。
// 4. 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回 public Object readResolve() { return SingletonHard.instance; } } • 1 • 2 • 3 • 4 • 5
序列化
package com.jsxs.pattern.singletion.demo7; import java.io.Serializable; /** * @Author Jsxs * @Date 2023/4/16 17:47 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Singleton * @Description: TODO * @Version 1.0 */ public class Singleton implements Serializable { // 1.私有构造函数 private Singleton() { } // 2.静态内部类 private static class SingletonHard { private static final Singleton instance = new Singleton(); } // 3.提供公共的对象 public static Singleton getInstance() { return SingletonHard.instance; } // 4. 当金星反序列化时,会自动调用该方法,将该方法的返回值直接返回 public Object readResolve() { return SingletonHard.instance; } } • 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
实现类
package com.jsxs.pattern.singletion.demo7; import java.io.*; /** * @Author Jsxs * @Date 2023/4/16 17:55 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws Exception { // writeObject2File(); ReadObjectFromFile(); ReadObjectFromFile(); } // 从文件读取数据(对象) public static void ReadObjectFromFile() throws Exception { //1. 创建对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt")); //2. 读取对象 Singleton singleton = (Singleton) ois.readObject(); System.out.println(singleton); //3. 释放资源 ois.close(); } // 向文件中写数据 (对象) public static void writeObject2File() throws Exception { //1. 获取Singleton对象 Singleton instance = Singleton.getInstance(); // 2.创建对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt")); // 3.写对象 oos.writeObject(instance); //4.释放资源 oos.close(); } } • 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
我们先进行写入数据、然后再读取数据~
- 反射方式破解单列地解决办法
private static boolean flag=false; // 1.定义私有化构造函数: 不让外部类创建对象 private Singleton(){ synchronized (Singleton.class){ // 判断flag地值是否是true,如果是true,说明非第一次访问,直接跑一个异常即可。如果是false的话,说明第一次创建对象 if (flag){ throw new RuntimeException("不能创建多个对象"); } // 将flag的值设置为true flag=true; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13
反射实现
package com.jsxs.pattern.singletion.demo8; /** * @Author Jsxs * @Date 2023/4/16 18:22 * @PackageName:com.jsxs.pattern.singletion.demo8 * @ClassName: Singleton * @Description: TODO 反射破坏单列模式 * @Version 1.0 */ public class Singleton { private static boolean flag=false; // 1.定义私有化构造函数: 不让外部类创建对象 private Singleton(){ synchronized (Singleton.class){ // 判断flag地值是否是true,如果是true,说明非第一次访问,直接跑一个异常即可。如果是false的话,说明第一次创建对象 if (flag){ throw new RuntimeException("不能创建多个对象"); } // 将flag的值设置为true flag=true; } } // 2.定义静态内部类 private static class SingletonHard{ private static final Singleton instance=new Singleton(); } // 3.开发端口 public static Singleton getInstance(){ return SingletonHard.instance; } } • 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
实现类
package com.jsxs.pattern.singletion.demo8; import java.lang.reflect.Constructor; /** * @Author Jsxs * @Date 2023/4/16 18:25 * @PackageName:com.jsxs.pattern.singletion.demo8 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws Exception { // 1.获取字节码对象 Class clazz = Singleton.class; // 2.获取无参构造方法对象 Constructor cons = clazz.getDeclaredConstructor(); // 3.取消访问检查 cons.setAccessible(true); //4. 创建Singleton对象 Singleton o = (Singleton)cons.newInstance(); Singleton o2 = (Singleton)cons.newInstance(); System.out.println(o==o2); } } • 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
(7).JDK源码解析-Runtime类
Runtime类就是i使用的单列设计模式的饿汉模式
package com.jsxs.pattern.singletion.demo9; import java.io.IOException; import java.io.InputStream; /** * @Author Jsxs * @Date 2023/4/16 21:12 * @PackageName:com.jsxs.pattern.singletion.demo9 * @ClassName: RuntomeDemo * @Description: TODO * @Version 1.0 */ public class RuntimeDemo { public static void main(String[] args) throws IOException { // 获取Runtime类的对象 Runtime runtime = Runtime.getRuntime(); // 调用runtime的方法exec Process exec = runtime.exec("ipconfig"); // 调用process对象的获取输入流的方法 InputStream inputStream = exec.getInputStream(); byte[] bytes = new byte[1024 * 1024]; int count=0; while ((count=inputStream.read())!=-1){ System.out.println(new String(bytes,0,count,"utf-8")); } System.out.println("你好"); } } • 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
2.原型模式
(1).概述
用一个创建的实列作为原型,通过复制该原型对象来创建一个和原型都相同的新对象。
(2).结构
原型模式包含如下角色:
- 抽象原型类: 规定了具体原型对象必须实现的Clone()方法。
JDK帮我们做了
。 - 具体原型类: 实现抽象原型类的cone()方法,它是可以被复制的对象
- 访问类: 使用具体原型类中的clone()方法来复制新的对象。
(3).实现
原型模式的克隆分为浅克隆和深克隆
浅克隆
: 浅克隆:对当前对象进行克隆,并克隆该对象所包含的8种基本数据类型和String类型属性(拷贝一份该对象并重新分配内存,即产生了新的对象);但如果被克隆的对象中包含除8中数据类型和String类型外的其他类型的属性,浅克隆并不会克隆这些属性(即不会为这些属性分配内存,而是引用原来对象中的属性,而不拷贝对象包含的引用指向的对象。)会指向原有对象的地址,内存地址一样
深克隆
: 深克隆:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。不再指向原有对象的地址。
Java中的Object类
中提供了 clone()
方法来实现浅克隆
。Cloneable接口是上面的类图中的抽象原型类,而实现了cloneable接口的子实现类就是具体的原型类。
具体原型类: 这里我们继承coneable接口重写clone()方法实现克隆
package com.jsxs.pattern.prototype.demo; /** * @Author Jsxs * @Date 2023/4/18 13:23 * @PackageName:com.jsxs.pattern.prototype.demo * @ClassName: Realizetype * @Description: TODO * @Version 1.0 */ public class Realizetype implements Cloneable{ public Realizetype(){ System.out.println("具体的原型对象被创建了"); } // 1.重写方法 @Override public Realizetype clone() throws CloneNotSupportedException { System.out.println("具体原型复制成功!!!"); return (Realizetype)super.clone(); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23
访问类
package com.jsxs.pattern.prototype.demo; /** * @Author Jsxs * @Date 2023/4/18 13:33 * @PackageName:com.jsxs.pattern.prototype.demo * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws CloneNotSupportedException { // 1.创建一个原型类对象 Realizetype realizetype = new Realizetype(); // ->利用无参的构造方法去创建对象 //2.调用Realizetype类中我们重写的clone()方法进行对象的克隆 Realizetype clone = realizetype.clone(); System.out.println("原型对象和克隆的对象是否是同一个对象 "+(realizetype==clone)); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
(4).案列
用原型模式生成 "三好学生"奖状
同一学校的"三号学生"奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个:"三好学生"奖状出来,然后再修改奖状上的名字即可。
具体原型类: 这里我们继承coneable接口重写clone()方法实现克隆
package com.jsxs.pattern.prototype.demo2; /** * @Author Jsxs * @Date 2023/4/18 13:52 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Citation * @Description: TODO * @Version 1.0 */ public class Citation implements Cloneable{ // 基本属性 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } // 重写clone方法 @Override public Citation clone() throws CloneNotSupportedException { return (Citation)super.clone(); } public void show(){ System.out.println(name+"获得奖状一张!!"); } } • 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
访问类
package com.jsxs.pattern.prototype.demo2; /** * @Author Jsxs * @Date 2023/4/18 13:54 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws CloneNotSupportedException { // 1.原型对象的创建 Citation citation = new Citation(); citation.setName("李明-原型对象"); // 2.克隆对象 Citation clone = citation.clone(); clone.setName("吉士-克隆对象"); citation.show(); clone.show(); System.out.println(citation==clone); } } • 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
(5).使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象
- 性能和安全要求比较高
(6).扩展(深克隆)
将上面的"三好学生"奖状的案列中的name属性更改为Student类型的属性。
浅克隆
引用对象类
package com.jsxs.pattern.prototype.demo2; /** * @Author Jsxs * @Date 2023/4/18 14:22 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Student * @Description: TODO * @Version 1.0 */ public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } • 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
具体原型类
package com.jsxs.pattern.prototype.demo2; /** * @Author Jsxs * @Date 2023/4/18 13:52 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Citation * @Description: TODO * @Version 1.0 */ public class Citation implements Cloneable{ // 基本属性 private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } // 重写clone方法 @Override public Citation clone() throws CloneNotSupportedException { return (Citation)super.clone(); } public void show(){ System.out.println(student.getName()+"获得奖状一张!!"); } } • 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
访问类
package com.jsxs.pattern.prototype.demo2; /** * @Author Jsxs * @Date 2023/4/18 13:54 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws CloneNotSupportedException { // 1.原型对象的创建 Citation citation = new Citation(); Student student = new Student(); student.setName("原型对象-张三"); citation.setStudent(student); // 2.克隆对象 Citation clone = citation.clone(); Student student1 = citation.getStudent(); student1.setName("克隆对象-李四"); clone.show(); citation.show(); System.out.println(student==student1); } } • 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
说明: student和student1的对象是一个对象,就会产生studen1对象的属性会覆盖student的属性的效果。对具体原型类中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程
Student引用类-> 序列化
package com.jsxs.pattern.prototype.demo3; import java.io.Serializable; /** * @Author Jsxs * @Date 2023/4/18 14:22 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Student * @Description: TODO * @Version 1.0 */ public class Student implements Serializable { //序列化 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } • 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
具体克隆类 -> 序列化
package com.jsxs.pattern.prototype.demo3; import java.io.Serializable; /** * @Author Jsxs * @Date 2023/4/18 13:52 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Citation * @Description: TODO * @Version 1.0 */ public class Citation implements Serializable { // 基本属性 private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public void show(){ System.out.println(student.getName()+"获得奖状一张!!"); } } • 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
访问类
package com.jsxs.pattern.prototype.demo3; import java.io.*; /** * @Author Jsxs * @Date 2023/4/18 13:54 * @PackageName:com.jsxs.pattern.prototype.demo2 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client{ public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException { // 1.原型对象的创建 Citation citation = new Citation(); Student student = new Student(); student.setName("原型对象-张三"); citation.setStudent(student); // 2.创建对象输出流对象 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt")); out.writeObject(citation); //将原型对象写入文件中 out.close(); //关闭资源 ObjectInputStream ipt = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt")); Citation citation1 = (Citation)ipt.readObject(); ipt.close(); //关闭资源 Student student1 = citation1.getStudent(); student.setName("克隆对象-李四"); citation.show(); citation1.show(); System.out.println(student==student1); } } • 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
3.工厂方法模式 (单点咖啡和甜点)
(1).概述
需求: 设计一个咖啡点餐系统。
设计1个咖啡类,并定义两个子类(美式咖啡)和拿铁咖啡;再设计一个咖啡店类,咖啡店具有点咖啡的功能。
- 反列
咖啡类
package com.jsxs.pattern.factory.before; /** * @Author Jsxs * @Date 2023/4/17 12:47 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: Coffe * @Description: TODO 咖啡类 * @Version 1.0 */ public abstract class Coffee { // 0. 子类必须要实现抽象方法 public abstract String getName(); // 1.加糖 public void addSuger() { System.out.println("加糖"); } // 2.加奶 public void addMilk() { System.out.println("加奶"); } } • 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
咖啡类型
package com.jsxs.pattern.factory.before; /** * @Author Jsxs * @Date 2023/4/17 12:51 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: LatteCoffe * @Description: TODO 拿铁咖啡 * @Version 1.0 */ public class LatteCoffee extends Coffee{ @Override public String getName() { return "拿铁咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.before; /** * @Author Jsxs * @Date 2023/4/17 12:50 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: AmericanCoffee * @Description: TODO 美式咖啡 * @Version 1.0 */ public class AmericanCoffee extends Coffee{ @Override public String getName() { return "美式咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
咖啡商店
package com.jsxs.pattern.factory.before; /** * @Author Jsxs * @Date 2023/4/17 12:52 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: CoffeeStore * @Description: TODO 咖啡店 * @Version 1.0 */ public class CoffeeStore { public Coffee orderCoffee(String type){ // /声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象 Coffee coffee=null; if ("american".equals(type)){ coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡 }else if ("latte".equals(type)){ coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡 }else { throw new RuntimeException("对不起,你点的咖啡没有"); } // 添加配料 coffee.addMilk(); coffee.addSuger(); return coffee; } } • 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
package com.jsxs.pattern.factory.before; /** * @Author Jsxs * @Date 2023/4/17 12:57 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { CoffeeStore coffeeStore = new CoffeeStore(); Coffee coffee = coffeeStore.orderCoffee("american"); System.out.println(coffee.getName()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18
假如需要新增产品,那么我们就迫不得已需要修改数据
在Java种,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重
。假如我们要更换对象,所有new对象的地方需要修改一遍,这显然违背了软件设计的开闭原则。如果使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦。如果要更换对象,直接在工厂里更换该对象即可,达到了对象解耦的目的,所以说: “工厂模式最大的优点是: 解耦”。
(2).简单工厂模式
简单工厂模式不是一种设计模式,反而更像一种编程习惯。
- 结构
简单工厂包含以下的角色
- 抽象产品: 定义了产品的规范、
描述了产品的主要特性和功能
。 - 具体产品:
实现或者继承
抽象产品的子类 - 具体工厂: 提供了创建产品的方法,调用者通过方法来获取产品。
抽象产品
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 13:54 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: Coffee * @Description: TODO * @Version 1.0 */ public abstract class Coffee { // 0. 子类必须要实现抽象方法 public abstract String getName(); // 1.加糖 public void addSuger() { System.out.println("加糖"); } // 2.加奶 public void addMilk() { System.out.println("加奶"); } } • 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
具体产品
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 12:51 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: LatteCoffe * @Description: TODO 拿铁咖啡 * @Version 1.0 */ public class LatteCoffee extends Coffee { @Override public String getName() { return "拿铁咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 12:50 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: AmericanCoffee * @Description: TODO 美式咖啡 * @Version 1.0 */ public class AmericanCoffee extends Coffee { @Override public String getName() { return "美式咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
简单工厂
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 13:56 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: SimleCoffeeFactory * @Description: TODO 简单工厂 * @Version 1.0 */ public class SimpleCoffeeFactory { public Coffee createCoffee(String type){ Coffee coffee=null; if ("american".equals(type)){ coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡 }else if ("latte".equals(type)){ coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡 }else { throw new RuntimeException("对不起,你点的咖啡没有"); } return coffee; } } • 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
咖啡店
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 13:55 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: CoffeeStore * @Description: TODO * @Version 1.0 */ public class CoffeeStore { // 1.创建点餐业务 public Coffee orderCoffee(String type) { // 2.具体商品我们去工厂获取 SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory(); Coffee coffee = simpleCoffeeFactory.createCoffee(type); // 3.加配料 coffee.addMilk(); coffee.addSuger(); return coffee; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24
客户端
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 14:02 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { CoffeeStore coffeeStore = new CoffeeStore(); Coffee coffee = coffeeStore.orderCoffee("american"); System.out.println(coffee.getName()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18
简单工厂的优缺点
工厂处理创建对象的细节,一旦有了简单工厂类
、咖啡店类
中的订单方法
就变成此对象的客户,后期如果需要咖啡对象直接从该工厂种获取即可,这样也就接触了和Coffee实现类的耦合;同时又产生了新的耦合,咖啡店对象
和简单工厂类对象
的耦合,工厂对象
和商品对象
的耦合。
后期如果再加新产品的咖啡,我们势必需要简单工厂类的代码
,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只要修改该工厂类的代码,省去其他的修改操作。
优点: 封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开
,这样以后就避免了修改客户端代码,如果要实现新产品直接修改工厂类,而不要在源代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展
缺点:增加新产品的时候,依然需要修改工厂类,违反了开闭原则
- 简单工厂的扩展
静态工厂:
在开发过程中: 一部分人将工厂类创建对象的功能定义为静态的,这个就是静态工厂模式,也不是23种设计模式之一。
将这个方法更改成: 静态方法
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 13:56 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: SimleCoffeeFactory * @Description: TODO 简单工厂 * @Version 1.0 */ public class SimpleCoffeeFactory { public static Coffee createCoffee(String type){ Coffee coffee=null; if ("american".equals(type)){ coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡 }else if ("latte".equals(type)){ coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡 }else { throw new RuntimeException("对不起,你点的咖啡没有"); } return coffee; } } • 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
利用类名去创建对象
package com.jsxs.pattern.factory.Single_Factory; /** * @Author Jsxs * @Date 2023/4/17 13:55 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: CoffeeStore * @Description: TODO * @Version 1.0 */ public class CoffeeStore { // 1.创建点餐业务 public Coffee orderCoffee(String type) { // 2.具体商品我们去工厂获取 Coffee coffee = SimpleCoffeeFactory.createCoffee(type); // 3.加配料 coffee.addMilk(); coffee.addSuger(); return coffee; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23
(3).工厂方法模式
针对上面的优缺点: 使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
定义一个用于创建对象的接口,让子类决定实列化哪个产品对象。工厂方法使一个产品类的实列化延迟到了其他工厂的子类。
- 结构
工厂方法模式的主要角色
- 抽象工厂:
提供一个创建产品的接口
,调用者通过它访问具体工厂的工厂方法来创建产品。 - 具体工厂: 主要是实现抽象工厂的抽象方法,完成具体产品的创建
- 抽象产品:
定义了产品的规范
,描述了产品的主要特性和功能。 - 具体产品:
实现了产品所定义的接口
,由具体的工厂来创建,它同具体工厂之间一一对应。
- 实现
抽象产品
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 13:54 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: Coffee * @Description: TODO * @Version 1.0 */ public abstract class Coffee { // 0. 子类必须要实现抽象方法 public abstract String getName(); // 1.加糖 public void addSuger() { System.out.println("加糖"); } // 2.加奶 public void addMilk() { System.out.println("加奶"); } } • 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
具体产品
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs`在这里插入代码片` * @Date 2023/4/17 12:51 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: LatteCoffe * @Description: TODO 拿铁咖啡 * @Version 1.0 */ public class LatteCoffee extends Coffee { @Override public String getName() { return "拿铁咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 12:50 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: AmericanCoffee * @Description: TODO 美式咖啡 * @Version 1.0 */ public class AmericanCoffee extends Coffee { @Override public String getName() { return "美式咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
抽象工厂
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 14:52 * @PackageName:com.jsxs.pattern.factory.factory_method * @ClassName: CoffeeFactory * @Description: TODO 工厂接口 * @Version 1.0 */ public interface CoffeeFactory { Coffee createCoffee(); } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14
具体工厂
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 14:53 * @PackageName:com.jsxs.pattern.factory.factory_method * @ClassName: AmericanCoffeeFactoryImp * @Description: TODO 实现美式咖啡 * @Version 1.0 */ public class AmericanCoffeeFactoryImp implements CoffeeFactory{ @Override public Coffee createCoffee() { return new AmericanCoffee(); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 14:54 * @PackageName:com.jsxs.pattern.factory.factory_method * @ClassName: LatteCoffeeFactoryImp * @Description: TODO 拿铁咖啡实现 * @Version 1.0 */ public class LatteCoffeeFactoryImp implements CoffeeFactory{ @Override public Coffee createCoffee() { return new LatteCoffee(); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
咖啡商店
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 14:50 * @PackageName:com.jsxs.pattern.factory.factory_method * @ClassName: CoffeeStore * @Description: TODO 咖啡店 * @Version 1.0 */ public class CoffeeStore { private CoffeeFactory coffeeFactory; public void setCoffeeFactory(CoffeeFactory coffeeFactory) { this.coffeeFactory = coffeeFactory; } // 点咖啡的功能 public Coffee orderCoffee() { Coffee coffee = coffeeFactory.createCoffee(); coffee.addMilk(); coffee.addSuger(); return coffee; } } • 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
测试类
package com.jsxs.pattern.factory.factory_method; /** * @Author Jsxs * @Date 2023/4/17 14:57 * @PackageName:com.jsxs.pattern.factory.factory_method * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { CoffeeStore coffeeStore = new CoffeeStore(); // 点一杯美式咖啡 coffeeStore.setCoffeeFactory(new AmericanCoffeeFactoryImp()); // 订餐... Coffee coffee = coffeeStore.orderCoffee(); System.out.println(coffee.getName()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21
- 优缺点
优点:
- 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程。
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂,无需对原工厂进行任何修改,满足开闭原则。
缺点:
- 每增加一个产品就需要
增加一个具体产品类和一个对应的具体工厂
,增加了系统的复杂性。
4.抽象工厂模式 (咖啡-套餐)
前面介绍的工厂方法模式考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视。
这些工厂只生产同种类产品,同种类产品称为同等级产品.也就是说:“工厂方法模式只考虑生产同等级的产品,凡是现实生活中许多工厂是综合性的工厂,能生产多等级(种类)的产品,如电器厂及生产电视又生产洗衣机或空调,大学既有软件专业也有生物专业等。”
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族。下图所示X轴是产品等级,也就是同一类产品(华为手机 苹果手机)。Y轴是产品族,也就是同一品牌的不同产品(华为手机 华为电脑),同一品牌的产品产自同一个工厂。
(1).概述
抽象工厂模式:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所需产品的具体类
就能得到同族的不同等级的产品模式结构
。
抽象工厂模式是 工厂方法模式的升级版本,工厂方法模式只生产同一个等级的产品,而抽象工厂模式可以生产多个等级的产品。
(2).结构
抽象工厂模式的主要角色:
- 抽象工厂:
提供一个创建产品的接口
,它包含了多个创建产品的方法,可以创建多个不同等级的产品 - 具体工厂: 主要是
实现抽象工厂中的多个抽象方法
,完成具体产品的创建 - 抽象产品:
定义了产品的规范
,描述了产品的主要特性和功能。 - 具体产品:
实现了产品所定义的接口
,由具体的工厂来创建,它同具体工厂之间一一对应。
(3).实现
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯
等,钥匙按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸。其中拿铁咖啡、没事咖啡是一个产品等级
,都是咖啡;提拉米苏、抹茶慕斯
是一个产品等级;拿铁咖啡和提拉米苏
是同一产族(都是意大利风格),美式咖啡和抹茶慕斯
是同一产族(美式风格)。
抽象咖啡
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 12:47 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: Coffe * @Description: TODO 咖啡类 * @Version 1.0 */ public abstract class Coffee { // 0. 子类必须要实现抽象方法 public abstract String getName(); // 1.加糖 public void addSuger() { System.out.println("加糖"); } // 2.加奶 public void addMilk() { System.out.println("加奶"); } } • 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
抽象甜品
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:20 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: Dessert * @Description: TODO 甜品-抽象类 * @Version 1.0 */ public abstract class Dessert { public abstract void show(); } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15
具体咖啡
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 12:51 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: LatteCoffe * @Description: TODO 拿铁咖啡 * @Version 1.0 */ public class LatteCoffee extends Coffee { @Override public String getName() { return "拿铁咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 12:50 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: AmericanCoffee * @Description: TODO 美式咖啡 * @Version 1.0 */ public class AmericanCoffee extends Coffee { @Override public String getName() { return "美式咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
具体甜品
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:21 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: Trmisu * @Description: TODO 提拉米苏类 * @Version 1.0 */ public class Tiramisu extends Dessert{ @Override public void show() { System.out.println("提拉米苏-意大利风格"); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:23 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: MatchaMousee * @Description: TODO 抹茶慕斯类 * @Version 1.0 */ public class MatchaMousee extends Dessert{ @Override public void show() { System.out.println("抹茶慕斯-美式风格"); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
抽象大工厂
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:24 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: DessertFactory * @Description: TODO * @Version 1.0 */ public interface DessertFactory { // 1.生产咖啡 Coffee createCoffee(); // 2.生产甜品 Dessert createDessert(); } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
美式套餐
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:25 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: AmericanFactoryImp * @Description: TODO 美式风格工厂 * @Version 1.0 */ public class AmericanFactoryImp implements DessertFactory{ @Override public Coffee createCoffee() { return new AmericanCoffee(); // 美式咖啡 } @Override public Dessert createDessert() { return new MatchaMousee(); //抹茶慕斯 } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22
意大利套餐
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:27 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: LatteFactoryImp * @Description: TODO * @Version 1.0 */ public class LatteFactoryImp implements DessertFactory{ @Override public Coffee createCoffee() { return new LatteCoffee(); } @Override public Dessert createDessert() { return new Tiramisu(); //提拉米苏 } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22
咖啡店
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:28 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: CofeStore * @Description: TODO * @Version 1.0 */ public class CoffeeStore { private DessertFactory dessertFactory; public void setDessertFactory(DessertFactory dessertFactory) { this.dessertFactory = dessertFactory; } public void Order(){ Coffee coffee = dessertFactory.createCoffee(); Dessert dessert = dessertFactory.createDessert(); System.out.println(coffee.getName()); coffee.addMilk(); coffee.addSuger(); dessert.show(); } } • 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
客户
package com.jsxs.pattern.factory.abstract_factory; /** * @Author Jsxs * @Date 2023/4/17 16:28 * @PackageName:com.jsxs.pattern.factory.abstract_factory * @ClassName: CofeStore * @Description: TODO * @Version 1.0 */ public class CoffeeStore { private DessertFactory dessertFactory; public void setDessertFactory(DessertFactory dessertFactory) { this.dessertFactory = dessertFactory; } public void Order(){ Coffee coffee = dessertFactory.createCoffee(); Dessert dessert = dessertFactory.createDessert(); System.out.println(coffee.getName()); coffee.addMilk(); coffee.addSuger(); dessert.show(); } } • 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
如果我们需要再添加套餐(产品族)的话,只需要再加一个对应的工厂类即可,不需要再修改其他的类。
(4).优缺点
优点:
当一个产品族(套餐)中的多个对象被设计成一起工作的时候,它能保证客户端始终只使用同一个产品族中的对象 (套餐的对象必定一定)。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改
。eg:美式套餐中我们赠送一个意大利风格的甜品。(不能赠送或新增单个产品,可以新增一整个套餐)
(5).使用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视、洗衣机、空调等。
- 系统中有多个产品族。但每次只使用其中的某一族产品。如有人只喜欢穿一个指定的套餐(固定的产品族)
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实列的创建细节和内部结构。如: 输入法换皮肤,一整套一整套的换。生成不同的操作系统程序。
(6).JDK源码解析- Collection.iterator方法
使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。
5.工厂模式的扩展💔 -(这个不属于23种)
简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件
的方式解除工厂对象和产品对象
的耦合。在工厂中加载配置文件中的全类名,并创建对象和存储,客户端如果需要对象对象,直接进行获取即可。
beans.properties: 配置文件
american=com.jsxs.pattern.factory.config_factory.AmericanCoffee latte=com.jsxs.pattern.factory.config_factory.LatteCoffee • 1 • 2
咖啡抽象类
package com.jsxs.pattern.factory.config_factory; /** * @Author Jsxs * @Date 2023/4/17 13:54 * @PackageName:com.jsxs.pattern.factory.Single_Factory * @ClassName: Coffee * @Description: TODO * @Version 1.0 */ public abstract class Coffee { // 0. 子类必须要实现抽象方法 public abstract String getName(); // 1.加糖 public void addSuger() { System.out.println("加糖"); } // 2.加奶 public void addMilk() { System.out.println("加奶"); } } • 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
具体实现
package com.jsxs.pattern.factory.config_factory; /** * @Author Jsxs * @Date 2023/4/17 12:50 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: AmericanCoffee * @Description: TODO 美式咖啡 * @Version 1.0 */ public class AmericanCoffee extends Coffee { @Override public String getName() { return "美式咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
package com.jsxs.pattern.factory.config_factory; /** * @Author Jsxs * @Date 2023/4/17 12:51 * @PackageName:com.jsxs.pattern.factory.before * @ClassName: LatteCoffe * @Description: TODO 拿铁咖啡 * @Version 1.0 */ public class LatteCoffee extends Coffee { @Override public String getName() { return "拿铁咖啡"; } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
咖啡工厂类
package com.jsxs.pattern.factory.config_factory; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Properties; import java.util.Set; /** * @Author Jsxs * @Date 2023/4/17 17:50 * @PackageName:com.jsxs.pattern.factory.config_factory * @ClassName: CoffeeFactory * @Description: TODO 咖啡工厂类 * @Version 1.0 */ public class CoffeeFactory { // 1. 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储 //2.定义容器对象存储咖啡对象 private static HashMap<String,Coffee> map=new HashMap<String,Coffee>(); //3.加载配置文件,只需要加载一次 static { //3.1创建Properties对象 Properties properties = new Properties(); //3.2调用load方法进行加载 InputStream in = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //获取输入流、读取哪个文件 try { properties.load(in); //这个参数是: 输入流。加载完毕之后,这个properties就已经存在数据了。 //3.3从properties集合中获取全类名创建对象 Set<Object> keys = properties.keySet(); // 获取配置文件中全部的键 for (Object key : keys) { String ClassName = properties.getProperty((String) key); // 通过获取到配置文件中的键,我们得到值。 //3.4 通过反射去创建对象 Class aClass = Class.forName(ClassName); // 通过加载类全民获取字节码对象 Coffee coffee = (Coffee) aClass.newInstance(); //通过无参构造创建对象 // 将名称和对象那个存储到我们自定义的容器中 map.put((String) key,coffee); } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } // 根据名称获取对象 public static Coffee createCoffee(String type){ return map.get(type); } } • 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
package com.jsxs.pattern.factory.config_factory; /** * @Author Jsxs * @Date 2023/4/17 18:09 * @PackageName:com.jsxs.pattern.factory.config_factory * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Coffee coffee = CoffeeFactory.createCoffee("latte"); System.out.println(coffee.getName()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17
静态成员变量用来存储创建的对象(键存储的是名称、值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需执行一次
。
6.建造者模式
(1).概述
将一个复杂对象的构建
与便是分离
,使得同样的构建过程可以创建不同的表示。
将主机拆开就是分离,将分离的主机合起来就是构建。在一样的构建方法中我们更换掉不同品牌的相同组件就是创建了不同的主机
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构建出复杂的对象。这个模式适用于:
某个对象的构建过程复杂的情况
。 - 由于实现了构建和装配的解耦。
不同的构建器,相同的装配,也可以做出不同的对象
;相同的构建器,不同的装配顺序也可以做出不同的对象
。也就是实现了构建算法。装配算法的解耦,实现了更好的复用。 建造者模式可以将部件和其组装过程分开,一步步创建一个复杂的对象
。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
(2).结构
建造者(Builder)模式包含如下角色
- 抽象建造者类(Builder): 这个接口要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
- 具体建造者类(ConCreteBuilder): 实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实列。
- 产品类(Product): 要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在知道者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
(3).实列
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里的Bike是产品,包含车架,车座等组件;Builder是抽象建造者,摩拜单车公司和ofo公司是具体的创建者;Direct是指挥者。
protected:访问权限->本类以及本类的子类。创建对象的i两种方式->有参创造和无参创造。无参创造会回溯到祖宗类Object类,所以不管用哪种方式->我们尽量带上无参构造
- 指挥者和抽象类分离
产品
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:40 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Bike * @Description: TODO 具体的产品对象 * @Version 1.0 */ public class Bike { private String frame; //车架 private String seat; //车座 public String getFrame() { return frame; } public void setFrame(String frame) { this.frame = frame; } public String getSeat() { return seat; } public void setSeat(String seat) { this.seat = seat; } } • 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
建造者接口
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:42 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Builder * @Description: TODO * @Version 1.0 */ public abstract class Builder { // 1.声明Bike类型的变量 protected Bike bike=new Bike(); // 设计图或者概念-还没组装 public abstract void buildFame(); //车架 public abstract void buildSeat(); //车座 public abstract Bike createBike(); //构建具体自行车的方法 } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23
具体建造者
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:50 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: MobileCompany * @Description: TODO 摩拜公司 * @Version 1.0 */ public class MobileCompany extends Builder{ @Override public void buildFame() { bike.setFrame("摩拜-车架"); } @Override public void buildSeat() { bike.setFrame("摩拜-车座"); } @Override public Bike createBike() { return bike; } } • 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
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:52 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: OfoCompany * @Description: TODO * @Version 1.0 */ public class OfoCompany extends Builder{ @Override public void buildFame() { bike.setFrame("off-车架"); } @Override public void buildSeat() { bike.setSeat("off-车座"); } @Override public Bike createBike() { return bike; } } • 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
指挥者
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:55 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Director * @Description: TODO 总工程师-指挥者 * @Version 1.0 */ public class Director { // 声明Builder类型的变量 private Builder builder; // 声明有参构造创建对象 public Director(Builder builder) { this.builder = builder; } // 组装自行车的功能 public Bike construct(){ //构造自行车 this.builder.buildFame(); this.builder.buildSeat(); return builder.createBike(); } } • 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
客户端
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 16:32 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Director director = new Director(new MobileCompany()); // 指挥者组装对象 Bike bike = director.construct(); System.out.println(bike.getFrame()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20
指挥者类Director在建造者模式中很重要,它用于知道具体构建这如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有一些情况需要简化系统结构,可以把指挥者类和抽象类建造者进行结合
- 抽象类和建造者不分离
自行车产品
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:40 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Bike * @Description: TODO 具体的产品对象 * @Version 1.0 */ public class Bike { private String frame; //车架 private String seat; //车座 public String getFrame() { return frame; } public void setFrame(String frame) { this.frame = frame; } public String getSeat() { return seat; } public void setSeat(String seat) { this.seat = seat; } } • 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
抽象创建者--- 结合
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:42 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Builder * @Description: TODO * @Version 1.0 */ public abstract class Builder { // 1.声明Bike类型的变量 protected Bike bike=new Bike(); // 设计图或者概念-还没组装 public abstract void buildFame(); //车架 public abstract void buildSeat(); //车座 public abstract Bike createBike(); //构建具体自行车的方法 // 组装自行车的功能 public Bike construct(){ //构造自行车 this.buildFame(); this.buildSeat(); return this.createBike(); } } • 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
具体建造者
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:50 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: MobileCompany * @Description: TODO 摩拜公司 * @Version 1.0 */ public class MobileCompany extends Builder{ @Override public void buildFame() { bike.setFrame("摩拜-车架"); } @Override public void buildSeat() { bike.setFrame("摩拜-车座"); } @Override public Bike createBike() { return bike; } @Override public Bike construct() { return super.construct(); } } • 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
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 15:52 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: OfoCompany * @Description: TODO * @Version 1.0 */ public class OfoCompany extends Builder{ @Override public void buildFame() { bike.setFrame("off-车架"); } @Override public void buildSeat() { bike.setSeat("off-车座"); } @Override public Bike createBike() { return bike; } @Override public Bike construct() { return super.construct(); } } • 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
客户端
package com.jsxs.pattern.build.demo1; /** * @Author Jsxs * @Date 2023/4/18 16:32 * @PackageName:com.jsxs.pattern.build.demo1 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { MobileCompany mobileCompany = new MobileCompany(); Bike bike = mobileCompany.construct(); System.out.println(bike.getFrame()); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18
说明: 这样做确实简化了系统结构,让生产者来构建自行车,但同时也加重了抽象建造者类的职责,也不是太符合单一职责的原则。如果构建自行车contrstuct()这个方法过于复杂,建议还是封装到指挥者中。
(4).优缺点
优点:
建造者模式的封装性很好
。使用建造者模式可以有效地封装变化,在使用建造者模式的场景中,一般产品类和建造这类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。- 在建造者模式中,客户端不必知道产品内部组成的细节,
将产品与产品的创建过程解耦
,使得相同的创建过程可以创建不同的产品对象。 可以更加精密地控制产品地创建过程
。将复杂产品地创建步骤分解在不同地方法中,使得创建过程更加清晰,也是更方便使用程序来控制创建过程。- 建造者模式很容易进行扩展,通过一个新的建造者类就可以完成,基本上不用修改之前已经测试通过地代码,因此也就不会对原有功能引入风险,符合开闭原则。
缺点:
建造者模式所创建地产品一般具有较多地共同点,其组成部分很相似,如果产品之间地差异性很大,则不适用建造者模式
,因此其实用范围收到一定限制。
(5).使用场景
建造者模式创建地是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在一下场合使用:
- 创建的对象比较复杂,有多个部件构成,
各部件面临着复杂的变化,但构件间的建造顺序是稳定的
。 - 常见复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
(6).模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实列,代码可读性会非常差,而且很容易引入错误
,此时就应该利用建造者模式进行重构。
package com.jsxs.pattern.build.demo2; /** * @Author Jsxs * @Date 2023/4/18 17:18 * @PackageName:com.jsxs.pattern.build.demo2 * @ClassName: Phone * @Description: TODO * @Version 1.0 */ public class Phone { private String cpu; private String Screen; private String money; private String mainBoard; // 私有构造方法 private Phone(Builder builder) { this.cpu = builder.cpu; this.Screen = builder.Screen; this.money = builder.money; this.mainBoard = builder.mainBoard; } // 静态内部类 public static final class Builder{ private String cpu; private String Screen; private String money; 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 money(String money) { this.money = money; return this; } public Builder mainBoard(String mainBoard) { this.mainBoard = mainBoard; return this; } public Phone build(){ return new Phone(this); // 传递这个内部类 } @Override public String toString() { return "Builder{" + "cpu='" + cpu + '\'' + ", Screen='" + Screen + '\'' + ", money='" + money + '\'' + ", mainBoard='" + mainBoard + '\'' + '}'; } } @Override public String toString() { return "Phone{" + "cpu='" + cpu + '\'' + ", Screen='" + Screen + '\'' + ", money='" + money + '\'' + ", mainBoard='" + mainBoard + '\'' + '}'; } } • 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 • 75 • 76 • 77
package com.jsxs.pattern.build.demo2; /** * @Author Jsxs * @Date 2023/4/18 17:26 * @PackageName:com.jsxs.pattern.build.demo2 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { // 利用建造者创建对象 ---- 因为这些方法的返回值对象都是当前对象,所以可以使用链式编程 Phone.Builder builder = new Phone.Builder() .cpu("inter") .screen("三星") .mainBoard("苹果") .money("1200"); Phone phone = builder.build(); System.out.println(phone); } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23
重构后的代码在使用起来更方便,某种程度上可以提高开发效率。从软件设计,对程序员的要求比较高。
7.创建者模式对比
(1).工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式(结果)
;而建造者模式注重的是部件构建的过程(过程)
,意在通过一步步地精确构造出一个复杂地对象。
我们举个简单的列子来说明两者地差异: 如要制造一个超人,如果使用工厂方法模式,直接生产出来地就是一个力大无穷、能够飞翔、内裤外穿地超人;而如果使用建造者模式,则需要组装收、头、脚、驱赶,然后再把内裤外穿,于是一个超人就诞生了。
(2).抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族地创建
,一个产品家族是这样地一系列产品;具有不同分类欸都地产品组合,采用抽象工厂模式是不需要关心构建过程,只关心什么产品由什么工厂生产
即可。
建造者模式则是要求按照指定地蓝图建造产品,他的主要目的是通过组件装零配件而生产地一个新产品
。
如果将抽象工厂模式看成汽车配件生产工厂
,生产一个产品族地产品.那么建造者模式就是一个汽车组装工厂
,通过对部件地组装可以返回一量完整的汽车。