設計模式 #2 (工廠模式)
文章中所有工程程式碼和UML
建模檔案都在我的這個GitHub
的公開庫--->DesignPattern。Star
來一個好嗎?秋梨膏!
簡述 :提供一種建立物件的最佳方式。
在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。
面向介面(抽象)程式設計?聞到內味了嗎?7大設計原則中的依賴倒置原則、迪米特法則、介面隔離原則。
當然不止用到了一個原則,一般設計模式都是多個設計原則的集合體。
簡單工廠模式
簡述:建立產品介面,需要產品時,利用工廠進行建立即可。
# 反例 :
public class negtive {
/*===============服務端======================*/
interface Food{
void eat();
}
static class Noodles implements Food{
@Override
public void eat() {
System.out.println("吃麵條。。。。。");
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
Food food01 = new Noodles();
food01.eat();
}
}
UML
類圖如下:
這時候,產品來改需求來了,“哥,你先把刀放下。我們們現在這 Noodles
改名了,得改個特牛逼的名字Spaghetti
,讓使用者記住我們們這是西餐義大利麵。”
這時候,因為你原有設計是上面的反例,你得能從修改服務端的原始碼開始,再修改客戶端原始碼。以後再有改名這類事,你還要把刀拿出來放桌上給產品看。
這種設計過於脆弱,因為這樣服務端原始碼和客戶端原始碼是耦合的,改變會牽一髮而動全身。
# 正例:
public class postive {
/*===============服務端======================*/
interface Food{
void eat();
}
static class Spaghetti implements Food {
@Override
public void eat() {
System.out.println("吃西餐麵條。。。。。");
}
}
static class FoodFactory {
public Food getFood(int num){
Food food =null;
switch (num){
case 1 :
food = new Spaghetti();
}
return food;
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory = new FoodFactory();
Food food01 = foodFactory.getFood(1);
food01.eat();
}
}
UML
類圖如下:
通過這樣一個正例,把建立物件的程式碼全交給服務端處理,將服務端程式碼和客戶端程式碼進行了解耦。以後產品再找你聊天是不是可以暫時把刀收起來了?
這樣做的好處,不只是服務端開發人員受益,當服務端程式碼修改時,客戶端也不知道,也不需要知道。
這樣的設計模式並不是十全十美的,任何一種設計模式都不會是十全十美的。只是根據業務邏輯在各方面進行取捨。
簡單工廠模式的缺點:
- 客戶必須記住工廠中常量和具體產品的對映關係。
- 一旦產品品種體量增大到一定程度,工廠類將變得非常臃腫。
- 最致命的缺陷,增加產品時,就要修改工廠類。違反開閉原則。
工廠方法模式
簡述:為了進行擴充套件,不違反開閉原則。
這裡是基於簡單工廠模式進行改進。
# 正例:
public class postive {
/*===============服務端======================*/
//-----------------------產品--------------------
interface Food{
void eat();
}
static class Spaghetti implements Food {
@Override
public void eat() {
System.out.println("吃西餐麵條。。。。。");
}
}
//新增產品
static class Rice implements Food {
@Override
public void eat() {
System.out.println("吃米飯。。。。。");
}
}
//--------------------------工廠-----------------------
interface FoodFactory {
Food getFood();
}
static class SpaghettiFactory implements FoodFactory{
@Override
public Food getFood() {
return new Spaghetti();
}
}
//新增產品工廠
static class RiceFactory implements FoodFactory{
@Override
public Food getFood() {
return new Rice();
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory = new SpaghettiFactory();
Food food01 = foodFactory.getFood();
food01.eat();
}
}
UML
類圖如下:
針對簡單工廠違反開閉原則的這一缺陷,工廠方法模式進行優化。可以看到此時再去增加產品,不再需要修改工廠類,而是增加相應的產品類和工廠類即可。這是符合開閉原則的。
這裡就會有聰明的小問號有很多朋友了:
- 如果原始碼作者修改相關工廠類的類名,那這時候呼叫工廠類的客戶端程式碼就需要修改了,這不如簡單工廠呢?
首先這裡要明確一個概念,工廠類在實際使用中,是相當於介面類的,介面類一般不允許進行修改(非必須),工廠類作者有責任,有義務保證工廠類的類名是穩定的,也就是說,工廠類是比產品類更加穩定的。
- 既然使我們後面自己擴充套件的
Rice
類,為什麼不直接例項化它,直接使用。我們就是作者,為什麼不能直接使用?
這裡需要擴充套件一下,有時候一個產品類並不是孤立的,它和其他類一起組成一個服務框架。
下面增加一些類:
/*===============服務端======================*/
//------------------------產品質檢流程-----------------------、
static class QualityInspection {
public void checking(FoodFactory foodFactory){
System.out.println("我是人肉質檢員。。。。。準備開吃 -_- ");
Food food = foodFactory.getFood();
food.eat();
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory01 = new SpaghettiFactory();
FoodFactory foodFactory02 = new RiceFactory();
QualityInspection inspection = new QualityInspection();
inspection.checking(foodFactory02);
inspection.checking(foodFactory01);
UML
類圖如下:
這時候,如果Rice
沒有他的工廠類,甚至都沒辦法參加質檢,那還怎麼賣?
所以編寫工廠類並不只是單純為了例項化某些產品類,而是能讓配套服務通過工廠介面,得以呼叫工廠建立產品例項。
有的小朋友大大的眼睛裡還有疑惑:那為什麼QualityInspection
的checking
方法不直接呼叫Food
介面再進行產品的例項化呢?
這時候回到簡單工廠模式,產品類不同於工廠類,它是善變的,它會隨著需求的變化而變化,這時候,直接依賴產品類的各種方法,將需要被修改,違反開閉原則。這是死路,小朋友別槓了。哈哈哈。
當然,工廠方法模式也是有缺陷的:
- 當業務需要的型別變多,目前只有食物,當產生飲料,日用品等類別時,我們又要建立新的工廠來實現,造成程式碼重複臃腫。
抽象工廠模式
針對工廠方法模式的缺陷,抽象工廠模式將進行改進,一個工廠負責建立一個產品簇的物件。
關於產品簇:是指多個存在內在聯絡的或者存在邏輯關係的產品。
簡述:在抽象工廠模式中,介面是負責建立一個相關物件的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供物件。
抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠建立其他工廠。該超級工廠又稱為其他工廠的工廠。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
# 正例:
public class postive {
/*===============服務端======================*/
//-----------------------產品--------------------
/*----------------螺絲---------------------*/
interface Screw{
void createScrew();
}
static class Screw_06 implements Screw {
@Override
public void createScrew() {
System.out.println("create Screw_06 666666。。。。。");
}
}
static class Screw_08 implements Screw {
@Override
public void createScrew() {
System.out.println("create Screw_08 8888888。。。。。");
}
}
/*----------------螺母---------------------*/
interface Nut{
void createNut();
}
static class Nut_06 implements Nut {
@Override
public void createNut() {
System.out.println("create Nut_06 666666。。。。。");
}
}
static class Nut_08 implements Nut {
@Override
public void createNut() {
System.out.println("create Nut_08 8888888。。。。。");
}
}
//--------------------------工廠-----------------------
interface ComponentsFactory {
Screw getScrew();
Nut getNut();
}
/*----------------6號工廠---------------------*/
static class Factory_666 implements ComponentsFactory {
@Override
public Screw getScrew() {
return new Screw_06();
}
@Override
public Nut getNut() {
return new Nut_06();
}
}
/*----------------8號工廠---------------------*/
static class Factory_888 implements ComponentsFactory {
@Override
public Screw getScrew() {
return new Screw_08();
}
@Override
public Nut getNut() {
return new Nut_08();
}
}
//------------------------產品質檢流程-----------------------、
static class QualityInspection {
public void checking(ComponentsFactory Factory){
System.out.println("我是人肉質檢員。。。。。等待產出零件 -_- ");
Screw screw = Factory.getScrew();
Nut nut = Factory.getNut();
screw.createScrew();
nut.createNut();
System.out.println("開始質檢.......");
System.out.println(" ");
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
ComponentsFactory Factory01 = new Factory_666();
ComponentsFactory Factory02 = new Factory_888();
QualityInspection inspection = new QualityInspection();
inspection.checking(Factory01);
inspection.checking(Factory02);
}
}
UML
類圖如下:
可以看到,如果在需要進行一種N
號螺絲或者螺母的擴充套件,只需要增加一個實現N
號螺絲或者螺母介面的產品類,利用一個新增N
號工廠進行建立即可。
可以看到,抽象工廠仍然保持著簡單工廠模式和工廠方法模式的優點:
- 服務端程式碼和客戶端程式碼是低耦合的。(簡單工廠模式)
- 所有這一切動作都是新增,不是修改,符合開閉原則。
還新增了一個特有的優點:
- 抽象工廠有效減少了工廠的數量,一個工廠就生產同一個產品簇的產品。
這下產品來改需求,是不是還可以笑嘻嘻跟他聊會天了?
再次強調,一個抽象工廠負責建立同一個產品簇的物件。而產品簇是指多個存在內在聯絡的或者存在邏輯關係的產品。也就是6
號工廠只生產6
號的零部件,不負責生產8
號零部件。不能不同產品簇的產品混合到一個工廠中進行生產。
缺陷:當增加產品簇時(增加6
、 8
號螺帽的生產),這時候就要修改以前工廠(6
、 8
號工廠)的原始碼了。
總結就是:
- 當產品簇比較固定時,考慮使用抽象工廠。
- 當產品簇經常變動時,不建議使用抽象工廠。