浅谈简单工厂模式、工厂方法模式、抽象工厂模式
一、概述
简单说说对这几个设计模式的理解。名字相近,一几度弄不清这都是啥跟啥。
都叫xx工厂模式,顾名思义,工厂是要生产产品(在编程中就是类的实例对象)的。而这三种模式都是生产型模式,区别在于生产实例的方式、时机以及所能够达到编程原则约束、灵活性不同。
二、前情须知
上一段如何的最后一句话“所能够达到编程原则约束、灵活性不同”怎么理解呢?这还得说说编程原则的事。这里我们着重关注一下“开放封闭”原则,该原则强调要能够对现有软件进行功能上的进行拓展,但不允许或者尽量少修改原有代码。
那么,假如你是个有心人,这里应该就去思考——为什么要遵守这一原则呢?举一个简单的例子吧(ps:该例子参考程杰的《大话设计模式》里的故事)。假如小李负责维护你们公司的薪资管理系统,当前全公司员工的薪资计算方式主要是两种:实习生的薪资计算方式,正式员工的薪资计算方式。而现在领导要求小李给这个系统拓展功能,新增一种薪资计算方式——临时工的薪资计算方式。那么请问,假如小李心术不正或者一时糊涂,拿到系统源码后,在其中加入一段代码,以一种不合法的手段抬高了自己的薪资,那么……。所以,正确的做法是,当初设计该薪资管理系统时,让其符合开放封闭原则,当遇到需要新增薪资计算方式的时候,只要求小李写一个功能类放进去即可,而不至于非得把整个模块让他知道。这就是所谓的“能够对现有功能进行拓展,但不允许修改原有代码”。
说到编程原则,插一句。当初我懵懵懂懂,问我的领导——编程的规范、原则很多,但是咱们在做东西的时候也没记得都遵守了啊。领导是这样回答我的——在时间足够,人力足够,资金足够的条件下,我可以做得要多优雅有多优雅。但是现实情况往往是,时间、人力、资金等一系列资源都不够的情况下去搞开发,那就只能是有多少资源,做成什么样了。
三、从初学者Style说起
还以上述情景为例,如果一个初学者来编写这个薪资管理系统,那么八成会弄成这个样子:
package origin;
public class Salary {
public static void main(String[]args) {
// 员工身份
String identity = "实习生";
// 税前工资(实际情况值里应该是一个传入值)
int beforeTaxSalary = 17000;
// 经过“薪资管理系统”的计算得到实际薪资
double realSalary = calculateSalary(identity,beforeTaxSalary);
System.out.println(realSalary);
}
/**
* 根据员工身份和税前工资计算应发工资
*
* @param identity
* 员工身份(对应薪资计算方法)
* @param beforeTaxSalary
* 税前工资
* @return 实际工资
*/
private static double calculateSalary(Stringidentity, int beforeTaxSalary) {
// 如果是实习生
if (identity.equals("实习生")) {
// 实习生的薪资算法
return 0.8 *beforeTaxSalary;
} else if (identity.equals("正式员工")) {
return beforeTaxSalary;
}
return beforeTaxSalary;
}
}
观看上述“初学者Style”的代码,我们会发现,如果要进行功能拓展(比如新增一个临时工薪资计算方式),那么我们小将上述代码文件中的calculateSalary方法打开,然后在里面继续写else if语句。这就印证了我在文章的第二部分前情须知里说说的,万一让一个心术不正的人去开发维护拓展功能,那将是多么危险的一件事呢。
四、简单工厂模式遵守了开放原则
下面我们来看看简单工厂模式是怎么解决上述危险问题的。
简单工厂抽象出了每种薪资计算方式的主要功能——根据员工身份和税前薪资计算应发工资。所以将薪资计算方式都分割开来写在单独的文件中,这些文件都实现抽象即可。之后每次进行功能拓展,只要添加实现了该抽象的类文件即可。绘制成UML图就是下面这样:
Ps:该模式只遵循了开放原则,并不遵循封闭原则。
以下是代码实现:
package factory.simple;
/**
* 薪资计算
*
* @author lxp
*
*/
public abstract class AbstractSalaryCalculation {
/**
* 税前薪资
*/
private int beforeTaxSalary;
/**
* 薪资计算方法
*
* @return
*/
public abstract double calculateSalary();
public AbstractSalaryCalculation(int beforeTaxSalary) {
this.beforeTaxSalary =beforeTaxSalary;
}
public int getBeforeTaxSalary() {
return beforeTaxSalary;
}
public void setBeforeTaxSalary(int beforeTaxSalary) {
this.beforeTaxSalary =beforeTaxSalary;
}
}
package factory.simple;
/**
* 正式员工的薪资计算类
*
* @author lxp
*
*/
public class EmployeeCalculationextends AbstractSalaryCalculation {
public EmployeeCalculation(int beforeTaxSalary) {
super(beforeTaxSalary);
}
/**
* 正式员工的薪资计算方式
*/
@Override
public double calculateSalary() {
return this.getBeforeTaxSalary();
}
}
package factory.simple;
/**
* 实习生的薪资计算类
*
* @author lxp
*
*/
public class PracticeCalculationextends AbstractSalaryCalculation {
public PracticeCalculation(int beforeTaxSalary) {
super(beforeTaxSalary);
}
/**
* 实习生的薪资计算方式
*/
@Override
public double calculateSalary() {
return 0.8 *this.getBeforeTaxSalary();
}
}
package factory.simple;
/**
* 简单工厂,用来生成薪资计算方式实例。
*
* @author lxp
*
*/
public class SimpleFactory {
private Stringidentify;
public static AbstractSalaryCalculation getSalaryCalculation(Stringidentify, int beforeTaxSalary) {
// 此处不用switch,因为1.6以下不兼容。
AbstractSalaryCalculation salaryCalculation =null;
if (identify.equals("实习生")) {
salaryCalculation = new PracticeCalculation(beforeTaxSalary);
} else if (identify.equals("正式员工")) {
salaryCalculation = new EmployeeCalculation(beforeTaxSalary);
}
return salaryCalculation;
}
}
package factory.simple.test;
import factory.simple.AbstractSalaryCalculation;
import factory.simple.SimpleFactory;
public class TestSimpleFactory {
public static void main(String[] args) {
String practiceIdentify = "实习生";
String employeeIdentify = "正式员工";
int salary = 3000;
// 不同的身份,工厂创建的计算方式实例不同,结果也不一样。
AbstractSalaryCalculation salaryCalculation1 = SimpleFactory.getSalaryCalculation(practiceIdentify, salary);
System.out.println(salaryCalculation1.calculateSalary());
// 如果在有新的计算方式,只需添加实现了AbstractSalaryCalculation的类文件,然后修改主函数即可。
AbstractSalaryCalculation salaryCalculation2 = SimpleFactory.getSalaryCalculation(employeeIdentify, salary);
System.out.println(salaryCalculation2.calculateSalary());
}
}
五、工厂方法模式遵从了开放-封闭原则
在上一节中说道,简单工厂模式遵从了开放原则,但没有遵从封闭原则,不能算是真正意义上解决了我们之前提到的问题。那么接下来进化到工厂方法模式,它我们来看看工厂方法模式是如何彻底解决该问题的呢?呵呵,我个人觉得这种方法真是……唉。
先来分析一下为啥每拓展一项新的功能就要改代码呢?因为无论是“初学者style”还是后来的“简单工厂模式”,都需要在类文件里进行一些判断,就是if else的那些东西。每没新添加一种计算方式,就需要打开这些文件,添加新的ifelse分支。所以造成了不遵从封闭原则。那么工厂方法模式其实就是将简单工厂进行更高度的抽象,拆分成一个个的生成薪资计算方式的工厂,具体使用哪一个,就让用户(也就是客户端)自己去选取好了。这样一来,就彻底遵从了开放-封闭原则。
当然啦,如此一来也引入了别的麻烦——每拓展一个新的功能,就不仅仅需要添加该功能的类文件,还需要添加生产该类的工厂的类文件。UML图如下:
那么相应的,代码如下:
package factory.method.factory;
import factory.method.calculation.AbstractSalaryCalculation;
/**
* 生产工厂的接口
*
* @author lxp
*
*/
public interface ICreateSalaryCalculation {
AbstractSalaryCalculation createSalaryCalculation(int beforeTaxSalary);
}
package factory.method.factory;
import factory.method.calculation.AbstractSalaryCalculation;
import factory.method.calculation.PracticeCalculation;
public class CreatePracticeCalculationFactory implements ICreateSalaryCalculation {
@Override
public AbstractSalaryCalculation createSalaryCalculation(int beforeTaxSalary) {
AbstractSalaryCalculation calculation = new PracticeCalculation(beforeTaxSalary);
return calculation;
}
}
package factory.method.factory;
import factory.method.calculation.AbstractSalaryCalculation;
import factory.method.calculation.EmployeeCalculation;
public class CreateEmployeeCalculationFactory implements ICreateSalaryCalculation {
@Override
public AbstractSalaryCalculation createSalaryCalculation(int beforeTaxSalary) {
AbstractSalaryCalculation calculation = new EmployeeCalculation(beforeTaxSalary);
return calculation;
}
}
package factory.method.calculation;
/**
* 薪资计算
*
* @author lxp
*
*/
public abstract class AbstractSalaryCalculation {
/**
* 税前薪资
*/
private int beforeTaxSalary;
/**
* 薪资计算方法
*
* @return
*/
public abstract double calculateSalary();
public AbstractSalaryCalculation(int beforeTaxSalary) {
this.beforeTaxSalary =beforeTaxSalary;
}
public int getBeforeTaxSalary() {
return beforeTaxSalary;
}
public void setBeforeTaxSalary(int beforeTaxSalary) {
this.beforeTaxSalary =beforeTaxSalary;
}
}
package factory.method.calculation;
/**
* 实习生的薪资计算类
*
* @author lxp
*
*/
public class PracticeCalculationextends AbstractSalaryCalculation {
public PracticeCalculation(int beforeTaxSalary) {
super(beforeTaxSalary);
}
/**
* 实习生的薪资计算方式
*/
@Override
public double calculateSalary() {
return 0.8 *this.getBeforeTaxSalary();
}
}
package factory.method.calculation;
/**
* 正式员工的薪资计算类
*
* @author lxp
*
*/
public class EmployeeCalculationextends AbstractSalaryCalculation {
public EmployeeCalculation(int beforeTaxSalary) {
super(beforeTaxSalary);
}
/**
* 正式员工的薪资计算方式
*/
@Override
public double calculateSalary() {
return this.getBeforeTaxSalary();
}
}
package factory.method.calculation.test;
import factory.method.calculation.AbstractSalaryCalculation;
import factory.method.factory.CreateEmployeeCalculationFactory;
import factory.method.factory.ICreateSalaryCalculation;
public class TestMethodFactory {
public static void main(String[] args) {
// 实例化创建薪资计算方式的工厂,如果想拓展功能,新增一种薪资计算方式,只要添加实现了ICreateSalaryCalculation接口的文件和实现了AbstractSalaryCalculation的文件即可。客户端自己决定要采取的计算方法。
ICreateSalaryCalculation factory = new CreateEmployeeCalculationFactory();
// 工厂创建出薪资计算方式
AbstractSalaryCalculation salaryCalculation = factory.createSalaryCalculation(5000);
// 计算薪资
double realSalary = salaryCalculation.calculateSalary();
System.out.println(realSalary);
}
}
其实,工厂方法这种设计思想很普遍,像我们常用的软件中的插件,就是给开发插件的厂商留下一个接口,这个接口里定义了可以拓展的功能的抽象,然后各个厂商实现这个接口,就可以将自己的功能集成进去啦~\(≧▽≦)/~然后还有就是大家常用的框架里是不是经常出现XXXFactory之类的呢?嘻嘻,也是啦。
六、抽象工厂模式遵从了开放-封闭原则且不需要再客户端进行判断
话说上一小节中提到的模式不过是把判断挪到了客户端,让客户端进行判断到底该用那种方式计算薪资。
写到这里我才知道,原来这三个设计模式名字中都带有“工厂”,但是抽象工厂模式与前两个并没有什么太的的关系(我个人是这么认为的),不过抽象工厂模式通过“反射+配置文件”的确彻底遵从了开放-封闭原则,只不过你需要多一些开销啦——开发功能类、开发对应的工厂类、添加配置。
为了全文的连贯性,我们还是用上面的例子。不过这回换成了不是一家公司,而是多家公司的薪资计算了。老规矩,我们先上UML图:
代码如下:
package factory.abstracted.interfaze;
/**
* 实习生的薪资计算接口
*
* @author 刘欣普
*
*/
public interface IPracticeCalculation {
double calculatePracticeSalary(int beforeTaxSalary);
}
package factory.abstracted.interfaze;
/**
* 正式员工的薪资计算接口
*
* @author 刘欣普
*
*/
public interface IEmployeeCalculation {
double calculateEmployeeSalary(int beforeTaxSalary);
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class CPracticeCalculationimplements IPracticeCalculation {
@Override
public double calculatePracticeSalary(int beforeTaxSalary) {
return beforeTaxSalary * 0.8;
}
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
public class CEmployeeCalculationimplements IEmployeeCalculation {
@Override
public double calculateEmployeeSalary(int beforeTaxSalary) {
return beforeTaxSalary;
}
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class BPracticeCalculationimplements IPracticeCalculation {
@Override
public double calculatePracticeSalary(int beforeTaxSalary) {
return beforeTaxSalary * 0.8;
}
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
public class BEmployeeCalculationimplements IEmployeeCalculation {
@Override
public double calculateEmployeeSalary(int beforeTaxSalary) {
return beforeTaxSalary;
}
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class APracticeCalculationimplements IPracticeCalculation {
/**
* A公司的实习生薪资
*/
@Override
public double calculatePracticeSalary(int beforeTaxSalary) {
return beforeTaxSalary * 0.8;
}
}
package factory.abstracted.calculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
public class AEmployeeCalculationimplements IEmployeeCalculation {
/**
* A公司的正式员工薪资
*/
@Override
public double calculateEmployeeSalary(int beforeTaxSalary) {
return beforeTaxSalary;
}
}
package factory.abstracted.bean;
import factory.abstracted.interfaze.IEmployeeCalculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public abstract class AbstractCompany {
/**
* 创建计算实习生薪资的实例
*
* @return
*/
public abstract IPracticeCalculation createPracticeCalculation();
/**
* 创建计算正式员工薪资的实例
*
* @return
*/
public abstract IEmployeeCalculation createEmployeeCalculation();
}
package factory.abstracted.bean;
import factory.abstracted.calculation.AEmployeeCalculation;
import factory.abstracted.calculation.APracticeCalculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class ACompanyextends AbstractCompany {
@Override
public IPracticeCalculation createPracticeCalculation() {
return new APracticeCalculation();
}
@Override
public IEmployeeCalculation createEmployeeCalculation() {
return new AEmployeeCalculation();
}
}
package factory.abstracted.bean;
import factory.abstracted.calculation.BEmployeeCalculation;
import factory.abstracted.calculation.BPracticeCalculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class BCompanyextends AbstractCompany {
@Override
public IPracticeCalculation createPracticeCalculation() {
return new BPracticeCalculation();
}
@Override
public IEmployeeCalculation createEmployeeCalculation() {
return new BEmployeeCalculation();
}
}
package factory.abstracted.bean;
import factory.abstracted.calculation.CEmployeeCalculation;
import factory.abstracted.calculation.CPracticeCalculation;
import factory.abstracted.interfaze.IEmployeeCalculation;
import factory.abstracted.interfaze.IPracticeCalculation;
public class CCompanyextends AbstractCompany {
@Override
public IPracticeCalculation createPracticeCalculation() {
return new CPracticeCalculation();
}
@Override
public IEmployeeCalculation createEmployeeCalculation() {
return new CEmployeeCalculation();
}
}
package factory.abstracted.test;
import factory.abstracted.bean.ACompany;
import factory.abstracted.bean.AbstractCompany;
public class TestAbstractedFactory {
public static void main(String[]args) {
/**
*注意下面这一行代码,当需要切换“产品系列”的时候,直接换掉这里就可以了。
* 注意,利用配置文件可以不需要修改代码,写成类似于
* AbstractCompany Company = Class.forName("XXX");此处的“XXX”取自配置文件
*/
AbstractCompany Company = new ACompany();
System.out.println("该公司的正式员工工资是" +Company.createEmployeeCalculation().calculateEmployeeSalary(3000));
System.out.println("该公司的实习生 工资是" +Company.createPracticeCalculation().calculatePracticeSalary(3000));
}
}
抽象工厂模式的有点是易于切换产品系类,但是缺点是当我需要添加新的“产品(薪资计算方式,例如UML图中画的临时工薪资计算方式)”的时候,需要添加的不仅仅是“产品”,还有生产该种产品的“工厂”。如果在客户端想调用,需要在配置文件中添加该工厂类信息以便反射获取实例。
其实该设计模式很常见,我们常用的hibernate框架就运用了这种设计模式。Hibernate运用配置决定使用哪种数据库,又运用配置将类的DAO实例关联。
ps:如果图片看不清楚,请查看原版。