別再到處 new 物件了,試試 3 大工廠模式,真香!!
你還在到處 new 物件嗎?
單身狗:我沒物件,new 怎麼了?
new 物件本身是沒問題的,但也不能全部 new 關鍵字走天下,其實有更好的方式,合適的時候可以試試工廠模式,程式碼會更優雅。
什麼是工廠模式?
顧名思義,工廠模式中的 "工廠" 指的是建立物件的工廠,它提供了一種建立物件的最佳方式,也就是工廠模式。
工廠模式的好處是這些物件不需要暴露自身的建立過程,統一由工廠模式進行建立和提供,隱藏了建立細節,避免了錯誤的建立物件的形式,也減少了重複建立冗餘程式碼。
一般情況下,工廠模式可以細分為三類:
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
不過在設計模式權威書籍《設計模式:可複用面向物件軟體的基礎
所以,從權威的角度說,工廠模式只分為: 工廠模式 和 抽象工廠模式 兩大類。
但不管白貓黑貓,能抓老鼠的就是好貓,設計模式亦是如此,不管怎麼分類,這些模式都是程式設計師們歷年過往經驗的濃縮,都是值得學習和借鑑的。
所以,本文棧長從細分的角度帶大家來實戰下這三個工廠設計模式。
1、簡單工廠
比如 XX 公司是做支付的,公司有幾大類的客戶:電商商戶、銀行客戶、代理商……
建立這些客戶的時候我們可以用簡單工廠模式來實現看看。
新建客戶基類:
可以把所有客戶公共的資訊放到一個客戶基類中,比如:客戶名、客戶型別等,所有的客戶繼承這個抽象基類。
/** * 客戶 * @author: 棧長 * @from: 公眾號Java技術棧 */ @Data @NoArgsConstructor @AllArgsConstructor public abstract class Customer { /** * 客戶名稱 */ private String name; /** * 客戶型別 */ private String type; }
新建電商商戶類:
/** * 商戶 * @author: 棧長 * @from: 公眾號Java技術棧 */ @Data @ToString(callSuper = true) public class Merchant extends Customer { /** * 合同型別 */ private int contractType; /** * 結算週期(天) */ private int settmentDays; public Merchant(String name, String type) { super(name, type); } }
新建銀行客戶類:
/**
* 銀行客戶
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@ToString(callSuper = true)
public class BankPartner extends Customer {
/**
* 銀行編碼
*/
private String code;
/**
* 銀行地址
*/
private String address;
public BankPartner(String name, String type) {
super(name, type);
}
}
新建代理商類:
/**
* 代理商
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@ToString(callSuper = true)
public class Agent extends Customer {
/**
* 代理週期
*/
private int period;
/**
* 代理產品
*/
private int[] products;
public Agent(String name, String type) {
super(name, type);
}
}
新增簡單工廠類:
新建一個簡單工廠,提供一個公共靜態方法,根據不同的客戶型別建立不同的客戶。
/**
* 客戶簡單工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class CustomerFactory {
private static Merchant createMerchant(String type, String name) {
return new Merchant(type, name);
}
private static BankPartner createBankPartner(String type, String name) {
return new BankPartner(type, name);
}
private static Agent createAgent(String type, String name) {
return new Agent(type, name);
}
public static Customer create(String type, String name) {
if ("M".equals(type)) {
return createMerchant(type, name);
} else if ("B".equals(type)) {
return createBankPartner(type, name);
} else if ("A".equals(type)) {
return createAgent(type, name);
}
return null;
}
}
新建測試類:
/**
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class Test {
public static void main(String[] args) {
Customer merchant = CustomerFactory.create("M", "Java技術棧商戶");
System.out.println(merchant);
Customer bankPartner = CustomerFactory.create("B", "Java技術棧銀行客戶");
System.out.println(bankPartner);
Customer agent = CustomerFactory.create("A", "Java技術棧代理商");
System.out.println(agent);
}
}
輸出結果:
本節教程所有實戰原始碼已上傳到這個倉庫:
可以看出簡單工廠的使用很簡單,就是耦合性太高了。
第一,物件和基類之間是基於繼承的。
第二,工廠類耦合了不同物件的建立,如果物件型別不是固定或者經常變動的,就要頻繁修改工廠類,比如我現在要再加一種客戶,就必須要改動工廠類,不符開閉原則。
所以,簡單工廠只適用於固定型別物件的建立。
2、工廠方法
工廠方法就是為某類產品提供一個工廠介面,然後為每個產品提供一個工廠實現類。
廢話少說,我們將簡單工廠的示例用工廠方法再改造一下。
新建工廠方法介面:
/**
* 工廠方法客戶介面
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public interface CustomerFactory {
Customer create(String type, String name);
}
新建商戶工廠實現類:
/**
* 商戶工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class MerchantFactory implements CustomerFactory {
@Override
public Customer create(String type, String name) {
return new Merchant(type, name);
}
}
新建銀行客戶工廠實現類:
/**
* 銀行客戶工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class BankPartnerFactory implements CustomerFactory {
@Override
public Customer create(String type, String name) {
return new BankPartner(type, name);
}
}
新建代理商工廠實現類:
/**
* 代理商工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class AgentFactory implements CustomerFactory {
@Override
public Customer create(String type, String name) {
return new Agent(type, name);
}
}
新建測試類:
/**
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class Test {
public static void main(String[] args) {
System.out.println("------工廠模式-工廠方法------");
CustomerFactory merchantFactory = new MerchantFactory();
Customer merchant = merchantFactory.create("M", "Java技術棧商戶");
System.out.println(merchant);
CustomerFactory bankPartnerFactory = new BankPartnerFactory();
Customer bankPartner = bankPartnerFactory.create("B", "Java技術棧銀行客戶");
System.out.println(bankPartner);
CustomerFactory agentFactory = new AgentFactory();
Customer agent = agentFactory.create("A", "Java技術棧代理商");
System.out.println(agent);
}
}
輸出結果:
本節教程所有實戰原始碼已上傳到這個倉庫:
可以看出,工廠方法也是挺簡單易用的,耦合性問題也解決了,每增加一個產品就新增一個產品工廠實現類就行了,擴充套件性非常好。
但也有一個問題,如果產品非常多,那勢必會造成工廠實現類氾濫,另外一種可怕的場景就是,如果涉及到工廠介面變更,工廠實現類的維護簡直就是一種惡夢。
3、抽象工廠
工廠方法中一個工廠只能建立一個物件,如果現在每次建立客戶的時候都需要同時建立一份客戶擴充套件資料,那就可以考慮使用抽象工廠。
新建客戶擴充套件基類:
可以把所有客戶公共的擴充套件資訊放到一個客戶擴充套件基類中,比如:客戶曾用名、客戶擴充套件說明等,所有的客戶繼承這個擴充套件抽象基類。
/**
* 客戶擴充套件
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@NoArgsConstructor
public abstract class CustomerExt {
/**
* 客戶曾用名
*/
private String formerName;
/**
* 客戶擴充套件說明
*/
private String note;
}
新建商戶擴充套件類:
/**
* 商戶
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@ToString(callSuper = true)
public class MerchantExt extends CustomerExt {
/**
* 介紹人
*/
private int introduceName;
/**
* 介紹人電話
*/
private String introduceTel;
}
新建銀行客戶擴充套件類:
/**
* 銀行客戶擴充套件
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@ToString(callSuper = true)
public class BankPartnerExt extends CustomerExt {
/**
* 分行個數
*/
private int branchCount;
/**
* ATM個數
*/
private int atmCount;
}
新建代理商擴充套件類:
/**
* 商戶
* @author: 棧長
* @from: 公眾號Java技術棧
*/
@Data
@ToString(callSuper = true)
public class AgentExt extends CustomerExt {
/**
* 來源
*/
private String source;
/**
* 資質
*/
private String certification;
}
新建抽象工廠介面:
/**
* 抽象工廠客戶介面
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public interface CustomerFactory {
Customer createCustomer(String type, String name);
CustomerExt createCustomerExt();
}
新建商戶工廠實現類:
/**
* 商戶工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class MerchantFactory implements CustomerFactory {
@Override
public Customer createCustomer(String type, String name) {
return new Merchant(type, name);
}
@Override
public CustomerExt createCustomerExt() {
return new MerchantExt();
}
}
新建銀行客戶工廠實現類:
/**
* 銀行客戶工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class BankPartnerFactory implements CustomerFactory {
@Override
public Customer createCustomer(String type, String name) {
return new BankPartner(type, name);
}
@Override
public CustomerExt createCustomerExt() {
return new BankPartnerExt();
}
}
新建代理商工廠實現類:
/**
* 代理商工廠
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class AgentFactory implements CustomerFactory {
@Override
public Customer createCustomer(String type, String name) {
return new Agent(type, name);
}
@Override
public CustomerExt createCustomerExt() {
return new AgentExt();
}
}
新建測試類:
/**
* @author: 棧長
* @from: 公眾號Java技術棧
*/
public class Test {
public static void main(String[] args) {
System.out.println("------工廠模式-抽象工廠------");
CustomerFactory merchantFactory = new MerchantFactory();
Customer merchant = merchantFactory.createCustomer("M", "Java技術棧商戶");
CustomerExt merchantExt = merchantFactory.createCustomerExt();
System.out.println(merchant);
System.out.println(merchantExt);
CustomerFactory bankPartnerFactory = new BankPartnerFactory();
Customer bankPartner = bankPartnerFactory.createCustomer("B", "Java技術棧銀行客戶");
CustomerExt bankPartnerExt = bankPartnerFactory.createCustomerExt();
System.out.println(bankPartner);
System.out.println(bankPartnerExt);
CustomerFactory agentFactory = new AgentFactory();
Customer agent = agentFactory.createCustomer("A", "Java技術棧代理商");
CustomerExt agentExt = agentFactory.createCustomerExt();
System.out.println(agent);
System.out.println(agentExt);
}
}
輸出結果:
可以看出,抽象工廠和工廠方法十分類似,只不過抽象工廠裡面只生產一個物件,而抽象工廠可以生產多個物件。
抽象工廠缺點也很明顯,第一就是和工廠方法一樣工廠類非常多,第二就是擴充套件非常麻煩,比如我現在要為每個客戶型別再加一份客戶特殊資料,那所有涉及到抽象工廠的工廠類都要改,是不是要瘋了。。
總結
如果有多個屬於同一種類型的類,可以考慮使用工廠模式,統一提供生成入口,能從一定程度上解耦,擴充套件方便,也不用再到處 new 物件了。
但話又說回來,從示例可以看出,如果使用或者設計不當也會帶來維護上的工作量。
本節教程所有實戰原始碼已上傳到這個倉庫:
好了,今天的分享就到這裡了,後面棧長我會更新其他設計模式的實戰文章,公眾號Java技術棧第一時間推送。Java技術棧《設計模式》系列文章陸續更新中,請大家持續關注哦!
最後,覺得我的文章對你用收穫的話,動動小手,給個在看、轉發,原創不易,棧長需要你的鼓勵。
版權申明:本文系公眾號 "Java技術棧" 原創,原創實屬不易,轉載、引用本文內容請註明出處,禁止抄襲、洗稿,請自重,尊重大家的勞動成果和智慧財產權,抄襲必究。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源專案弄到 IntelliJ IDEA 啟用碼了,真香!
3.阿里 Mock 工具正式開源,幹掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式釋出,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!