创建型设计模式(待更新)
前言 - 熟能生巧(★★★)
关于设计模式的学习,需要结合具体的应用场景进行理解,即站在用户的角度去理解需求,目的是让自己设计的代码能够为用户提供统一的接口,并且设计的模块具有高内聚低耦合、有更好的可扩展性、便于后期的代码维护。
设计模式就是根据不同的需求设计出来了容易复用的框架,是工程应用中优质代码,比如观察者模式,工厂模式等,这些都和生活中的典型例子息息相关,既方便理解也方便应用。
有些设计模式在实现时有多种变种,比如工厂模式可以细分成简单工厂和多工厂模式,单例模式可以划分成懒汉式和饿汉式,是否需要用,具体用哪个需要结合具体场景考虑。如果功能简单,刻意使用某种设计模式可能会增加模块的复杂度。
这些设计模式虽然几乎尽人皆知,但不是每个人都能用得好。比如对于工厂模式,要想孰能生巧,熟练掌握该模式,需要多思考工厂方法如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、原型模式等)变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在。
关于类图中属性的可见性:
+
(public):可以被所有其他类所访问。–
(private):只能被自己类访问和修改。#
(protected) :自身,子类及同一个包中类可以访问。~
(package)default
:同一包中的类可以访问,声明时没有加修饰符。
Java
的特征包括:封装,多态,继承和抽象。- 为了理解
Java
的抽象,建议先根据需求,将各个实体类逐层向上抽象(由具体到抽象的过程),进而理解各种设计模式的设计逻辑和思路,分析其封装性、可扩展性、可见性(是否要使用private
,protected
或者final
修饰)和可维护性如何,是否满足设计模式的六大原则。 - 接着通过与其他设计模式的比较,从抽象到具体理解该设计模式在需求解决上有哪些优点和缺点,巩固设计模式的学习。
- 为了理解
通过的典型类图,即一个接口,多个抽象类,然后是
N
个实现类
设计模式的六大原则
参考书籍:设计模式之禅
笔记配套代码
文章目录
- 前言 - 熟能生巧(★★★)
- 创建型设计模式
- 一、单例模式
- 1、需求假设
- 2、单例模式的定义和实现
- 1)定义(自行实例化)
- 2)类图
- 3)代码实现(饿汉式,线程安全 ★★★★)
- 3、优点和适用场景
- 1)优点
- 2)缺点(★★)
- 3)应用场景(★★★★)
- 4、单例模式的扩展
- 1)懒汉式 - 线程不安全(★★★★)
- 2)对象复制问题
- 3)多例模式(应该可以配合抽象工厂实现固定容量的线程池,快速响应提交的任务 ★★★★)
- 二、工厂模式
- 1、需求假设(★★★)
- 2、工厂模式的定义和实现
- 1)定义(产品单一,★★★★)
- 2)类图
- 3)可见性分析
- 4)代码实现(★★★★★)
- a)人类接口
- b)各人种实现类
- c)人类创造工厂类(★★★★)
- d)Main类
- 3、优点和适合的场景
- 1)优点(★★★★)
- 2)场景
- 4、工厂模式的扩展
- 1)简单(静态)工厂模式
- 2)多工厂模式(Executors ★★★★)
- 3)替代单例模式
- 4)延迟初始化(★★★★)
- 三、抽象工厂模式
- 1、需求假设
- 2、抽象工厂模式的定义和实现
- 1)定义(产品间具有依赖关系,★★★★)
- 2)类图(男女人类 -> 黄种人抽象类 -> 接口)
- 3)可见性分析
- 4)代码实现
- a)人种接口
- b)抽象人种类
- c)人种实现类
- d)抽象工厂类(关于每个产品族的抽象)
- e)工厂实现类(N个产品对应N个工厂实现类)
- f)Main类
- 3、优点和适合场景
- 1)小总结(★★★★★)
- 2)优缺点(★★★)
- a)优点
- b)缺点
- 3)应用场景(★★★★)
- 四、生成器模式
- 1、需求假设
- 2、生成器模式的定义和实现
- 1)定义
- 2)类图(★★★★)
- 3)可见性分析(★★★)
- 4)代码实现
- a)车辆模型抽象类
- b)奔驰和宝马模型
- c)Main类生产BMW模型
- d)抽象汽车组装类(使用建造者管理Main类,为用户提供统一的接口)
- e)车模型组装者
- f)导演类
- 3、优点和适用场景
- 1)优点
- 2)应用场景
- 3)建造者和工厂方法的区别(★★★★★)
- 五、原型模式
- 1、需求假设
- 2、定义和实现
- 1)浅拷贝
- 2)深拷贝(★★★★)
- 3、应用场景(★★★★)
- 1)优点:
- 2)应用场景
- 3)注意事项
创建型设计模式
创建型设计模式的目的是根据用户简单需求,为用户提供创建好的产品,同时为用户隐藏产品的具体创建过程; 用户无法直接new
产品,需要通过以下创建型设计模式来获得产品。
一、单例模式
1、需求假设
需求:皇帝每天要上朝接待臣子、处理政务,臣子每天要
叩拜皇帝,皇帝只能有一个。问臣子如何保证每天访问的皇帝是同一个人?
2、单例模式的定义和实现
1)定义(自行实例化)
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
2)类图
单例模式的通用类图
3)代码实现(饿汉式,线程安全 ★★★★)
皇帝类:
public class Emperor{private static final Emperor emperor = new Emperor();private Emperor(){}public static Emeperor getInstance(){return emperor;}//皇帝发话了public static void say(){System.out.println("我就是皇帝某某某....");}
}
臣子类:
public class Minister {public static void main(String[] args) {for(int day=0;day<3;day++){Emperor emperor=Emperor.getInstance();emperor.say();}//三天见的皇帝都是同一个人,荣幸吧!}
}
Note:皇帝类有两个注意点:
- 由于单例模式下的实体类构造函数是私有的(
private
),要想访问到类内初始化的实例对象,需要设置静态方法(static
) - 饿汉式单例模式直接在类内初始化实例对象,要想通过静态方法返回给用户,则注意用
static final
关键字修饰。 - 除了构造方法,其他方法建议使用
static
静态方法。
3、优点和适用场景
1)优点
- 由于单例模式在内存中只有一个实例,减少了内存开支
- 当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(注意Java的GC机制)。
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
2)缺点(★★)
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
3)应用场景(★★★★)
- 要求生成唯一序列号的环境
- 整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。
最佳实践:
- 在
Spring
中,每个Bean
默认就是单例的,这样做的优点是Spring
容器可以管理这些Bean
的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。 - 如果采用非单例模式(
Prototype
类型),则Bean
初始化后的管理交由J2EE容器,Spring
容器不再跟踪管理Bean
的生命周期。
4、单例模式的扩展
1)懒汉式 - 线程不安全(★★★★)
public class Singleton {private static Singleton singleton = null;//限制产生多个对象private Singleton(){}//通过该方法获得实例对象public static Singleton getSingleton(){if(singleton == null){singleton = new Singleton();}return singleton;}
}
存在的问题:如一个线程A
执行到singleton=new Singleton()
,但还没有获得对象(对象初始化是需要时间的),第二个线程B
也在执行,执行到(singleton==null
)判断,那么线程B
获得判断条件也是为真,于是继续运行下去,线程A
获得了一个对象,线程B
也获得了一个对象,在内存中就出现两个对象!
解决方法:使用Synchronized
关键字修饰方法或者代码块,或者使用Reentrantlock
,但是**仍然建议使用饿汉式单例模式,静态final
对象无法被修改,可以保证线程安全 **!!
2)对象复制问题
Java中,对象默认是不可以被复制的,若实现了Cloneable
接口,并实现了clone
方法,则可以直接通过对象复制方式创建一个新对象。
由于单例类对象很少被要求复制,因此单例类不要实现Cloneable
接口。
3)多例模式(应该可以配合抽象工厂实现固定容量的线程池,快速响应提交的任务 ★★★★)
固定数量的皇帝类
public class Emperor {//定义最多能产生的实例数量private static int maxNumOfEmperor = 2;//每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性private static ArrayList<String> nameList=new ArrayList<String>();//定义一个列表,容纳所有的皇帝实例private static ArrayList<Emperor> emperorList=new ArrayList<Emperor>();//当前皇帝序列号private static int countNumOfEmperor =0;//产生所有的对象static{for(int i=0;i<maxNumOfEmperor;i++){emperorList.add(new Emperor("皇"+(i+1)+"帝"));}}private Emperor(){//世俗和道德约束你,目的就是不产生第二个皇帝}//传入皇帝名称,建立一个皇帝对象private Emperor(String name){nameList.add(name);}//随机获得一个皇帝对象public static Emperor getInstance(){Random random = new Random();//随机拉出一个皇帝,只要是个精神领袖就成countNumOfEmperor = random.nextInt(maxNumOfEmperor);return emperorList.get(countNumOfEmperor);}//皇帝发话了public static void say(){System.out.println(nameList.get(countNumOfEmperor));}
}
这种需要产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,提供系统的响应速度。例如读取文件,我们可以在系统启动时完成初始化工作,在内存中启动固定数量的reader
实例,然后在需要读取文件时就可以快速响应。
二、工厂模式
1、需求假设(★★★)
假设女娲可以通过八卦炉来造人,如果八卦炉的火候欠佳,则造出来白人;如果火候过旺,造出来黑人;如果火候刚刚好,则造出来黄种人,而不同人种除了肤色不同之外,语言也存在不同。
玉皇大帝知道女娲会造凡人,作为天神的他很好奇凡人究竟长啥样,因此跟女娲说:“女娲,凡人我还没见过,每个人种我都要一个,你能否帮我造出来?最近你不还要忙着补天吗,当然作为报酬,我宫里的玉石你顺便挑。”,女娲听了也随即答应了。这样女娲既要帮着生产(生产商),也要帮着采购(用户)。
2、工厂模式的定义和实现
1)定义(产品单一,★★★★)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
2)类图
3)可见性分析
先对上面的类图的可见性分析进行解释
- 用户只关心能否给他3个不一样的人种,因此3个产品
BlackHuman
,YellowHuman
,WhiteHuman
对用户可见; - 用户并不关心这3个人种是怎么捏出来的,他只想看看不同人种长啥样、说什么方言而已。因此作为生产商的女娲,其八卦炉(
AbstractHumanFactory
的createHuman()
)对用户可见,用于直接为用户生产3个人种。
4)代码实现(★★★★★)
a)人类接口
public interface Human {//每个人种的皮肤都有相应的颜色public void getColor();//人类会说话public void talk();
}
b)各人种实现类
public class BlackHuman implements Human {public void getColor(){System.out.println("黑色人种的皮肤颜色是黑色的!");}public void talk() {System.out.println("黑人会说话,一般人听不懂。");}
}---public class YellowHuman implements Human {public void getColor(){System.out.println("黄色人种的皮肤颜色是黄色的!");}public void talk() {System.out.println("黄色人种会说话,一般说的都是双字节。");}
}---public class WhiteHuman implements Human {public void getColor(){System.out.println("白色人种的皮肤颜色是白色的!");}public void talk() {System.out.println("白色人种会说话,一般都是但是单字节。");}
}
c)人类创造工厂类(★★★★)
//抽象人类创造工厂类
public abstract class AbstractHumanFactory {public abstract <T extends Human> T createHuman(Class<T> c);
}//人类创造工厂类
public class HumanFactory extends AbstractHumanFactory {public <T extends Human> T createHuman(Class<T> c){//定义一个生产的人种Human human=null;try {//产生一个人种human = (Human)Class.forName(c.getName()).newInstance();} catch (Exception e) {System.out.println("人种生成错误!");}return (T)human;}
}
Note:
- 在抽象工厂类中,采用了泛型(Generic)定义了
createHuman()
,通过定义泛型对createHuman
的输入参数产生两层限制:- 必须是
Class
类型; - 必须是
Human
的实现类
- 必须是
- 这里的工厂实现类通过反射来创建对象
d)Main类
public class NvWa {public static void main(String[] args) {//声明阴阳八卦炉AbstractHumanFactory YinYangLu = new HumanFactory();//女娲第一次造人,火候不足,于是白人产生了System.out.println("--造出的第一批人是白色人种--");Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);whiteHuman.getColor();whiteHuman.talk();//女娲第二次造人,火候过足,于是黑人产生了System.out.println("\n--造出的第二批人是黑色人种--");Human blackHuman = YinYangLu.createHuman(BlackHuman.class);blackHuman.getColor();blackHuman.talk();//第三次造人,火候刚刚好,于是黄色人种产生了System.out.println("\n--造出的第三批人是黄色人种--");Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);yellowHuman.getColor();yellowHuman.talk();}
}
3、优点和适合的场景
1)优点(★★★★)
- 良好的封装性:一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要**知道这个产品的类名(或约束字符串)**就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
- 扩展性好:只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成**“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个
BrownHuman
类**,工厂类不用任何修改就可完成系统扩展。 - 屏蔽产品类:产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口(如果存在多个产品,则关心多个产品的统一接口)。如果使用
JDBC
连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切
换一下驱动名称(前提条件是SQL
语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。 - 工厂方法模式是典型的解耦框架:
- 高层模块只需要知道产品的抽象类,其他的实现类
都不用关心,符合迪米特法则(最少知道原则)。 - 符合依赖倒置原则(基于接口编程),只依赖产品类的抽象;
- 符合里氏替换原则,使用产品子类替换产品父类
- 高层模块只需要知道产品的抽象类,其他的实现类
2)场景
工厂方法模式是new
一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度
4、工厂模式的扩展
1)简单(静态)工厂模式
如果只有一个工厂,则可以去掉AbstarctHumanFactory
,直接使用工厂实现类,利用其public static <T extends Human> createHuman(Class<T> c)
方法,创建并返回继承于父类Human
的对象,类图如下:
简单工厂类的代码实现如下
public class HumanFactory {public static <T extends Human> T createHuman(Class<T> c){//定义一个生产出的人种Human human=null;try {//产生一个人种human = (Human)Class.forName(c.getName()).newInstance();} catch (Exception e) {System.out.println("人种生成错误!");}return (T) human;}
}
相应的Main
类也要发生修改,这里就不附上代码了
简单(静态)工厂模式的缺点是扩展比较困难,不符合开闭原则。
2)多工厂模式(Executors ★★★★)
在使用工厂类初始化对象时,如果所有的产品类(黑/白/黄种人)都放到一个工厂方法中进行初始化会使代码结构不清晰。因此可以使用多工厂模式来分别创建。类图如下:
每个人种(具体的产品类)都对应了一个创建者,每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则。
工厂类代码如下:
//抽象工厂类
public abstract class AbstractHumanFactory {public abstract Human createHuman();
}
//工厂实现类1
public class BlackHumanFactory {public Human createHuman(){return new BlackHuman();}
}//工厂实现类2
public class WhiteHumanFactory {public Human createHuman(){return new WhiteHuman();}
}//工厂实现类3
public class YellowHumanFactory {public Human createHuman(){return new YellowHuman();}
}
Main类代码如下:
public class NvWa {public static void main(String[] args) {//女娲第一次造人,火候不足,于是白色人种产生了System.out.println("--造出的第一批人是白色人种--");Human whiteHuman = (new WhiteHumanFactory()).createHuman();whiteHuman.getColor();whiteHuman.talk();//女娲第二次造人,火候过足,于是黑色人种产生了System.out.println("\n--造出的第二批人是黑色人种--");Human blackHuman = (new BlackHumanFactory()).createHuman();blackHuman.getColor();blackHuman.talk();//第三次造人,火候刚刚好,于是黄色人种产生了System.out.println("\n--造出的第三批人是黄色人种--");Human yellowHuman = (new YellowHumanFactory()).createHuman();yellowHuman.getColor();yellowHuman.talk();}
}
优点:每一个产品类都对应了一个创建类,好处就是创建类的职责清晰,而且结构简单。
缺点:
- 增加扩展的难度:如果要扩展一个产品类,就需要建立一个相应的工厂类,这样增加了扩展的难度。
- 增加维护的难度:因为工厂类和产品类的数量相同,维护时需要考虑两个对象之间的关系。
解决方法:在复杂应用中,对象如果和多个工厂类绑定则代码维护成本较高,这时可以增加一个协调类避免调用者与各个
子工厂交流,协调类的作用是封装子工厂类,对高层模块提供关于多个工厂类的统一访问接口。
3)替代单例模式
单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象,类图如下所示(单例工厂聚合Singleton
类(has a
)):
Singleton
定义了一个private
的无参构造函数,目的是不允许通过new
的方式创建一个对象。
//单例类
public class Singleton {//不允许通过new产生一个对象private Singleton(){}public void doSomething(){//业务处理}
}
Singleton
没有提供构造器,工厂方法如何创建单例对象呢? 答案是通过反射获取private
构造器。
public class SingletonFactory {private static Singleton singleton;static{try {Class cl=Class.forName(Singleton.class.getName());//获得无参构造Constructorconstructor=cl.getDeclaredConstructor();//设置无参构造是可访问的constructor.setAccessible(true);//产生一个实例对象singleton = (Singleton)constructor.newInstance();} catch (Exception e) {//异常处理}}public static Singleton getSingleton(){return singleton;}
}
4)延迟初始化(★★★★)
工厂类可以用来实现延迟初始化,创建对象时先从缓存中(prMap
)取,如果缓存中没有,再创建并插入prMap
中,延迟初始化常用于线程池和数据库连接池的实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQ9lGN3J-1664638797387)(./img/Snipaste_2022-09-27_23-47-44.png)]
参考代码如下:
public class ProductFactory {private static final Map<String,Product> prMap = new HashMap();public static synchronized Product createProduct(String type) throws Exception{Product product =null;//如果Map中已经有这个对象if(prMap.containsKey(type)){product = prMap.get(type);}else{if(type.equals("Product1")){product = new ConcreteProduct1();}else{product = new ConcreteProduct2();}//同时把对象放到缓存容器中prMap.put(type,product);}return product;}
}
扩展场景:延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量(Map<String,List<Product>> prMap
),可以通过判断Map
中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会要求设置一个MaxConnections
最大连接数量,该数量就是内存中最大实例化的数量。
三、抽象工厂模式
1、需求假设
(工厂模式中假设的需求的后续)玉皇大帝收到3个人种的产品之后,欣赏了一番,但突然有了个疑问,他问女娲:“凡人是没有性别的吗?“, 这时女娲意识到她造的人没有性别,玉皇大帝看了女娲眉头紧锁,又有了新想法,让女娲造出男人和女人,让凡人在自己的土地上繁衍后代。因此女娲在多人种的基础上,造出了男人和女人,让男人和女人之间自由配对,完成社会文明的延续。
2、抽象工厂模式的定义和实现
1)定义(产品间具有依赖关系,★★★★)
为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类(并不是用了抽象工厂类就是抽象工厂模式)。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
2)类图(男女人类 -> 黄种人抽象类 -> 接口)
Java
的典型类图,即一个接口,多个抽象类,然后是N
个实现
类,每个人种都是一个抽象类,性别是在各个实现类中实现的。
3)可见性分析
- 用户关心的是男人和女人这两个产品,而不关心男人和女人的创建过程,因此用户只关心
Human
,FemaleFactory
和MaleFactory
这几个接口;
4)代码实现
a)人种接口
public interface Human {//每个人种都有相应的颜色public void getColor();//人类会说话public void talk();//每个人都有性别public void getSex();
}
b)抽象人种类
//白色人种
public abstract class AbstractWhiteHuman implements Human {//白色人种的颜色是白色的public void getColor(){System.out.println("白色人种的皮肤颜色是白色的!");}//白色人种讲话public void talk() {System.out.println("白色人种会说话,一般说的都是单字节。");}
}
//黑色人种
public abstract class AbstractBlackHuman implements Human {public void getColor(){System.out.println("黑色人种的皮肤颜色是黑色的!");}public void talk() {System.out.println("黑人会说话,一般人听不懂。");}
}
//黄色人种
public abstract class AbstractYellowHuman implements Human {public void getColor(){System.out.println("黄色人种的皮肤颜色是黄色的!");}public void talk() {System.out.println("黄色人种会说话,一般说的都是双字节。");}
}
c)人种实现类
以黄色人种为例:包括黄色男性人种和黄色女性人种
//黄人女性
public class FemaleYellowHuman extends AbstractYellowHuman {public void getSex() {System.out.println("黄人女性");}
}//黄人男性
public class MaleYellowHuman extends AbstractYellowHuman {public void getSex() {System.out.println("黄人男性");}
}
d)抽象工厂类(关于每个产品族的抽象)
public interface HumanFactory {//制造一个黄色人种public Human createYellowHuman();//制造一个白色人种public Human createWhiteHuman();//制造一个黑色人种public Human createBlackHuman();
}
e)工厂实现类(N个产品对应N个工厂实现类)
男性工厂实现类
public class MaleFactory implements HumanFactory {//生产出黑人男性public Human createBlackHuman() {return new MaleBlackHuman();}//生产出白人男性public Human createWhiteHuman() {return new MaleWhiteHuman();}//生产出黄人男性public Human createYellowHuman() {return new MaleYellowHuman();}
}
女性工厂实现类
public class FemaleFactory implements HumanFactory {//生产出黑人女性public Human createBlackHuman() {return new FemaleBlackHuman();}//生产出白人女性public Human createWhiteHuman() {return new FemaleWhiteHuman();}//生产出黄人女性public Human createYellowHuman() {return new FemaleYellowHuman();}
}
f)Main类
public class NvWa {public static void main(String[] args) {//第一条生产线,男性生产线HumanFactory maleHumanFactory = new MaleFactory();//第二条生产线,女性生产线HumanFactory femaleHumanFactory = new FemaleFactory();//生产线建立完毕,开始生产人了:Human maleYellowHuman = maleHumanFactory.createYellowHuman();Human femaleYellowHuman = femaleHumanFactory.createYellowHuman();System.out.println("---生产一个黄色女性---");femaleYellowHuman.getColor();femaleYellowHuman.talk();femaleYellowHuman.getSex();System.out.println("\n---生产一个黄色男性---");maleYellowHuman.getColor();maleYellowHuman.talk();maleYellowHuman.getSex();/** .....* 后面继续创建不同人种的男性,女性*/}
}
3、优点和适合场景
1)小总结(★★★★★)
这里以女娲造男女人来解释抽象工厂方法(相比于工厂模式,抽象工厂模式有多条生产线,这里使为了让男女之间产生爱情,因此产品是男女,男女之间具有相关性,两个产品由最终的两个工厂实现类创建)
- 有
N
个产品(男/女)则有N
个产品实体类; - 每个产品中有
M
个族(黑/白/黄人,产品族为3)则有M
个抽象产品类(不同族有不同语言); - 有
N
个产品(男/女)则有N
个工厂实现类;(不同工厂实现类对应不同的产品生产线) - 有
M
个产品族(黑/白/黄人)则有M
个抽象工厂方法(1个抽象工厂类); - 在
N
个工厂实现类中,每个工厂类可以生产1个产品族(黑男/黑女)
更加简化的抽象工厂类图如下所示:
- 抽象产品类和抽象工厂类绑定
- 产品实现类和工厂实现类绑定
2)优缺点(★★★)
a)优点
- 封装性:每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是抽象
- 产品族内的约束为非公开状态:如果女娲在造人的时候在产品族内增加了约束,比如为了**“满足白人出生率低,黄种人出生率高“的约束**,假设生产出来的男人在黑人和黄种人的占比为1:1,则在生产出的女人在白人和黄种人的占比可以配置为1:10。这个生产过程对调用工厂类的高层模块是透明的(只是举个例子,不提倡一夫多妻)
b)缺点
抽象工厂模式的最大缺点是产品族扩展非常困难,但对于产品扩展是非常方便的;
- 如果在产品中加入一个新的产品(中性人
intersex
),只需要增加一个关于中性人的工厂实体类即可; - 如果增加一个新的产品族(蓝人
BlueHuman
),则需要在抽象工厂类中增加一个新的工厂方法(createBlueHuman()
),相应在每个工厂实体类(黑/白/黄人)中去实现抽象工厂类增加的createBlueHuman()
;这一点违背了开闭原则(修改无法关闭)
因此抽象工厂模式适合于横向扩展(增加产品),不适合于纵向扩展(增加产品族)
3)应用场景(★★★★)
抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。
比如一个文本编辑器和一个图片处理器都是软件实体,但是Unix
下的文本编辑器和Windows
下的文本编辑器虽然功能和界面都相
同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条件:操作系统类型(造人的共同约束条件是人的性别)。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器,参考类图如下:
Note:不同操作系统的产品分为两个产品,每个系统产品中包含文本编辑器,图片处理器,即为两个产品族。
四、生成器模式
1、需求假设
A公司与汽车模型制造商B签了一个合同,将奔驰和宝马的车辆模型交给B去做,并额外增加了一个新的需求:汽车的启动、停止、喇叭声都由客户A自己控制。其中奔驰模型A是先有引擎声音,然后再响喇叭;奔驰模型B是先启动起来,然后再有引擎声音。
2、生成器模式的定义和实现
1)定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2)类图(★★★★)
Note:
- 生产出的N多个奔驰和宝马车辆模型都有
run()
方法,但是具体到每一个模型的run()
方法中间的执行任务的顺序是不同的,A说要啥顺序,B给啥顺序。 - 在
CarModel
中定义的setSequence
方法,客户A可以通过该方法配置要执行的顺序,run()
则会根据sequence
定义的顺序完成指定的顺序动作。
3)可见性分析(★★★)
- 用户虽然关心车辆模型是否支持对基本动作(
start
、stop
、alarm
)顺序的手动设置(setSequence
),以及车辆整体的表现结果(run
),但实际关心的是最终得到的不同类型的产品(getABenzModel()
),所以对于setSequence
和run
可以由建造者来完成,而通过导演类为用户提供一个统一的接口getABenzModel()
。 - 一个建造者对应一个用户的需求,对应一个车辆模型的组装顺序,为了给用户提供一个统一的接口,这里将所有建造者统一用导演类来管理。
- 对于抽象类中的
run()
,为了防止实现类中对其进行重写,需要对其使用final
修饰。 - 对于抽象类的基本方法,用户不可见但实现类可见,因此用
protected
修饰。
4)代码实现
a)车辆模型抽象类
public abstract class CarModel {//这个参数是各个基本方法执行的顺序private ArrayList<String> sequence = new ArrayList<String>();//模型是启动开始跑了protected abstract void start();//能发动,那还要能停下来,那才是真本事protected abstract void stop();//喇叭会出声音,是滴滴叫,还是哔哔叫protected abstract void alarm();//引擎会轰隆隆地响,不响那是假的protected abstract void engineBoom();//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑final public void run() {//循环一边,谁在前,就先执行谁for(int i=0;i<this.sequence.size();i++){String actionName = this.sequence.get(i);if(actionName.equalsIgnoreCase("start")){this.start(); //开启汽车}else if(actionName.equalsIgnoreCase("stop")){this.stop(); //停止汽车}else if(actionName.equalsIgnoreCase("alarm")){this.alarm(); //喇叭开始叫了}else if(actionName.equalsIgnoreCase("engine boom")){ //如果是engine boom关键字this.engineBoom(); //引擎开始轰鸣}}}//把传递过来的值传递到类内final public void setSequence(ArrayList<String> sequence){this.sequence = sequence;}
}
b)奔驰和宝马模型
//Benz
public class BenzModel extends CarModel {protected void alarm() {System.out.println("奔驰车的喇叭声音是这个样子的...");}protected void engineBoom() {System.out.println("奔驰车的引擎室这个声音的...");}protected void start() {System.out.println("奔驰车跑起来是这个样子的...");}protected void stop() {System.out.println("奔驰车应该这样停车...");}
}//BMW
public class BMWModel extends CarModel {protected void alarm() {System.out.println("宝马车的喇叭声音是这个样子的...");}protected void engineBoom() {System.out.println("宝马车的引擎室这个声音的...");}protected void start() {System.out.println("宝马车跑起来是这个样子的...");}protected void stop() {System.out.println("宝马车应该这样停车...");}
}
c)Main类生产BMW模型
public class Main {public static void main(String[] args) {/** 客户告诉XX公司,我要这样一个模型,然后XX公司就告诉我老大* 说要这样一个模型,这样一个顺序,然后我就来制造*/BenzModel benz = new BenzModel();//存放run的顺序ArrayList<String> sequence = new ArrayList<String>();sequence.add("engine boom"); //客户要求,run的时候时候先发动引擎sequence.add("start"); //启动起来sequence.add("stop"); //开了一段就停下来//我们把这个顺序赋予奔驰车benz.setSequence(sequence);benz.run();}
}
d)抽象汽车组装类(使用建造者管理Main类,为用户提供统一的接口)
public abstract class CarBuilder {//建造一个模型,你要给我一个顺序要,就是组装顺序public abstract void setSequence(ArrayList<String> sequence);//设置完毕顺序后,就可以直接拿到这个车辆模型public abstract CarModel getCarModel();
}
e)车模型组装者
//Benz
public class BenzBuilder extends CarBuilder {private BenzModel benz = new BenzModel();public CarModel getCarModel() {return this.benz;}public void setSequence(ArrayList<String> sequence) {this.benz.setSequence(sequence);}
}//BMW
public class BMWBuilder extends CarBuilder {private BMWModel bmw = new BMWModel();public CarModel getCarModel() {return this.bmw;}public void setSequence(ArrayList<String> sequence) {this.bmw.setSequence(sequence);}
}
f)导演类
public class Director {private ArrayList<String> sequence = new ArrayList();private BenzBuilder benzBuilder = new BenzBuilder();private BMWBuilder bmwBuilder = new BMWBuilder();/** A类型的奔驰车模型,先start,然后stop,其他什么引擎了,喇叭一概没有*/public BenzModel getABenzModel(){//清理场景,这里是一些初级程序员不注意的地方this.sequence.clear();//这只ABenzModel的执行顺序this.sequence.add("start");this.sequence.add("stop");//按照顺序返回一个奔驰车this.benzBuilder.setSequence(this.sequence);return (BenzModel)this.benzBuilder.getCarModel();}/** B型号的奔驰车模型,是先发动引擎,然后启动,然后停止,没有喇叭*/public BenzModel getBBenzModel(){this.sequence.clear();this.sequence.add("engine boom");this.sequence.add("start");this.sequence.add("stop");this.benzBuilder.setSequence(this.sequence);return (BenzModel)this.benzBuilder.getCarModel();}...
}
Note:由于一个建造者对应一个需求,如果每个建造者都和客户绑定耦合度太高,所以可以使用导演类为用户提供一个统一的接口。
3、优点和适用场景
1)优点
- 封装性:使用建造者模式可以使客户端不必知道产品内部组成的细节,如例子中我们就不需要关心每一个具体的模型内部是如何实现的,产生的对象类型就是
CarModel
- 不同建造者独立,容易扩展。
2)应用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使
用该模式。 - 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模
式非常合适。 - 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易
得到时,也可以采用建造者模式封装该对象的创建过程。
3)建造者和工厂方法的区别(★★★★★)
- 建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同;
- 工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的
五、原型模式
Note:原型模式和单例模式很好理解,这里简单介绍
1、需求假设
实现个性化电子账单,为每个客户发送个性化的邮件,每个邮件中包含每个客户的个人信息。
2、定义和实现
定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
类图:
如果AdvTemplate
是从数据库中读取到的电子账单模板,Mail
是一封邮件类,发送邮件时对该对象进行操作。
如果采用单线程发送,按照一封邮件发出去需要0.02秒(够小了,你还要到数据库中取数据呢),600万封邮件需要33个小时。
如果采用多线程发送,在产生第一封邮件对象,放到线程1中运行,还没有发送出去;线程2也启动了,直接就把邮件对象mail
的收件人地址和称谓修改掉了,线程不安全了。
因此有两种解决方法:
new
一个新的mail
对象- 对象的复制(
clone
):如果有对象之间存在很多公共的信息,clone
效率比new
高。
因此类图修正如下:
参考java重写clone()方法
1)浅拷贝
java浅克隆,即在实现Clonable
接口时,重写Object
的clone
方法,并转化成子类类型对象(Person p = (Person) super.clone()
),但如果子类对象中有引用类型(Address
),新clone
的对象p
与原型对象所指向的Address
对象是相同的。
public class Person{private Address address;@Overrideprotected Person clone() {// throws CloneNotSupportedException 写到下面//return (Person)super.clone();Person p=null;try{p=(Person)super.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return p;}
}
2)深拷贝(★★★★)
java深克隆则要求所有Person
对象的所有引用类型都要重写clone
方法,比如 p=(Person)super.clone();p.address = address.clone();
public class Address{@Overrideprotected Address clone() {// throws CloneNotSupportedException 写到下面//return (Person)super.clone();Addressaddr=null;try{addr=(Address)super.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return addr;}
}public class Person{private Address address;@Overrideprotected Person clone() {// throws CloneNotSupportedException 写到下面//return (Person)super.clone();Person p=null;try{p=(Person)super.clone();p.address = address.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return p;}
}
3、应用场景(★★★★)
原型模式的核心是一个clone
方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable
接口来标示这个对象是可拷贝的,Cloneable
接口只是一个标记作用,在JVM
中具有这个标记的对象才有可能被拷贝。
1)优点:
- 性能优良:原型模式是在内存二进制流的拷贝,要比直接
new
一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 - 在调用
clone()
方法时,JVM
会根据这个标识在堆内存中以二进制的方式拷贝对象,重新分配一个内存块,并没有执行构造函数(new
)
2)应用场景
- 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;
- 性能和安全要求的场景:通过
new
产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式; - 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时可以考虑使用
原型模式拷贝多个对象供调用者使用(JMM模型,volatile关键字)。 - 一般是和工厂方法模式一起出现,通过
clone
的方法创建一个对象,然后由工厂方法提供给调用者。
3)注意事项
- 构造函数不会被执行
- 深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现(这个场景不是很明白??)。
- 对象的
clone
与对象内的final
关键字是有冲
突的,原因是final
类型是无法赋值的,所以无法通过clone
之后对其进行修改。因此要使用clone方
法,在类的成员变量上就不要增加final关键字
创建型设计模式(待更新)相关推荐
- Java设计模式(二)创建型设计模式
文章目录 三 创建型设计模式 3.1 单例设计模式 3.1.1 饿汉式(线程安全) 3.1.2 懒汉式(线程不安全) 3.1.3 优缺点 3.1.4 补充 3.1.5 框架中的使用 3.1.4.1 S ...
- 02.创建型设计模式实验
写在前面 在网上抄个作业居然要不没有,要不要钱!老子有个屁的钱,气得我直接写完了,分享在这里供借鉴(抄可以,但你得会了再抄).随便转载,可以不署我名但需要声明非原创. 这是一系列的作业,所以也气了我一 ...
- 技术图文:02 创建型设计模式(下)
创建型设计模式(下) 知识结构: 图1 知识结构 单例模式 – 确保对象的唯一性 Sunny 软件公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量 ...
- 技术图文:02 创建型设计模式(上)
创建型设计模式(上) 知识结构: 图1 知识结构 简单工厂模式 Sunny 软件公司欲基于 C# 语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,如: 柱状图(histogram) ...
- 创建型设计模式对比总结 设计模式(八)
创建型模式是new 的一种替代方式,可以将对象的创建与具体的类型进行分离 目前已经介绍了5种创建型设计模式(如果简单工厂算一种的话,那就是6种) 分别是: 简单工厂模式.工厂方法模式.抽象工厂模式.建 ...
- java面向对象程序设计第三版_JAVA面向对象程序设计之创建型设计模式
[本文详细介绍了JAVA面向对象程序设计中的创建型设计模式,欢迎读者朋友们阅读.转发和收藏!] 1 基本概念 1.1 什么是设计模式 设计模式( Design pattern )是一套被反复使用.多数 ...
- 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 第1章 Python的创建型设计模式...
本节书摘来自华章出版社<Python编程实战:运用设计模式.并发和程序库创建高质量程序>一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云 ...
- 从框架源码中学习创建型设计模式
文章目录 从框架源码中解读创建型设计模式 工厂模式 案例一:RocketMQ源码-创建Producer生产者 案例二:RocketMQ源码-创建过滤器工厂 抽象工厂 案例一:Dubbo源码-创建缓存的 ...
- java设计模式之创建型设计模式
创建型设计模式: 抽象工厂(Abstract Factory): 用途:提供一个接口以创建一系列相关或相互依赖的对象,而无需指定具体的类. 场景: 一个系统要独立于它的产品的创建. 一个系统要由多个产 ...
最新文章
- JavaScript的语言标准
- 获取MySQL数据库表结构的信息(字段名,字段类型,注释等)
- 阿里文娱测试开发专家谈《算法基石:实时数据质量如何保障?》
- 手环是如何测试人体健康数据?
- 表面配准论文1--基于高阶图匹配方法的稠密表面配准
- LeetCode 1028. 从先序遍历还原二叉树(栈)
- h5 nan_手把手教你将H5游戏打包成快游戏
- 2018最火机器学习项目盘点—CV项目领冠榜单
- 计算机视觉空间域(spatial)注意力机制——CBAM
- vsftp不同帐号的目录和权限
- Python实现图像信息隐藏
- Atitit 运维之道 v2 s09.docx Atitit 运维之道 目录 1. 概念	2 1.1. devops算是最低门槛了。什么运维平台,搞来搞去也就那些东西。无外乎cmdb、部署、监
- 常用测试用例设计方法
- http请求过程:一,DNS域名解析系统详解
- 对数回归 matlab,高斯过程回归GPR-MATLAB语法解释
- Ps快捷键及基础知识
- 企业该如何做好IT规划
- ubuntu ibus拼音异常
- 计算机考研复试-计算机组成原理
- Discom BKS03/KS91D传感器