一、工厂模式(Factory Method)
工厂方法模式:工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。(在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行)
为什么要用工厂模式:
1)解耦 :把对象的创建和使用的过程分开
2)降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
3)降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
在开源框架中的使用
举两个比较常见的例子(暂时想到的,当然还有很多很多):
1)Spring中通过getBean("xxx")获取Bean;
2) Java消息服务JMS中(下面以消息队列ActiveMQ为例子)
工厂模式分为简单工厂模式、工厂模式以及抽象工厂模式三种。
1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern)。
2)工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式;
3)抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。
一、简单工厂模式:
适用场景
1)需要创建的对象较少。
2)客户端不关心对象的创建过程。
实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。
对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
具体实现如下:
1. 定义一个操作接口:
2. 定义具体的操作类
3. 定义简单工厂类:
4. 用户端代码:
Result:
我们无需提供具体的子类类名,只需要提供一个字符串即可得到相应的实例对象。
这样的话,当子类的类名更换或者增加子类时我们都无需修改客户端代码,只需要在简单工厂类上增加一个分支判断代码即可。
使用这种模式,我们在生成工厂的时候可以加一些业务代码,如日志、判断业务等,这时候可以直接在switch case中加上去就行了,如下:
这样做的优点:
我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。
如果,没有工厂的话,那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。
但是缺点也很明显:
如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
这时候就需要使用工厂模式了。
二、工厂模式
适用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
比较:
工厂模式中,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。
你想要加功能,本来是改工厂类的,而现在是修改客户端。
而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。
工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。
但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。
三、抽象工厂模式:
适用场景
- 和工厂方法一样客户端不需要知道它所创建的对象的类。
- 需要一组对象共同完成某种功能时,并且可能存在多组对象完成不同功能的情况。(同属于同一个产品族的产品)
- 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)
场景:对数据库中的表进行修改
此时,使用工厂模式结构图如下:
到此为止,工厂模式都可以很好的解决,由于多态的关系,sqlFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了:
所以抽象工厂与工厂方法模式的区别在于:
抽象工厂是可以生产多个产品的,例如 MysqlFactory 里可以生产 MysqlUser 以及 MysqlLogin 两个产品,而这两个产品又是属于一个系列的,因为它们都是属于MySQL数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 MysqlFactory 里就只可以生产一个 MysqlUser 产品。
抽象工厂模式的优缺点:
优点:
1. 抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory=new OracleFactory();
在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,
例如我现在要更改以上代码的数据库访问时,只需要更改具体的工厂即可。
2. 抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,
产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。
就像我们上面的例子,客户端只认识IUser和ILogin,至于它是MySQl里的表还是Oracle里的表就不知道了。
缺点:
1. 如果你的需求来自增加功能,比如增加Login表,就有点太烦了。首先需要增加 ILogin,mysqlLogin,oracleLogin。
然后我们还要去修改工厂类: sqlFactory, mysqlFactory, oracleFactory 才可以实现,需要修改三个类,实在是有点麻烦。
2. 还有就是,客户端程序肯定不止一个,每次都需要声明sqlFactory factory=new MysqlFactory(), 如果有100个调用数据库的类,
就需要更改100次sqlFactory factory=new oracleFactory()。
四、抽象工厂模式的改进1。(简单工厂+抽象工厂)
我们将IFactory、MySQLFactory以及OracleFactory三个工厂类都抛弃掉,取而代之的是一个简单工厂类EasyFactory,如下:
由于事先在简单工厂类里设置好了db的值,所以简单工厂的方法都不需要由客户端来输入参数,这样在客户端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createLogin();
方法来获得具体的数据库访问类的实例,客户端代码上没有出现任何一个 MySQL 或 Oracle 的字样,达到了解耦的目的,客户端已经不再受改动数据库访问的影响了。
五、 抽象工厂的改进2(反射+简单工厂)
使用反射的话,我们就可以不需要使用switch,因为使用switch的话,我添加一个sql server数据库的话,又要switch的话又需要添加case条件。
我们可以根据 选择的数据库名称,如 “mysql”, 利用反射技术自动的获得所需要的实例:
以上我们使用简单工厂模式设计的代码中,是用一个字符串类型的db变量来存储数据库名称的,所以变量的值到底是 MySQL 还是 Oracle ,完全可以由事先设置的那个db变量来决定,而我们又可以通过反射来去获取实例,这样就可以去除switch语句了。
六、 抽象工厂的改进3(反射+配置文件+简单工厂)
在使用反射之后,我们还是需要进easyFactory中修改数据库类型,还不是完全符合开-闭原则。
我们可以通过配置文件来达到目的,每次通过读取配置文件来知道我们应该使用哪种数据库。
如下是一个json类型的配置文件,也可以使用xml类型的配置文件:
之后就可以通过这个配置文件去找需要加载的类是哪一个。
我们通过反射机制+配置文件+简单工厂模式解决了数据库访问时的可维护、可扩展的问题。