-
一 分类
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式可以分为三类:1)简单工厂模式(Simple Factory)
2)工厂方法模式(Factory Method) 3)抽象工厂模式(Abstract Factory)工厂模式,也叫做说虚构造器,在简单工厂中间插入了一个具体产品工厂,这个工厂知道产品构造时候的具体细节,而简单工厂模式的产品具体构造细节是在一个个 if/else分支,或者在switch/case分支里面的。工厂模式的好处就在于将工厂和产品之间的耦合降低,将具体产品的构造过程放在了具体工厂类 里面。在以后扩展产品的时候方便很多,只需要添加一个工厂类,一个产品类,就能方便的添加产品,而不需要修改原有的代码。而在简单工厂中,如果要增加一个 产品,则需要修改工厂类,增加if/else分支,或者增加一个case分支,工厂模式符合软件开发中的OCP原则(open close principle),对扩展开放,对修改关闭。
要解耦就必须使调用者和被调用者之间没有直接的联系就是界于两者之间起纽带作用。调用者只与工厂联系,被调用者也只与工厂联系。调用者与被调用者这间没有直接联系。一个小孩想要一个工厂生产玩具并把玩具卖给小孩玩具有猫\狗\熊等等工厂根据小孩的喜好,性别,年龄生产出专门针对小孩的玩具。这时,我们说,小孩是调用者,玩具生产工人和玩具是被调用者。 小孩无法直接与工厂的工人联系,但中间商人和工厂帮助小孩找到想到的玩具。这就达到了解耦
-
二 为什么要使用工厂类?
我们经常一些功能类似的类,所以我们的思路是对进行抽象,使用接口暴露公共的方法,使用抽象类来提供公共的实现。
它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.
当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory
类似于在代码层面上面的反向代理一样;
比如:
日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
访问类:访问可能是SQLSERVER、等,用记可以选择创建访问不同的数据库。
-
三 代码分析
以下为不同模式的代码及原因分析,以生产宝马汽车为例,
不用工厂模式时代码如下:
第 1 个问题:
我们经常一些功能类似的类,所以我们的思路是对进行抽象,使用接口暴露公共的方法,使用抽象类来提供公共的实现。
参考 知乎 郭小成的回答 https://www.zhihu.com/question/24843188?sort=created
abstract class BMW { public BMW(){ } // 这是要暴露的方法} public class BMW320 { public BMW320(){ System.out.println("制造-->BMW320"); } } public class BMW523 { public BMW523(){ System.out.println("制造-->BMW523"); } } public class Customer { public static void main(String[] args) { BMW320 bmw320 = new BMW320(); BMW523 bmw523 = new BMW523(); } }
第2个问题:
这些功能类似的类的实例化成为了一个问题,每个类的构造方法参数还不一样,每次 new 对象很麻烦,封装成简单工厂模式。
这种情况就是把具体的实现类写死了;如果不确定到底要实现哪种类呢?每次都要改代码new对象,这是不符合需求的;
那么想到了一种简单方法,就是不写死,通过传参来实现,这就是简单工厂
简单工厂又称为静态工厂
// 产品代码abstract class BMW { public BMW(){ } } public class BMW320 extends BMW { public BMW320() { System.out.println("制造-->BMW320"); } } public class BMW523 extends BMW{ public BMW523(){ System.out.println("制造-->BMW523"); } } ----------// 工厂代码public class Factory { public BMW createBMW(int type) { switch (type) { case 320: return new BMW320(); case 523: return new BMW523(); default: break; } return null; } } -------------// 客户端代码public class Customer { public static void main(String[] args) { Factory factory = new Factory(); BMW bmw320 = factory.createBMW(320); BMW bmw523 = factory.createBMW(523); } }
第3个问题
简单工厂模式不利于拓展,违背了**开闭原则**,每次添加一个类,都要修改工厂类(如果是工厂类和业务类是两个小伙伴分开写的,那不是要花很多时间来沟通...),所以就有工厂方法模式,其原理就是对简单工厂也进行抽象。
这里有一个很严重的问题,就是如果想再加一种宝马型号,就需要修改工厂类的代码了,再添加一个switch,这样就不符合“开闭原则”,即只增加,不修改,这样不利于扩展;
于是再对这种共产模式进行抽象,通过继承的方式进行抽象
这种模式就是工厂方法模式
// 产品代码abstract class BMW { public BMW(){ } } public class BMW320 extends BMW { public BMW320() { System.out.println("制造-->BMW320"); } } public class BMW523 extends BMW{ public BMW523(){ System.out.println("制造-->BMW523"); } } --------// 工厂代码interface FactoryBMW { BMW createBMW(); } public class FactoryBMW320 implements FactoryBMW{ @Override public BMW320 createBMW() { return new BMW320(); } } public class FactoryBMW523 implements FactoryBMW { @Override public BMW523 createBMW() { return new BMW523(); } } ------------//客户端代码public class Customer { public static void main(String[] args) { FactoryBMW320 factoryBMW320 = new FactoryBMW320(); BMW320 bmw320 = factoryBMW320.createBMW(); FactoryBMW523 factoryBMW523 = new FactoryBMW523(); BMW523 bmw523 = factoryBMW523.createBMW(); } }
第4个问题
因为代码变得很多了
突然发现有些糟糕了,因为代码变得很多了,因为功能类似的产品我们进行 3 层抽象,针对每个产品我们还抽象出了 2 层的工厂类。但是我们在某个具体的业务场景中,不单单是只实例化一个类啊。举一个例子,在游戏中,我们要一个战士配装备,首先我们需要配一把枪械(枪械有很多,步枪,狙击枪等,使用问题 1 进行抽象),但是配了枪械之后,我们还需要配子弹啊(继续使用问题 1 的方法进行抽象),好了,现在可以抽象出 2 层的工厂类了,针对现在的情况我们是不是可以让一个工厂既生产枪械,又生产子弹呢? 这就是抽象工厂模式。简单来说,可以把有一些有联系或者相近的产品,放到一个工厂去生产,没有必要单独再开一个工厂了。
作者:郭小成 链接:https://www.zhihu.com/question/24843188/answer/110361396 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
比如说一个产品由一套组件组合,那么这一套组合就可以组成一个工厂了;
//发动机以及型号 public interface Engine { } public class EngineA extends Engine{ public EngineA(){ System.out.println("制造-->EngineA"); } } public class EngineBextends Engine{ public EngineB(){ System.out.println("制造-->EngineB"); } } //空调以及型号 public interface Aircondition { } public class AirconditionA extends Aircondition{ public AirconditionA(){ System.out.println("制造-->AirconditionA"); } } public class AirconditionB extends Aircondition{ public AirconditionB(){ System.out.println("制造-->AirconditionB"); } } //创建工厂的接口 public interface AbstractFactory { //制造发动机 public Engine createEngine(); //制造空调 public Aircondition createAircondition(); } //为宝马320系列生产配件 public class FactoryBMW320 implements AbstractFactory{ @Override public Engine createEngine() { return new EngineA(); } @Override public Aircondition createAircondition() { return new AirconditionA(); } } //宝马523系列 public class FactoryBMW523 implements AbstractFactory { @Override public Engine createEngine() { return new EngineB(); } @Override public Aircondition createAircondition() { return new AirconditionB(); } } public class Customer { public static void main(String[] args){ //生产宝马320系列配件 FactoryBMW320 factoryBMW320 = new FactoryBMW320(); factoryBMW320.createEngine(); factoryBMW320.createAircondition(); //生产宝马523系列配件 FactoryBMW523 factoryBMW523 = new FactoryBMW523(); factoryBMW320.createEngine(); factoryBMW320.createAircondition(); } }
四、抽象工厂模式的起源
下面引用一段的起源:
抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构建,在UNIX操作系统的视窗环境和Windows操作系统的视窗环境中,这两个构建有不同的本地实现,它们的细节有所不同。
在每一个操作系统中,都有一个视窗构建组成的构建家族。在这里就是Button和Text组成的产品族。而每一个视窗构件都构成自己的等级结构,由一个抽象角色给出抽象的功能描述,而由具体子类给出不同操作系统下的具体实现。
可以发现在上面的产品类图中,有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是UNIX产品族和Windows产品族。UNIX产品族由UNIX Button和UNIX Text产品构成;而Windows产品族由Windows Button和Windows Text产品构成。
系统对产品对象的创建需求由一个工程的等级结构满足,其中有两个具体工程角色,即UnixFactory和WindowsFactory。UnixFactory对象负责创建Unix产品族中的产品,而WindowsFactory对象负责创建Windows产品族中的产品。这就是抽象工厂模式的应用,抽象工厂模式的解决方案如下图:
显然,一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。
在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能消费某一个产品族了。
五、总结:
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。
所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。
六、应用
其实工厂的模式主要目的还是为了在创建目标对象的时候调用目标方法时可以和目标彻底的解耦,不管是是因为什么原因,目的就是为了在底层的结构或者框架变动后,上层不会有任何的代码改动,以及上层很多的调用者都不用管;做到彻底的解耦合;
方法就是相当于通过一个代理来代表他,以后所有事情都找代理,有点类似于反向代理的概念;
因为代理偷偷摸摸把代理的对象换了,前端是不知道的;是透明的;然后前端只看得到代理;并看不到代理的对象是谁,以及和前端想象的是否一样,都不重要;如此依赖,代理后面的世界的变化就可以灵活多变了,这样也永远不会影响到前端;
一般会借助单例模式和反射来实现,其实这个工厂模式和那种微服务也有些类似的相同的思想;
微服务的目的,其实就是为了尽可能的解耦,保证高可用还有可扩展的特性;
Class.forName(LoadProperty.getClassName(Constants.LOGINDAOIMPL)).newInstance();
七、延伸
其实这个工厂模式的核心,就是体现了面向接口编程,
无非就是第一遍对产品抽象,第二遍对产品抽象变成了简单工厂,直接在工厂里面对实例化方法私有,类似单例模式(getInstance方法);
第三遍直接对这个获取实例的方法也抽象;第四遍,就是把类似的同一家族放在一个工厂里面;
但是这里面有一个很严重的问题,就是不完全的面向接口编程,虽然彻底的和被调用者解耦合了;
但是对于工厂却没有做到彻底的解耦合,只不过是换了一个代理而已;因为你最终还是得要new一个实现者的类作为参数传进去或者直接做实例干活;
那如何做到彻底的完全的面向接口编程呢?
可以借用spring的 ioc和di的思想 使用反射的方法来实现,把要写死的实现类的调用全部换成接口,然后通过注入(依赖)的方式把反射出来的实例给传进去,就可以彻底的解耦和了,或者直接借用spring也是不错的;
这样就可以彻底的完美的实现了完全的面向接口编程了;
当然,如果需要不面向接口编程,然后把实例的方式用反射的方式传入实例;
Class.forName("com......");
最后也可以通过在配置文件里面修改具体要用那个实现类来实现具体方法了;只需要在配置文件里面把实例化的那个类配上即可;
但是,这里就又存在一个问题了,就是反射的性能问题,然后还有一个就是内部暴露的问题了;
这里就应该综合来考虑问题了;