设计模式 - 创建型模式
设计模式 - 创建型模式
从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析。
工厂方法模式(Factory Method)
工厂方法模式分为三种:
普通工厂模式
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:

举例:(我们举一个动物叫的例子)
首先,创建公共接口:
// 动物的抽象类
public interface Animal {
// 叫
void call();
}
创建实现类:
// 猫
public class Cat implements Animal{
@Override
public void call() {
System.out.println("喵喵喵");
}
}
// 狗
public class Dog implements Animal{
@Override
public void call() {
System.out.println("汪汪汪");
}
}
创建建工厂类:
// 动物的创建工厂
public class AnimalFactory {
// 创建方法
public Animal product(String type) {
if (type.equals("dog")) {
return new Dog();
} else if (type.equals("cat")) {
return new Cat();
}
System.out.println("请输入正确的类型");
return null;
}
}
多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:

将上面的代码做下修改,改动下AnimalFactory类就行,如下:
// 动物的创建工厂
public class AnimalFactory {
// 狗的工厂方法
public Animal productDog(){
return new Dog();
}
// 猫的工厂方法
public Animal productCat(){
return new Cat();
}
}
静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
// 动物的创建工厂
public class AnimalFactory {
// 狗的静态工厂方法
public static Animal productDog(){
return new Dog();
}
// 猫的静态工厂方法
public static Animal productCat(){
return new Cat();
}
}
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。

请看例子:
// 动物的抽象类
public interface Animal {
// 叫
void call();
}
实现类:
// 猫
public class Cat implements Animal {
@Override
public void call() {
System.out.println("喵喵喵");
}
}
// 狗
public class Dog implements Animal {
@Override
public void call() {
System.out.println("汪汪汪");
}
}
工厂抽象类:
// 工厂抽象类
public interface Provider {
Animal produce();
}
工厂实现类
// 猫的工厂方法
public class CatFactory implements Provider{
@Override
public Animal produce() {
return new Cat();
}
}
// 狗的工厂方法
public class DogFactory implements Provider{
@Override
public Animal produce() {
return new Dog();
}
}
抽象工厂的使用
public class Main {
public static void main(String[] args) {
Provider provider = new CatFactory();
Animal animal = provider.produce();
animal.call();
}
}
其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
首先我们写一个简单的单例类:
// 单例类
public class Singleton {
// 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
private static Singleton instance = null;
// 私有构造方法,防止被实例化
private Singleton() {
}
// 静态工程方法,创建实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
public Object readResolve() {
return instance;
}
}
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字
给方法加 synchronized
// 静态工程方法,创建实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
给创建单例对象加synchronized
// 静态工程方法,创建实例
public static Singleton getInstance() {
if (instance == null) {
synchronized (instance) {
if (instance == null) { // 双重判定
instance = new Singleton();
}
}
}
return instance;
}
这似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的.
看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:
// 用内部类来保证单例的唯一性
private static class SingletonFactory {
// 单例对象
private static Singleton instance = new Singleton();
}
// 获取单例对象方法
public static Singleton getInstance() {
return SingletonFactory.instance;
}
实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:
// 单例类
public class Singleton {
/**
* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
*/
private static Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/**
* 用内部类来保证单例的唯一性
*/
private static class SingletonFactory {
/**
* 单例对象
*/
private static Singleton instance = new Singleton();
}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
public Object readResolve() {
return instance;
}
}
其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。
通过单例模式的学习告诉我们:
1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!
建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。我们看一下代码:
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

创建的对象(必要):
/**
* 产品类--被建造的对象 电脑
*/
public class Computer {
// cpu
private String cpu ;
// 主板
private String mainBoard ;
// 内存
private String memory ;
// 硬盘
private String hardDisk ;
// get set 方法
}
抽象的建造者(非必要):
/**
* 抽象的建造者,即装电脑的步骤
* 至于安装什么型号的主板,不是我关心,而是具体的建造者关心的
*/
public interface Builder {
// 安装主板
void createMainBoard(String mainBoard) ;
// 安装 cpu
void createCpu(String cpu) ;
// 安装硬盘
void createhardDisk(String hardDisk) ;
// 安装内存
void createMemory(String memory) ;
// 组成电脑
Computer createComputer() ;
}
具体的建造者(必要):
/**
* 具体的建造者,这里是商场的一个装机人员
*/
public class AssemblerBuilder implements Builder {
private Computer computer = new Computer();
@Override
public void createCpu(String cpu) {
computer.setCpu(cpu);
}
@Override
public void createhardDisk(String hardDisk) {
computer.setHardDisk(hardDisk);
}
@Override
public void createMainBoard(String mainBoard) {
computer.setMainBoard(mainBoard);
}
@Override
public void createMemory(String memory) {
computer.setMemory(memory);
}
@Override
public Computer createComputer() {
return computer;
}
}
指导者(非必要):
/**
* 声明一个导演类「指挥者,这里可以装电脑的老板」,用来指挥组装过程,也就是组装电脑的流程
*/
public class Director {
private Builder builder;
// 使用多态,装机工非常多,我管你小美,小兰,小猪,我统统收了
public Director(Builder builder) {
this.builder = builder;
}
// 老板最后只想看到装成的成品---要交到客户手中
public Computer createComputer(String cpu, String hardDisk, String mainBoard, String memory) {
// 具体的工作是装机工去做
this.builder.createMainBoard(mainBoard);
this.builder.createCpu(cpu);
this.builder.createMemory(memory);
this.builder.createhardDisk(hardDisk);
return this.builder.createComputer();
}
}
从这点看出,建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工程模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建符合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定。
原型模式(Prototype)
原型模式的定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
原型模式的优点及适用场景
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
原型模式的注意事项
- 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
- 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
// Prototype对象继承Cloneable接口,clone方法就是克隆接口,object类有clone方法的实现
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype prototype = (Prototype) super.clone();
return prototype;
}
}
巨人的肩膀:
https://juejin.cn/post/6844903518449434638
https://www.jianshu.com/p/b96b37e8b760
https://wiki.jikexueyuan.com/project/java-design-pattern/prototype-pattern.html

浙公网安备 33010602011771号