接着上一篇工厂模式,特此感谢,设计模式之禅的作者老师,没有这本书就没有这些笔记。
首先光说拓展,那你肯定要有个基础呀,你才能从这个基础上拓展开来:上通用的类图模板和通用的代码模板:
- 产品类的实例化工作(一个产品对象具体是由哪一个产品类生成)是由工厂类负责的
//抽象的产品类,定义一些或者说规定一些由(实现了抽象工厂的)具体工厂创造出来的产品们,必须具有的一些共性。当然里面也可以有非抽象方法,可以是一些公共方法供子实现类们使用
public abstract class Product{
......
//比如,都得是水果吧,不能你给我生产一个篮球出来,这总不好吧
public abstract void methodRule();
}
- 我具体要的产品可以有很多吧,这里以两个为例
//具体的产品类1,有籽西瓜
public class ConcreteProduct1 extends Product{
@Override
public void methodRule(){
//我是个有籽西瓜
......
}
}
//具体的产品类2,雕牌洗衣粉
public class ConcreteProduct2 extends Product{
@Override
public void methodRule(){
//我是个洗衣粉
......
}
}
…
抽象产品类和具体产品类完了,就该抽象工厂和具体工厂了
//抽象工厂类,指定一些具体工厂生产西瓜时所应该遵守的标准或者规则
public abstract class Creator{
......
//比如,你这里指定,生产西瓜必须先洗手,然后用敏小言设计的机器生产瓜......
public abstract <T extends Product> T createProduct(Class<T> c);
}
- 我具体实现了抽象工厂的用来生产产品的具体工厂可以有很多个或者很多种吧,这里举两个例子
//具体的工厂1
public class ConcreteCreator1 extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
......
}
}
//具体的工厂2
public class ConcreteCreator2 extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
......
}
}
好了,按照类图模板,四个部分的代码就是上面,下来比如说我作为用户调用就是这样的:
...
//搞两个具体工厂出来,一个生产西瓜,一个生产洗衣粉,是面向接口编程吧
Creator create = new ConcreteCreator1();
Creator create = new ConcreteCreator2();
......
Product xiGua = creator.createProduct(ConcreteProduct1.class);
......
Product xiYiFen = creator.createProduct(ConcreteProduct2.class);
......
可以看出,在想要增加产品类时我只需要适当的修改具体的工厂类或者说新增一个新的或者多个新的工厂类出来即可。造肥皂造香蕉随你便
然后呢,作者前辈举出了两个很好的例子,摘录一下:
- 比如在使用数据库的过程中,咱们Java这边一般底层就是用的是JDBC连接数据库嘛。如果我们很多时候MySQL用的好好的突然上级指示换成PostgresSQL,那么咱们在使用的是标准SQL的前提下只需改动一下驱动名称而其他几步就不需要更改了
- 例如需要设计一个链接邮件服务器的框架,需求方或者客户说有三种网络协议共咱们选择,HTTP、PTTH、JQK,那咱们是不是可以把这三种连接方法作为具体的产品类,定义一个自己的IConnectMail接口,然后里面定义好对邮件的抽象操作方法;然后我们就可以用具体的产品类去(不同的方法)实现三个具体的产品类
- 再定义一个工厂方法,按照不同的传入条件去选择不同的连接方式。当比如某些邮件服务器又提供了XXX接口,我们就只需要增加一个产品类就行了
另外,工厂模式我个人觉得可以从两个维度去拓展开来:【工厂模式封装了对象创建的过程,使用者不需要关心对象创建的细节。在需要生成复杂对象的场景下,都可以使用工厂模式实现。工厂模式分为三种:简单工厂模式、工厂方法模式和抽象工厂模式
。】
- 第一个维度:
- 缩小为
简单工厂模式或者叫静态工厂模式
:简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象
。
- 简单工厂模式。
定义一个工厂类,根据参数类型返回不同类型的实例。适用于对象实例类型不多的场景
,如果对象实例类型太多,每增加一种类型就要在工厂类中增加相应的创建逻辑,这是违背开放封闭原则的。
- 简单工厂模式。
- 增加为多个工厂类:因为我们往往需要使用两个或两个以上的工厂,所以简单工厂是不够的。其实就是我感觉就是多个简单工厂的叠加。
- 既然大家都可以创建实例或者说进行实例化,那么我工厂模式是不是可以和你单例模式抢饭碗
- 延迟初始化
- 缩小为
- 第二个维度:升级为抽象工厂模式:
- 比如,如果咱们要造一台电脑,不使用抽象工厂模式,实现的话因为电脑是由许多的构件组成的,
我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起
。此时也比较容易扩展,要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可
,不需要修改现有的工厂。但是,这种方式有一个问题,那就是如果 Intel 家产的 CPU 和 AMD 产的主板不能兼容使用
,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。- 工厂方法模式。简单工厂模式的升级版,不再是提供一个统一的工厂类来创建所有对象的实例,
而是每种类型的对象实例都对应不同的工厂类,每个具体的工厂类只能创建一个类型的对象实例。
- 抽象工厂模式。较少使用,适用于创建多个产品的场景。如果按照工厂方法模式的实现思路,需要在具体工厂类中实现多个工厂方法,是非常不友好的。抽象工厂模式就是把这些工厂方法单独剥离到抽象工厂类中,然后创建工厂对象并通过组合的方式来获取工厂方法。
- 工厂方法模式。简单工厂模式的升级版,不再是提供一个统一的工厂类来创建所有对象的实例,
- 比如,如果咱们要造一台电脑,使用抽象工厂模式,实现的话就是我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的
。- 当然,
抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则
。
- 当然,
- 比如,如果咱们要造一台电脑,不使用抽象工厂模式,实现的话因为电脑是由许多的构件组成的,
具体例子如下:
- 第一个维度:缩小为简单工厂模式(静态工厂模式)。
就是当我们生产东西只会用到一种工厂时,那咱们为啥还要先搞一个抽象工厂定义好写有具体工厂们需要遵守的规则的抽象方法们,然后再由具体工厂实现抽象工厂呢,就一个就用个静态方法就可以了。
public class XiGuaFactory{
public static <T extands XiGua> T CreateHuman(Class<T> c){
//定义一个生产出的人种
XiGua xiGua = null;
try {
//产生一个人种
human = (XiGua)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
System.out.println("西瓜生成错误!");
}
return (T)xiGua;
}
}
下来比如说我作为用户调用就是这样的:
public static void main(String[] args){
//第一次生产有籽西瓜
...
XiGua youZiXiGua = XiGuaFactory.createHuman(YouZiXiGua.class);
......
XiGua wuZiXiGua = XiGuaFactory.createHuman(WuZiXiGua.class);
......
}
当然啦,这种简单工厂模式或者说静态工厂模式的缺点也显而易见,没有一个抽象工厂来管理底下很多子实现具体工厂,咱们后期要扩展出很多个或者很多种具体的工厂就很难了,但是这种简单工厂或者静态工厂确实好用。
- 第一个维度:升级为多个工厂类。
public abstract class AbstractFactory{
public abstract X createXxx();
}
public class WuZiXiGuaFactory extends AbstractFactory{
public WuZiXiGua createXiGua(){
return new WuZiXiGua();
}
}
public class YouZiXiGuaFactory extends AbstractFactory{
public YouZiXiGua createXiGua(){
return new YouZiXiGua();
}
}
public class XiYiFenFactory extends AbstractFactory{
public XiYiFen createXiYiFen(){
return new XiYiFen();
}
}
下来比如说我作为用户调用就是这样的:
public static void main(String[] args){
//第一次生产有籽西瓜
...
XiGua youZiXiGua = (new WuZiXiGuaFactory()).createXiGua();
......
XiGua wouZiXiGua = (new WuZiXiGuaFactory()).createXiGua();
......
XiYiFen xiYiFen = (new XiYiFenFactory()).createXiYiFen();
......
}
此时可扩展性也不太好,当咱们要扩展一个产品类时就得建立一个相应的工厂类(工厂类和产品类数量是一一对应的,所以咱们得实时考虑他俩之间的关系,就很难)。一般采用多工厂时会增加一个协调类(封装子工厂类,对高层模块提供统一的访问接口,又来一个大管家呗),避免调用者与各个子工厂交流
- 第一个维度:抢单例模式的饭碗(单例模式就是来保证在内存中只有一个对象),那咱们工厂方法模式如果也可以保证在内存中只生产一个对象,那么不就成功抢了饭碗了嘛
public class Singleton{
//把构造器的权限修饰符改为private
private Singleton(){
}
public void doSomething(){
......
}
}
//通过反射方式创建一个单例对象
public class SingletonFactory{
private static Singleton singleton;
static{
//通过反射方式创建一个单例对象
Class cl= Class.forName(Singleton.class.getName());
//获得无参构造
Constructor constructor=cl.getDeclaredConstructor();
//设置无参构造是可访问的
constructor.setAccessible(true);
//产生一个实例对象
singleton = (Singleton)constructor.newInstance();
}
public static Singleton getSingleton(){
return singleton;
}
}
- 第一个维度:延迟初始化(一个对象被消费完后并不立即释放,工厂类用来保持这个对象的初始状态等待再次被使用)
- 延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断 Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会 要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量
- 延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断 Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会 要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量
//ProductFactory负责产品类对象的创建工作,
public class ProductFactory {
//通过prMap变量产生一个缓存,对需要 再次被重用的对象保留
//通过定义一个Map容器,容纳所有产生的对象
private static final Map<String,Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception{
Product product =null;
//如果Map中已经有这个对象,如果在Map容器中已经有的对象,则直接取出返回
if(prMap.containsKey(type)){
product = prMap.get(type);
}else{ //如果没有,则根据需要的类型产生一个对象并放入到Map容 器中,以方便下次调用
if(type.equals("Product1")){
product = new ConcreteProduct1();
}else{
product = new ConcreteProduct2();
}
//同时把对象放到缓存容器中
prMap.put(type,product);
}
return product;
}
}
第二个维度:抽象模式工厂(Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们 的具体类。))
- 在场景类中,没有任何一个方法与实现类有关系,对于一个产品来说,我们只要知道它 的工厂方法就可以直接产生一个产品对象,无须关心它的实现类,只要知道工厂类是谁,接口、抽象类是谁就行,不用关心对象如何创建出来。
- 使用场景:
- 一个对象族(或是一组没有任何关系的对象) 都有相同的约束,则可以使用抽象工厂模式。(例如一个文本编辑器和一个图片
处理器,都是软件实体,但是linux下的文本编辑器和Windows下的文本编辑器虽然功能和界面都相同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条 件:操作系统类型。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片 处理器。).一句话,涉及到不同OS时就可以考虑使用抽象工厂模式(通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软 件功能、应用逻辑、UI都应该是非常类似的,唯一不同的是调用不同的工厂方法,由不同的 产品类去处理与操作系统交互的信息)
- 一个对象族(或是一组没有任何关系的对象) 都有相同的约束,则可以使用抽象工厂模式。(例如一个文本编辑器和一个图片
咱们之前的西瓜样式太单一了,只有籽之分,没颜色、没大小、没产地、没压成汁、没冰镇…。但是要从头开始就划不来了,那咱们就在上面工厂模式基础上改造改造呗
所以咱们的设计思路就是,
- 把对象的属性扩展扩展,多弄几个属性
- 把原来的生产所用的工厂拆开成为多种类型的工厂
程序其实就和咱们之前的一样,一个接口大管家下面管着好多抽象类,然后抽象类下面对应个很多实现类。 - 有N个产品族,在抽象工厂类中就应该有N个创建方法,如何创建一个产品,则是由具体的实现类来完成的。有M个产品等级就应该有M个实现工厂类,在每个实现工厂中,实现不同产品族 的生产任务。
- 但是抽象方法缺点在于,如果要增加一个产品C,也就是说产品家族由原来的2个增加到3个,看看我们的程序 有多大改动吧!抽象类AbstractCreator要增加一个方法createProductC(),然后两个实现类都要 修改。抽象类和接口一块要改…
- 除了上面自己编的,可能不太贴切的例子,还有俩例子如下:
- 经典:造车:
public class TSLAFactory implements CarFactory { @Override public Car createCar() { return new TSLA(); } } public class BMWFactory implements CarFactory { @Override public Car createCar() { return new BMW(); } }
- Netty 在创建 Channel 的时候使用的就是工厂方法模式,因为服务端和客户端的 Channel 是不一样的。
Netty 将反射和工厂方法模式结合在一起,只使用一个工厂类,然后根据传入的 Class 参数来构建出对应的 Channel,不需要再为每一种 Channel 类型创建一个工厂类
。【虽然通过反射技术可以有效地减少工厂类的数据量**,但是反射相比直接创建工厂类有性能损失,所以对于性能敏感的场景**,应当谨慎使用反射。】
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> { private final Constructor<? extends T> constructor; public ReflectiveChannelFactory(Class<? extends T> clazz) { ObjectUtil.checkNotNull(clazz, "clazz"); try { this.constructor = clazz.getConstructor(); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) + " does not have a public non-arg constructor", e); } } @Override public T newChannel() { try { return constructor.newInstance(); } catch (Throwable t) { throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t); } } @Override public String toString() { return StringUtil.simpleClassName(ReflectiveChannelFactory.class) + '(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)"; } }
- Netty 在创建 Channel 的时候使用的就是工厂方法模式,因为服务端和客户端的 Channel 是不一样的。
- 另外,可以看看书中的原图和原程序,很贴切:
- 经典:造车:
巨人的肩膀:
设计模式之禅
Head First 设计模式
https://javadoop.com/