Java设计模式(二)创建型设计模式
文章目录
- 三 创建型设计模式
- 3.1 单例设计模式
- 3.1.1 饿汉式(线程安全)
- 3.1.2 懒汉式(线程不安全)
- 3.1.3 优缺点
- 3.1.4 补充
- 3.1.5 框架中的使用
- 3.1.4.1 Spring中的使用
- 3.1.4.2 JDK中的单例模式
- 3.2 原型设计模式
- 3.2.1 未使用设计模式的飞机大战
- 3.2.2 采用设计模式的飞机大战
- 3.2.3 深拷贝与浅拷贝
- 3.2.4 原型的结构模式
- 3.2.5 框架中的应用
- 3.2.5.1 ArrayList中的使用
- 3.2.5.2 Spring中的使用
- 3.3 工厂方法设计模式
- 3.3.1 未使用工厂模式的坦克大战
- 3.3.2 使用工厂模式的坦克大战
- 3.3.3 结构模式
- 3.3.4 框架中的应用
- 3.3.4.1 JDK中的工厂模式
- 3.4 抽象工厂设计模式
- 3.4.1 代码实现
- 3.4.2 抽象工厂模式结构
- 3.4.3 框架中的使用
- 3.4.3.1 Spring中的使用
- 3.4.4 工厂方法与抽象工厂方法的区别
- 3.5 建造者设计模式
- 3.5.1 代码实现
- 3.5.2 建造者模式结构
- 3.5.3 框架中的使用
- 3.5.3.1 StringBuilder类中的使用
- 3.5.3.2 Mybatis中的使用
三 创建型设计模式
3.1 单例设计模式
单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。singleton一词在逻辑学中指“有且仅有一个元素的集合”,这非常恰当地概括了单例的概念,也就是“一个类仅有一个实例”。
3.1.1 饿汉式(线程安全)
package com.shu;/*** @description: 太阳类,世界上只有一个太阳,既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。* @author: shu* @createDate: 2022/9/8 16:06* @version: 1.0*/
public class Sun {// static关键字确保太阳的静态性private static final Sun sun=new Sun();// 调用该方法进行类的初始化private static Sun getInstance(){return sun;}private Sun(){}
}
- private关键字确保太阳实例的私有性、不可见性和不可访问性,而static关键字确保太阳的静态性,将太阳放入内存里的静态区,在类加载的时候就初始化了。
- 它与类同在,也就是说它是与类同时期且早于内存堆中的对象实例化的,该实例在内存中永生,内存垃圾收集器(Garbage Collector,GC)也不会对其进行回收。
- final关键字则确保这个太阳是常量、恒量,它是一颗终极的恒星,引用一旦被赋值就不能再修改,最后,new关键字初始化太阳类的静态实例,并赋予静态常量sun。
- 这就是饿汉模式(eager initialization),即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。
3.1.2 懒汉式(线程不安全)
package com.shu;/*** @description: 太阳类,世界上只有一个太阳,既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。* @author: shu* @createDate: 2022/9/8 16:06* @version: 1.0*/
public class Sun {// static关键字确保太阳的静态性private static Sun sun;// 调用该方法进行类的初始化,private static Sun getInstance(){if(sun==null){sun=new Sun();}return sun;}
}
- 只有在某线程第一次调用getInstance()方法时才会运行对太阳进行实例化的逻辑代码,之后再请求就直接返回此实例了。
- 这样的好处是如无请求就不实例化,节省了内存空间,而坏处是第一次请求的时候速度较之前的饿汉初始化模式慢,因为要消耗CPU资源去临时造这个太阳(即使速度快到可以忽略不计)。
并发缺点
在多线程模式下是有缺陷的。试想如果是并发请求的话,判空逻辑就会同时成立,这样就会多次实例化太阳,并且对sun进行多次赋值(覆盖)操作,这违背了单例的理念。(加锁synchronized)
加锁一:将所有的等待者拒之门外,性能浪费
package com.shu;/*** @description: 太阳类,世界上只有一个太阳,既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。* @author: shu* @createDate: 2022/9/8 16:06* @version: 1.0*/
public class Sun {// static关键字确保太阳的静态性private static Sun sun;// 调用该方法进行类的初始化,private static synchronized Sun getInstance(){if(sun==null){sun=new Sun();}return sun;}
}
加锁二:只对关键步骤进行上锁操作,优化性能
package com.shu;/*** @description: 太阳类,世界上只有一个太阳,既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。* @author: shu* @createDate: 2022/9/8 16:06* @version: 1.0*/
public class Sun {// static关键字确保太阳的静态性private volatile static Sun sun;// 调用该方法进行类的初始化,private static Sun getInstance(){if(sun==null){synchronized (Sun.class) {sun = new Sun();}}return sun;}
}
- 关键字volatile对静态变量的修饰则能保证变量值在各线程访问时的同步性、唯一性。
- getInstance()方法,我们去掉了方法上的关键字synchronized,使大家都可以同时进入方法并对其进行开发。
- 这就是懒加载模式的双检锁:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。
3.1.3 优缺点
优点
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点
- 违反了_单一职责原则_。 该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
- 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。
3.1.4 补充
静态内部内使用
public class Singleton_00 {public static Map<String,String> cache = new ConcurrentHashMap<String, String>();}
CAS锁
public class Singleton_06 {private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();private Singleton_06() {}public static final Singleton_06 getInstance() {for (; ; ) {Singleton_06 instance = INSTANCE.get();if (null != instance) return instance;INSTANCE.compareAndSet(null, new Singleton_06());return INSTANCE.get();}}public static void main(String[] args) {System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2dSystem.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d}}
- java并发库提供了很多原子类来支持并发访问的数据安全性;AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
- AtomicReference 可以封装引用一个V实例,支持并发访问如上的单例方式就是使用了这样的一个特点。
- 使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
- 当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。
枚举单例模式
public enum Singleton_07 {INSTANCE;public void test(){System.out.println("hi~");}
}
- Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
- 这种方式解决了最主要的;线程安全、自由串行化、单一实例。
3.1.5 框架中的使用
3.1.4.1 Spring中的使用
- 项目加载的时候bean(一个bean对应某个类)自动创建(初始化,建一个实例),而后是每次调用bean的时候是注入的(不是重新new,所有整个系统都是这个实例,而且是spring自动提供的)。
- 对于Spring中实现Singleton模式,是以IOC容器为单位(就是说在这个容器里面bean实现Singleton),换句话说,一个JVM可能有多个IOC容器,而在这个容器里实现了singleton bean, 而Java中实现Singleton模式而言,有一个JVM,JVM中某个class只有一个实例 。
- singleton模式中,singleton的class在整个JVM中只有一个instance, Spring的Bean,你可以一个class配置多个Bean,这个class就有了多个instance。
- 这个singleton是指在spring容器中,这个Bean是单实例的,是线程共享的,所以要求这些类都是线程安全的。也就是说,不能出现修改Bean属性的方法,当然除了设值得那些setter。只要满足线程安全,这些bean都可以用singleton,而且我们在绝大多数使用上,也是这样用的,包括dao,service,Beanfactory是Spring初始以静态方式载入的,Spring的单例IOC是基于容器级的,所以这你都不用担心与考虑.
3.1.4.2 JDK中的单例模式
unsafe类
在研究多线程时会经常到这个类来,因为 CAS 就是通过 Unsafe 类来实现的。在 Unsafe 类中,Unsafe 对象也是通过单例模式获取。下面从源码中省略多余代码,提取出来单例模式部分。可以看到 Unsafe 构造方法被标记为 private,使用静态成员变量 theUnsafe 声明单例对象,并在静态代码块中进行初始化,从这里可以看出这是一个标准的饿汉式单例。
public final class Unsafe {private static final Unsafe theUnsafe;private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}static {registerNatives();Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});theUnsafe = new Unsafe();// 省略多余代码}
Runtime类
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}
}
Sl4fj类
public final class LoggerFactory {private LoggerFactory() {}public static ILoggerFactory getILoggerFactory() {if (INITIALIZATION_STATE == 0) {Class var0 = LoggerFactory.class;synchronized(LoggerFactory.class) {if (INITIALIZATION_STATE == 0) {INITIALIZATION_STATE = 1;performInitialization();}}}// 省略多余代码}
}
3.2 原型设计模式
- 原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。
- 原型模式(Prototype),在制造业中通常是指大批量生产开始之前研发出的概念模型,并基于各种参数指标对其进行检验,如果达到了质量要求,即可参照这个原型进行批量生产。
- 原型模式达到以原型实例创建副本实例的目的即可,并不需要知道其原始类,也就是说,原型模式可以用对象创建对象,而不是用类创建对象,以此达到效率的提升。
3.2.1 未使用设计模式的飞机大战
package com.shu;/*** @description: 敌机实体类* @author: shu* @createDate: 2022/9/9 14:19* @version: 1.0*/
public class EnemyPlane {private int x;//敌机横坐标private int y = 0;//敌机纵坐标public EnemyPlane(int x) {//构造器this.x = x;}public int getX() {return x;}public int getY() {return y;}public void fly() {//让敌机飞y++;//每调用一次,敌机飞行时纵坐标+1}}
package com.shu;/*** @description: 客服端* @author: shu* @createDate: 2022/9/9 14:20* @version: 1.0*/
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class Client {public static void main(String[] args) {List<EnemyPlane> enemyPlanes = new ArrayList<EnemyPlane>();for (int i = 0; i < 500; i++) {//此处于随机纵坐标处出现敌机,采用new关键字来实例化对象EnemyPlane ep = new EnemyPlane(new Random().nextInt(200));enemyPlanes.add(ep);}}}
- 使用了循环的方式来批量生产敌机,并使用了new关键字来实例化敌机,循环结束后500架敌机便统统被加入第4行定义的飞机列表enemyPlanes中。
- 这种做法看似没有任何问题,然而效率却是非常低的。我们知道在游戏画面上根本没必要同时出现这么多敌机,而在游戏还未开始之前,也就是游戏的加载阶段我们就实例化了这一关卡的所有500架敌机,这不但使加载速度变慢,而且是对有限内存资源的一种浪费。
- 然而遗憾的是,懒加载依然会有性能问题,主要原因在于我们使用的new关键字进行的基于类的实例化过程,因为每架敌机都进行全新构造的做法是不合适的。
3.2.2 采用设计模式的飞机大战
- 原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个 克隆方法。
- 所有的类对 克隆方法的实现都非常相似。 该方法会创建一个当前类的对象, 然后将原始对象所有的成员变量值复制到新建的类中。 你甚至可以复制私有成员变量, 因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。
- 支持克隆的对象即为_原型_。 当你的对象有几十个成员变量和几百种类型时, 对其进行克隆甚至可以代替子类的构造。
package com.shu;/*** @description:* @author: shu* @createDate: 2022/9/9 14:28* @version: 1.0*/
public class EnemyPlane1 implements Cloneable{private int x;//敌机横坐标private int y = 0;//敌机纵坐标public EnemyPlane1(int x) {//构造器this.x = x;}public int getX() {return x;}public int getY() {return y;}public void fly() {//让敌机飞y++;//每调用一次,敌机飞行时纵坐标+1}/*** 重写克隆方法,调用该方法来实例化对象* @return* @throws CloneNotSupportedException*/@Overrideprotected EnemyPlane1 clone() throws CloneNotSupportedException {return (EnemyPlane1) super.clone();}
}
3.2.3 深拷贝与浅拷贝
浅拷贝
- 浅拷贝是指只复制原始类型的值,比如原始类型int定义的值,它们会被复制到新克隆出的对象中。
- 引用类型同样会被拷贝,但是请注意这个操作只是拷贝了地址引用(指针)。
/*** @description: * @author: shu* @createDate: 2022/9/9 14:28* @version: 1.0*/
public class ShallowCopy {public static void main(String[] args) throws CloneNotSupportedException {Teacher teacher = new Teacher();teacher.setName("riemann");teacher.setAge(28);Student student1 = new Student();student1.setName("edgar");student1.setAge(20);student1.setTeacher(teacher);Student student2 = (Student) student1.clone();System.out.println(student2.getName());System.out.println(student2.getAge());System.out.println(student2.getTeacher().getName());System.out.println(student2.getTeacher().getAge());teacher.setName("jack");System.out.println("student1的teacher为: " + student1.getTeacher().getName());System.out.println("student2的teacher为: " + student2.getTeacher().getName());}
}class Teacher implements Cloneable {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}class Student implements Cloneable {private String name;private int age;private Teacher teacher;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}public Object clone() throws CloneNotSupportedException {Object object = super.clone();return object;}
}
我们可以验证上面是结论是否正确
深拷贝
- 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
package com.shu;/*** @description:* @author: shu* @createDate: 2022/9/9 14:46* @version: 1.0*/
public class DeepCopy {public static void main(String[] args) throws CloneNotSupportedException {Teacher teacher = new Teacher();teacher.setName("riemann");teacher.setAge(28);Student student1 = new Student();student1.setName("edgar");student1.setAge(20);student1.setTeacher(teacher);Student student2 = (Student) student1.clone();System.out.println("-------------拷贝后-------------");System.out.println(student2.getName());System.out.println(student2.getAge());System.out.println(student2.getTeacher().getName());System.out.println(student2.getTeacher().getAge());System.out.println("-------------修改老师的信息后-------------");// 修改老师的信息teacher.setName("jack");System.out.println("student1的teacher为: " + student1.getTeacher().getName());System.out.println("student2的teacher为: " + student2.getTeacher().getName());}
}class Teacher implements Cloneable {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Object clone() throws CloneNotSupportedException {return super.clone();}
}class Student implements Cloneable {private String name;private int age;private Teacher teacher;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}public Object clone() throws CloneNotSupportedException {// 浅复制时:// Object object = super.clone();// return object;// 改为深复制:Student student = (Student) super.clone();// 本来是浅复制,现在将Teacher对象复制一份并重新set进来student.setTeacher((Teacher) student.getTeacher().clone());return student;}
}
3.2.4 原型的结构模式
优点
- 你可以克隆对象, 而无需与它们所属的具体类相耦合。
- 你可以克隆预生成原型, 避免反复运行初始化代码。
- 你可以更方便地生成复杂对象。
- 你可以用继承以外的方式来处理复杂对象的不同配置。
缺点
- 克隆包含循环引用的复杂对象可能会非常麻烦。
3.2.5 框架中的应用
3.2.5.1 ArrayList中的使用
/*** Returns a shallow copy of this <tt>ArrayList</tt> instance. (The* elements themselves are not copied.)** @return a clone of this <tt>ArrayList</tt> instance*/public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
3.2.5.2 Spring中的使用
3.3 工厂方法设计模式
- 程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(Factory Method)则可以升华为一种设计模式,它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。
- 工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
- 我们完全不必关心产品的制造过程(实例化、初始化),而将这个任务交由相应的工厂来全权负责,工厂最终能交付产品供我们使用即可,如此我们便摆脱了产品生产方式的束缚,实现了与制造过程彻底解耦。
3.3.1 未使用工厂模式的坦克大战
模拟不同的敌机进行攻击
package com.shu.common01;/*** @description: 敌机* @author: shu* @createDate: 2022/9/10 10:46* @version: 1.0*/
public abstract class Enemy {private int x;private int y;public Enemy(int x, int y) {this.x = x;this.y = y;}/*** 在左边绘制不同的敌机*/public abstract void show();
}
飞机敌机
package com.shu.common01;/*** @description: 飞机敌机* @author: shu* @createDate: 2022/9/10 10:48* @version: 1.0*/
public class Airplane extends Enemy{public Airplane(int x, int y) {super(x, y);}/*** 在左边绘制不同的敌机*/@Overridepublic void show() {System.out.println("绘制飞机敌机,进行攻击");}
}
坦克敌机
package com.shu.common01;/*** @description: 坦克敌机* @author: shu* @createDate: 2022/9/10 10:49* @version: 1.0*/
public class Tank extends Enemy{public Tank(int x, int y) {super(x, y);}/*** 在左边绘制不同的敌机*/@Overridepublic void show() {System.out.println("绘制坦克敌机,进行攻击");}
}
客户端
package com.shu.common01;/*** @description: 客服端* @author: shu* @createDate: 2022/9/10 10:51* @version: 1.0*/import java.util.Random;public class Client {public static void main(String[] args) {int screenWidth = 100;//屏幕宽度System.out.println("游戏开始");Random random = new Random();//准备随机数int x = random.nextInt(screenWidth);//生成敌机横坐标随机数Enemy airplan = new Airplane(x, 0);//实例化飞机airplan.show();//显示飞机x = random.nextInt(screenWidth);//坦克同上Enemy tank = new Tank(x, 0);tank.show();}
}
我们可以看出代码的耦合度很高,当然我们这的实例很少,如果实例过大,代码臃肿且不易维护
3.3.2 使用工厂模式的坦克大战
我们详细一下,工厂的产出最终是产品,我们并不需要关注具体是生产流程,只关注最终的结果。
工厂
package com.shu.common01;/*** @description:* @author: shu* @createDate: 2022/9/10 11:04* @version: 1.0*/import java.util.Random;public class SimpleFactory {private int screenWidth;private Random random;//随机数public SimpleFactory(int screenWidth) {this.screenWidth = screenWidth;this.random = new Random();}/*** 根据传递不同的类型生成不同的产品* @param type* @return*/public Enemy create(String type) {int x = random.nextInt(screenWidth);//生成敌人横坐标随机数Enemy enemy = null;switch (type) {case "Airplane":enemy = new Airplane(x, 0);//实例化飞机break;case "Tank":enemy = new Tank(x, 0);//实例化坦克break;}return enemy;}}
调用
package com.shu.common01;/*** @description: 客服端* @author: shu* @createDate: 2022/9/10 10:51* @version: 1.0*/import java.util.Random;public class Client {public static void main(String[] args) {SimpleFactory simpleFactory=new SimpleFactory(100);simpleFactory.create("Airplane").show();simpleFactory.create("Tank").show();}
}
我们可以看到代码十分简洁,且易于维护,然而,这个简单工厂的确很“简单”,但并不涉及任何的模式设计范畴,虽然客户端中不再直接出现对产品实例化的代码,但羊毛出在羊身上,制造逻辑只是被换了个地方,挪到了简单工厂中而已,并且客户端还要告知产品种类才能产出,这无疑是另一种意义上的耦合。
3.3.3 结构模式
优点
- 你可以避免创建者和具体产品之间的紧密耦合。
- 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
- 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
缺点
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。
3.3.4 框架中的应用
3.3.4.1 JDK中的工厂模式
Calendar使用了工厂模式的简单工厂模式
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) {// fall back to the default instantiation}}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 no known calendar type is explicitly specified,// perform the traditional way to create a Calendar:// create a BuddhistCalendar for th_TH locale,// a JapaneseImperialCalendar for ja_JP_JP locale, or// a GregorianCalendar for any other locales.// NOTE: The language, country and variant strings are interned.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;}
3.4 抽象工厂设计模式
- 回到我们上面说的问题实例化的代码换了位置,但是告知产品种类才能产出,这无疑是另一种意义上的耦合。
- 抽象工厂模式(Abstract Factory)是对工厂的抽象化,而不只是制造方法。
- 、我们知道,为了满足不同用户对产品的多样化需求,工厂不会只局限于生产一类产品,但是系统如果按工厂方法那样为每种产品都增加一个新工厂又会造成工厂泛滥。
假设你正在开发一款家具商店模拟器。 你的代码中包括一些类, 用于表示:
- 一系列相关产品, 例如 椅子Chair 、 沙发Sofa和 咖啡桌CoffeeTable 。
- 系列产品的不同变体。 例如, 你可以使用 现代Modern 、 维多利亚Victorian 、 装饰风艺术ArtDeco等风格生成 椅子 、 沙发和 咖啡桌 。
我们当然是选择不同的工厂生产不同的东西,这样效率高,也方便管理
对于系列产品的每个变体, 我们都将基于 抽象工厂接口创建不同的工厂类。 每个工厂类都只能返回特定类别的产品, 例如, 现代家具工厂ModernFurnitureFactory只能创建 现代椅子ModernChair 、 现代沙发ModernSofa和 现代咖啡桌ModernCoffeeTable对象。
3.4.1 代码实现
抽象接口
package com.shu.common02;/*** @description: 抽象工厂方法* @author: shu* @createDate: 2022/9/10 12:13* @version: 1.0*/
public interface FurnitureFactory {/*** 生成椅子* @return*/Chair creatChair();/*** 生成办公桌* @return*/CoffeeTable creatCoffeeTable();/*** 生成沙发* @return*/Sofa creatSofa();
}
现代工厂
package com.shu.common02;/*** @description:* @author: shu* @createDate: 2022/9/10 12:25* @version: 1.0*/
public class ModernFurnitureFactory implements FurnitureFactory{/*** 生成椅子** @return*/@Overridepublic Chair creatChair() {return new Chair(180,150,"现代风格");}/*** 生成办公桌** @return*/@Overridepublic CoffeeTable creatCoffeeTable() {return new CoffeeTable(180,150,"现代风格");}/*** 生成沙发** @return*/@Overridepublic Sofa creatSofa() {return new Sofa(180,150,"现代风格");}
}
维多利亚工厂
package com.shu.common02;/*** @description:* @author: shu* @createDate: 2022/9/10 12:23* @version: 1.0*/
public class VictorianFurnitureFactory implements FurnitureFactory{/*** 生成椅子** @return*/@Overridepublic Chair creatChair() {return new Chair(180,150,"维多利亚");}/*** 生成办公桌** @return*/@Overridepublic CoffeeTable creatCoffeeTable() {return new CoffeeTable(180,150,"维多利亚");}/*** 生成沙发** @return*/@Overridepublic Sofa creatSofa() {return new Sofa(180,150,"维多利亚");}
}
测试
package com.shu.common02;/*** @description:* @author: shu* @createDate: 2022/9/10 12:26* @version: 1.0*/
public class Client02 {public static void main(String[] args) {// 现代风格ModernFurnitureFactory modernFurnitureFactory = new ModernFurnitureFactory();modernFurnitureFactory.creatChair();modernFurnitureFactory.creatCoffeeTable();modernFurnitureFactory.creatSofa();// 维多利亚风格VictorianFurnitureFactory victorianFurnitureFactory=new VictorianFurnitureFactory();victorianFurnitureFactory.creatChair();victorianFurnitureFactory.creatCoffeeTable();victorianFurnitureFactory.creatSofa();}
}
- 假设客户端想要工厂创建一把椅子。 客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。 无论是现代风格, 还是维多利亚风格的椅子, 对于客户端来说没有分别, 它只需调用抽象 椅子接口就可以了。 这样一来, 客户端只需知道椅子以某种方式实现了 sitOn坐下方法就足够了。 此外, 无论工厂返回的是何种椅子变体, 它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。
- 最后一点说明: 如果客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。 而在此之前, 应用程序必须根据配置文件或环境设定选择工厂类别。
3.4.2 抽象工厂模式结构
优点
- 你可以确保同一工厂生成的产品相互匹配。
- 你可以避免客户端和具体产品代码的耦合。
- 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
- 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
缺点
- 由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。
总而言之,可以认为抽象工厂模式:有多个抽象产品类;一个实体工厂类可以生产多种产品类,客户可以从一个工厂获得所有想要的产品。
3.4.3 框架中的使用
3.4.3.1 Spring中的使用
在 Spring 中,BeanFactory 是用于管理 Bean 的一个工厂,所有工厂都是 BeanFactory 的子类。这样我们可以通过 IOC 容器来管理访问 Bean,根据不同的策略调用 getBean() 方法,从而获得具体对象。
- BeanFactory 的子类主要有 ClassPathXmlApplicationContext、XmlWebApplicationContext , StaticWebApplicationContext、StaticApplicationContext。
- 在 Spring, 中,DefaultListableBeanFactory 实现了所有工厂的公共逻辑。
3.4.4 工厂方法与抽象工厂方法的区别
- 工厂方法是作为一个方法直接挂载在抽象的creator类中的(例如:抽象披萨商店类),除了工厂方法外其他的方法都已经具体实现。工厂方法作为抽象方法延后到子类(即具体的披萨商店类)实现。
- 而抽象工厂的使用场景是需要创建的不是一个产品,而是一组产品的时候。按照工厂方法的解决思路,就必须要在抽象的creator类中设置多个工厂方法,这样显然不好,所以就将这几个“工厂方法”抽取到专门的抽象工厂类中,creator类通过组合的方式创建Factory对象来获取工厂方法。
- 所以才会说:工厂方法用的是继承,抽象工厂用的是组合。工厂方法适用于创建单个产品的场景,抽象工厂适用于创建多个产品(产品族)的场景。
3.5 建造者设计模式
- 建造者模式(Builder)所构建的对象一定是庞大而复杂的,并且一定是按照既定的制造工序将组件组装起来的,例如计算机、汽车、建筑物等。我们通常将负责构建这些大型对象的工程师称为建造者。
- 与工厂系列模式不同的是,建造者模式的主要目的在于把烦琐的构建过程从不同对象中抽离出来,使其脱离并独立于产品类与工厂类,最终实现用同一套标准的制造工序能够产出不同的产品。
3.5.1 代码实现
- 建筑物本身应该由多个组件组成,且各组件按一定工序建造,缺一不可。
- 建筑物的组件建造是相当复杂的,为了简化其数据模型,我们将组成建筑物的模块归纳为3个组件,分别是地基、墙体、屋顶。
package com.shu;/*** @description: 建筑* @author: shu* @createDate: 2022/9/10 14:20* @version: 1.0*/import java.util.ArrayList;
import java.util.List;public class Building {// 用List来模拟建筑物组件的组装private List<String> buildingComponents = new ArrayList<>();/*** 地皮* @param basement*/public void setBasement(String basement) {// 地基this.buildingComponents.add(basement);}/*** 墙* @param wall*/public void setWall(String wall) {// 墙体this.buildingComponents.add(wall);}/*** 吊顶* @param roof*/public void setRoof(String roof) {// 屋顶this.buildingComponents.add(roof);}@Overridepublic String toString() {String buildingStr = "";for (int i = buildingComponents.size() - 1; i >= 0; i--) {buildingStr += buildingComponents.get(i);}return buildingStr;}}
组建专业的建筑施工团队对建筑工程项目的实施至关重要,于是地产开发商决定通过招标的方式来选择施工方。招标大会上有很多建筑公司来投标,他们各有各的房屋建造资质,有的能建别墅,有的能建多层公寓,还有能力更强的能建摩天大楼,建造工艺也各有区别。但无论如何,开发商规定施工方都应该至少具备三大组件的建造能力,于是施工标准公布出来了。
package com.shu;/*** @description: 建造标准* @author: shu* @createDate: 2022/9/10 14:22* @version: 1.0*/
public interface Builder {/*** 建筑地基*/void buildBasement();/*** 建筑墙体*/void buildWall();/*** 建筑屋顶*/void buildRoof();/*** 得到最后的建筑* @return*/Building getBuilding();
}
A公司
package com.shu;/*** @description: A施工队* @author: shu* @createDate: 2022/9/10 14:26* @version: 1.0*/
public class HouseBuilder implements Builder {private Building house;public HouseBuilder() {this.house = new Building();}/*** 建筑地基*/@Overridepublic void buildBasement() {System.out.println("挖土方,部署管道、线缆,水泥加固,搭建围墙、花园。");house.setBasement("╬╬╬╬╬╬╬╬\n");}/*** 建筑墙体*/@Overridepublic void buildWall() {System.out.println("搭建木质框架,石膏板封墙并粉饰内外墙。");house.setWall("|田|田 田|\n");}/*** 建筑屋顶*/@Overridepublic void buildRoof() {System.out.println("建造木质屋顶、阁楼,安装烟囱,做好防水。");house.setRoof("╱◥███◣\n");}/*** 得到最后的建筑** @return*/@Overridepublic Building getBuilding() {return house;}
}
B公司
package com.shu;/*** @description:* @author: shu* @createDate: 2022/9/10 14:30* @version: 1.0*/
public class ApartmentBuilder implements Builder {private Building apartment;public ApartmentBuilder() {this.apartment = new Building();}/*** 建筑地基*/@Overridepublic void buildBasement() {System.out.println("深挖地基,修建地下车库,部署管道、线缆、风道。");apartment.setBasement("╚═════════╝\n");}/*** 建筑墙体*/@Overridepublic void buildWall() {System.out.println("搭建多层建筑框架,建造电梯井,钢筋混凝土浇灌。");for (int i = 0; i < 8; i++) {// 此处假设固定8层apartment.setWall("║ □ □ □ □ ║\n");}}/*** 建筑屋顶*/@Overridepublic void buildRoof() {System.out.println("封顶,部署通风井,做防水层,保温层。");apartment.setRoof("╔═════════╗\n");}/*** 得到最后的建筑** @return*/@Overridepublic Building getBuilding() {return apartment;}
}
- 虽然施工方很好地保证了建筑物三大组件的施工质量,但开发商还是不放心,因为施工方毕竟只负责干活,施工流程无法得到控制。
- 工程总监并不关心是哪个施工方来造房子,更不关心施工方有什么样的建造工艺,但他能保证对施工工序的绝对把控,也就是说,工程总监只控制施工流程。
package com.shu;/*** @description: 监工* @author: shu* @createDate: 2022/9/10 14:32* @version: 1.0*/
public class Director {public Builder builder;public Director(Builder builder) {this.builder = builder;}public void setBuilder(Builder builder) {this.builder = builder;}/*** 监工规定施工步骤,工程总监并不关心是哪个施工方来造房子,更不关心施工方有什么样的建造工艺,* 但他能保证对施工工序的绝对把控,也就是说,工程总监只控制施工流程。* @return*/public Building direct(){builder.buildBasement();builder.buildWall();builder.buildRoof();return builder.getBuilding();}
}
测试
package com.shu;/*** @description:* @author: shu* @createDate: 2022/9/10 14:36* @version: 1.0*/
public class Client {public static void main(String[] args) {Director director = new Director(new HouseBuilder());Building direct = director.direct();System.out.println(direct);System.out.println("----------------------------------------");Director director1 = new Director(new ApartmentBuilder());Building direct1 = director1.direct();System.out.println(direct1);}
}
3.5.2 建造者模式结构
优点
- 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
- 生成不同形式的产品时, 你可以复用相同的制造代码。
- 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
缺点
- 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
3.5.3 框架中的使用
3.5.3.1 StringBuilder类中的使用
public final class StringBuilderextends AbstractStringBuilderimplements Serializable, CharSequence
{@Overridepublic StringBuilder append(Object obj) {return append(String.valueOf(obj));}...@Overridepublic String toString() {// 创建拷贝对象, 不分享内部的字符数组return new String(value, 0, count);}
}
3.5.3.2 Mybatis中的使用
mybatis 的 SqlSessionFactoryBuilder 类 用到了建造者模式,而且在 Mybatis中SqlSessionFactory是由SqlSessionFactoryBuilder产生的
public class SqlSessionFactoryBuilder {...public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
}
解析配置文件
package org.apache.ibatis.builder.xml;
...public class XMLConfigBuilder extends BaseBuilder {// 解析配置累private void parseConfiguration(XNode root) {try {// issue #117 read properties first// 1.读取XML的<properties>标签propertiesElement(root.evalNode("properties"));// 2. 读取XML的<settings>标签Properties settings = settingsAsProperties(root.evalNode("settings"));// 3. 加载自定义的配置loadCustomVfs(settings);loadCustomLogImpl(settings);// 4. 加载XML的<typeAliases> 标签typeAliasesElement(root.evalNode("typeAliases"));// 5. 加载XML的 <plugins> 标签pluginElement(root.evalNode("plugins"));// 6. 加载XML的<objectFactory> 标签objectFactoryElement(root.evalNode("objectFactory"));// 7. 加载XML的<objectWrapperFactory>标签objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 8. 加载XML的<reflectorFactory> 标签reflectorFactoryElement(root.evalNode("reflectorFactory"));// 9. 更新设置settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 10. 加载XML的<environments> 标签environmentsElement(root.evalNode("environments"));// 11. 加载XML的 <databaseIdProvider> 标签databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 12. 加载XML的 <typeHandlers> 标签typeHandlerElement(root.evalNode("typeHandlers"));// 13. 加载XML的 <mappers> 标签mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}
}
Java设计模式(二)创建型设计模式相关推荐
- java设计模式之创建型设计模式
创建型设计模式: 抽象工厂(Abstract Factory): 用途:提供一个接口以创建一系列相关或相互依赖的对象,而无需指定具体的类. 场景: 一个系统要独立于它的产品的创建. 一个系统要由多个产 ...
- JavaScript设计模式之创建型设计模式
此系列总结与<JavaScript设计模式>,总共分为创建型设计模式.结构型设计模式.行为型设计模式.技巧型设计模式和架构性设计模式五大类. github原文地址:YOU-SHOULD-K ...
- android 工厂模式创建,7、Android设计模式---(创建型设计模式)抽象工厂模式
一.介绍,定义 抽象工厂模式也是创建型模式之一,抽象工厂模式起源于对不同操作系统的图形化解决方案,如不同操作系统的按钮和文本框控件及其实现. 为创建一组相关或者相互依赖的对象提供接口,而不需要具体指定 ...
- Java内功心法,创建型设计模式包括哪些
1. 单例(Singleton) Intent 确保一个类只有一个实例,并提供该实例的全局访问点. Class Diagram 使用一个私有构造函数.一个私有静态变量以及一个公有静态函数来实现. 私有 ...
- 【设计模式】创建型设计模式
文章目录 1. 基础 ①如何学习设计模式 ② 类模型 ③ 类关系 2. 设计原则 3. 模板方法 ① 定义 ②背景 ③ 要点 ④ 本质 ⑤ 结构图 ⑥ 样例代码 4. 观察者模式 ① 定义 ②背景 ③ ...
- java设计模式 (二) 创建模式
java设计模式 (二) 创建型模式 单例模式 Singleton pattern 现实场景 双11, 小华收快递, 早上圆通快递叫他下楼收快递, 刚上来, 顺丰快递叫他下楼收快递,.然后没多久EMS ...
- 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 第1章 Python的创建型设计模式...
本节书摘来自华章出版社<Python编程实战:运用设计模式.并发和程序库创建高质量程序>一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云 ...
- javaScript设计模式-创建型设计模式
我们大家一听到设计模式就感觉设计模式是一个高端的东西,到底什么是设计模式呢?其实设计模式也就是我们的前辈在写代码的时候遇到的问题,提出的解决方案,为了方便人与人之间的交流,取了个名字,叫做设计模式. ...
- 二、java设计模式之工厂方法+抽象工厂模式(创建型设计模式)
创建型设计模式-工厂模式和应用 工厂模式介绍: 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象 例子: 需要购买一辆车,不用管 ...
最新文章
- C++知识点19——使用C++标准库(再谈string——string的初始化、赋值、添加、删除、访问)
- 音频录入后以不同采样率输出
- 《操作系统》实验报告——进程调度的模拟
- 约三分之二的 DDoS 攻击指向通信服务提供商
- python函数使用易错举例
- 4个数字,如何最快找到最小的2个数
- 《解析几何》吕林根,徐子道第四版 习题 1.4.7,1.4.8,1.4.9
- Netty in action—第一个Netty应用
- java遍历map集合传值,Java之五种遍历Map集合的方式
- Spark机器学习库简介
- BIGEMAP教您如何选择分度带(中央子午线)
- 华为HCNE题库大全(第一部)
- openoffice 中文乱码问题
- 给加西亚的信》--如何做一名优秀员工
- 【前端技术】一篇文章搞掂:JS
- 008.UG_NX自由曲面
- CES生产函数中参数的意义
- 1007	 Rikka with Travels	Rikka with Travels
- ai修复图片 python_百度AI攻略:拉伸图像恢复
- Linux下端口映射工具
热门文章
- java 转换gbk编码,java中GBK转UTF-8乱码的解决方法
- saiku 3.7.4 构建过程
- 优思学院|六西格玛黑带的个人成功特质
- android 百度地址转经纬度,百度地图经纬度和地址的相互转换
- 万向区块链小课堂:超短文梳理区块链层级,字字珠玑
- 使用PlantUML快速绘制流程图/时序图/类图/用例图…
- 《非对称风险》书中精髓:「风险共担」是我们理解人类社会和世界的前提,一个没有风险共担的系统会慢慢积累不平衡,最终垮掉。
- 三坐标测量基础知识之日常维护和保养
- 采集时如何有效地防止被网站屏蔽IP
- 七年级上册英语|好看又容易画的思维导图