1. 程式人生 > >淺談簡單工廠模式、工廠方法模式、抽象工廠模式

淺談簡單工廠模式、工廠方法模式、抽象工廠模式

淺談簡單工廠模式工廠方法模式抽象工廠模式

概述

簡單說說對這幾個設計模式的理解名字相近一幾度弄不清這都是啥跟啥

都叫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:如果圖片看不清楚,請檢視原版。