程序猿學社的GitHub,歡迎Star
github技術專題
本文已記錄到github
前言
通過上一篇文章,我們已經知道程序猿是有女(男)朋友,也知道如何保證只會有一個對象(男女朋友),本文我們來看一看工廠模式。
社長:“老王,我們開始繼續23中設計模式之工廠模式”
隔壁老王: “社長,工廠模式有什麼用,爲什麼要用工廠模式?”
社長: “我先買一個小關子,先來看一看傳統的寫法”
小故事
週末一大早,6點鐘左右,我就被吵醒,然後一臉呆萌呆萌的看着我,原來是忘記給我家乖乖餵食物咯,看了一下存放貓糧的袋子,也彈盡糧絕咯,只能上寵物店去購買貓糧。他的名字叫湯圓
傳統方式
通過代碼,我們實現去寵物店購買貓糧的這樣一個需求。
package com.cxyxs.designmode.factory;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/3 23:16
* Modified By:
*/
public interface PetShop {
void buy();
}
class Meat implements PetShop{
@Override
public void buy() {
System.out.println("社長給湯圓購買肉乾");
}
}
/**
* 提供者
* =======================
* 調用者
*/
class Test{
public static void main(String[] args) {
PetShop ps = new Meat();
ps.buy();
}
}
- 這種寫法違反了迪米特法則也就是最少知道原則,通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類來說,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外泄漏任何信息。
- 結合生活實際理解,例如我這裏是去購買肉乾,我有必要知道這個肉是如何生產出來的嗎?
- 實際上也違反勒開閉原則,對擴展開放(提供者),對修改關閉(調用者)
例如我們把類Meat改爲Meat1,我們可以發現,我們需要修改兩個地方,一個是提供者,還有一個是調用者,兩邊都要改動,這就違反了對修改關閉這一條,也沒有實現程序的一個解耦。
隔壁老王: “社長,你也說了,上面這種方式,提供者一變,調用者就得跟着變,在項目開發過程中,如何也出現這樣的問題,那大家還能不能愉快的玩耍咯”
社長:“別急,我們可以通過簡單工廠來實現”
簡單工廠(第一種)
從設計模式的類型上來說,簡單工廠模式是屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)模式,但不屬於23種GOF設計模式之一。簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例
肉乾畢竟不能當飯喫,所以,還得買貓糧,肉乾只能當零食喫。所以這時候我們的需求又發生了改變。
- 不清楚這個圖如何畫的,可以百度查一下UML類圖
package com.cxyxs.designmode.factory.simple;
/**
* Description:寵物店
* Author: 程序猿學社
* Date: 2020/4/3 23:16
* Modified By:
*/
public interface PetShop {
void buy();
}
class Meat implements PetShop{
@Override
public void buy() {
System.out.println("社長給湯圓購買肉乾");
}
}
class Foot implements PetShop{
@Override
public void buy() {
System.out.println("社長給湯圓購買貓糧");
}
}
class CatFootFacoty{
public static PetShop buy(String name){
switch (name){
case "貓糧":
return new Foot();
case "肉乾":
return new Meat();
}
return null;
}
}
/**
* 提供者代碼
* =======================
* 調用端代碼
*/
class Test{
public static void main(String[] args) {
PetShop ps = CatFootFacoty.buy("肉乾");
PetShop ps1 = CatFootFacoty.buy("貓糧");
ps.buy();
ps1.buy();
}
}
好處:
- 在這裏藉助了一個工廠類CatFootFacoty,實現解耦,我們不用關心肉乾和貓糧是生產生產的,只需要告訴工廠,我需要這兩樣東西。由工廠統一管理。
- 產品很少的情況下,可以實現簡單工廠模式
缺點:
- 如果產品一多,工廠類就會變得很臃腫,不利於維護,同時他違反咯開閉原則,開閉原則很重要的一點,當軟件需要變化時,儘量通過擴展軟件實體的行爲來實現變化,而不是通過修改已有代碼來實現變化。
- 例如我現在需要新增一個購買魚的產品需求,需要新增一個魚類,再修改工廠類,類會變得越來越多,如果上百個產品,這就意味着上百個類,增加了程序的複雜度。
工廠方法模式(第二種)
跟簡單工廠相比,工廠方法模式是簡單工廠的plus版本,越來越流程化。不像簡單工廠一個工廠,既生產肉乾,又生產貓糧等等,工廠方法模式,就是把具體生產的工作,交給具體的工廠來負責,例如,生產肉乾的是一個工廠,生產貓糧的又是一個工廠。
- 所以工廠方法模式符合開閉原則
社長: “我們剛剛已經瞭解到簡單工廠一些優缺點,其中很重要的一點就是不符合開閉原則,我們來看一看工廠方法模式”
社長: “既然工廠方法模式是簡單工廠的plus版本,我們直接把簡單工廠的代碼拿過來,新創建一個包methodmodel,老王,有沒有跟上我的節奏”
隔壁老王: “歐了,已經搞定咯,如何改造成工廠方法模式*
社長: "話不多說,先看看類圖,再看代碼 "
- 這個類圖代碼寫完後,可以自動生成,在idea中,右擊
再把對應的類拖進來就可以自動生成,
package com.cxyxs.designmode.factory.methodmodel;
/**
* Description:寵物店
* Author:程序猿學社
* Date: 2020/4/3 23:16
* Modified By:
*/
public interface PetShop {
void buy();
}
class Meat implements PetShop {
@Override
public void buy() {
System.out.println("社長給湯圓購買肉乾");
}
}
class Foot implements PetShop {
@Override
public void buy() {
System.out.println("社長給湯圓購買貓糧");
}
}
interface Factory{
PetShop food();
}
class MeatFactory implements Factory{
@Override
public PetShop food() {
return new Meat();
}
}
class FootFactory implements Factory{
@Override
public PetShop food() {
return new Foot();
}
}
/**
* 提供者代碼
* =======================
* 調用端代碼
*/
class Test{
public static void main(String[] args) {
Factory factory = new MeatFactory();
FootFactory footFactory = new FootFactory();
PetShop food = factory.food();
PetShop food1 = footFactory.food();
food.buy();
food1.buy();
}
}
隔壁老王: “社長,你這代碼,我沒有看出什麼好處來,只知道你這代碼越來越複雜,繞的頭都暈咯。”
社長: “老王,別急呀,我們之前已經知道簡單工廠是不符合開閉原則的,也就是說,儘量不要在已經的代碼上進行修改,應該進行擴展”
社長: “我家的湯圓現在已經不滿足於肉乾和貓糧咯,需要喫魚,我們看看,我們如何在現有的代碼上進行改動”
/**
* 擴展喫魚的部分開頭
*/
class Fish implements PetShop {
@Override
public void buy() {
System.out.println("社長給湯圓購買小魚魚");
}
}
class FishFactory implements Factory{
@Override
public PetShop food() {
return new Fish() ;
}
}
/**
* 提供者代碼
* =======================
* 調用端代碼
*/
class Test{
public static void main(String[] args) {
Factory factory = new MeatFactory();
FootFactory footFactory = new FootFactory();
Factory fishFactory = new FishFactory();
PetShop food = factory.food();
PetShop food1 = footFactory.food();
PetShop food2 = fishFactory.food();
food.buy();
food1.buy();
food2.buy();
}
}
- 擴展產品,具體工廠也跟着擴展,不需要修改以前的代碼,遵守了開閉原則。
好處:
- 提供者修改代碼後,調用者是不知道的,迪米特法則,也就是最少知道原則。
- 在簡單工廠上做咯優化,擴展產品,不需要修改以前的代碼,只需要擴展一個產品和一個具體工廠即可
隔壁老王: “就拿你上面main方法裏面的MeatFactory舉例,假設MeatFactory類變爲MeatFactory123,還是需要修改提供者和調用者兩邊的代碼”
社長 “工廠名稱有一套規範,只需要提供者保證儘量不改動類名就行,不然就是一個死循環,看看mybatis工廠類,版本變動,工廠名也不會變動。mybatis開發就相當於提供者,我們使用人員,就相當於調用者,如果mybatis工廠名變動,我們開發也不知道,這體驗是不是很不好。所以,這個問題,不用擔心,都有規範的”
抽象工廠模式(第三種)
社長: “我們之前只是很簡單的實現喫,貓還會喫、睡等行爲(多個產品等級)。產品等級一多,工廠類就會變得越來越臃腫”
使用工廠方法模式
- 需要使用到12個類,工廠的涉及類就有6個
package com.cxyxs.designmode.factory.abstrastinterface;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/4 18:20
* Modified By:
*/
public interface Foot {
void eat();
}
class Meat implements Foot{
@Override
public void eat() {
System.out.println("給湯圓喫肉乾");
}
}
class Fish implements Foot{
@Override
public void eat() {
System.out.println("給湯圓喫小魚仔");
}
}
interface Toy{
void play();
}
class CatTeaser implements Toy{
@Override
public void play() {
System.out.println("社長利用逗貓棒跟湯圓玩耍");
}
}
class Ball implements Toy{
@Override
public void play() {
System.out.println("湯圓一個人跟小球進行玩耍");
}
}
/**
* 食物工廠代碼
*/
interface FootFactory{
public Foot proFoot();
}
class MeatFactory implements FootFactory{
@Override
public Foot proFoot() {
return new Meat();
}
}
class FishFactory implements FootFactory{
@Override
public Foot proFoot() {
return new Fish();
}
}
/**
* 玩具工廠
*/
interface ToyFactory{
public Toy proToy();
}
class CatTeaserFactory implements ToyFactory{
@Override
public Toy proToy() {
return new CatTeaser();
}
}
class BallFactory implements ToyFactory{
@Override
public Toy proToy() {
return new Ball();
}
}
使用抽象工廠實現
社長: “先看看類圖,再根據類圖實現對應的代碼”
package com.cxyxs.designmode.factory.abstrastinterface.plus;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/4 18:20
* Modified By:
*/
public interface Foot {
void eat();
}
class Meat implements Foot {
@Override
public void eat() {
System.out.println("給湯圓喫肉乾");
}
}
class Fish implements Foot {
@Override
public void eat() {
System.out.println("給湯圓喫小魚仔");
}
}
interface Toy{
void play();
}
class CatTeaser implements Toy {
@Override
public void play() {
System.out.println("社長利用逗貓棒跟湯圓玩耍");
}
}
class Ball implements Toy {
@Override
public void play() {
System.out.println("湯圓一個人跟小球進行玩耍");
}
}
/**
* 食物工廠代碼
*/
interface Factory{
public Foot proFoot();
public Toy proToy();
}
class MeatAndCatTeaserFactory implements Factory {
@Override
public Foot proFoot() {
return new Meat();
}
@Override
public Toy proToy() {
return new CatTeaser();
}
}
class FishAndBallFactory implements Factory{
@Override
public Foot proFoot() {
return new Fish();
}
@Override
public Toy proToy() {
return new Ball();
}
}
- 工廠類由之前的6個,變爲3個。減少了工廠類的臃腫
優點:
- 抽象工廠可以理解爲簡單工廠和工廠方法模式的一個彙總
- 對產品進行了抽象,適合一些維度有關聯的(也就是說,有邏輯關係),例如,我舉的這個例子,在工廠方法中,會有喫的工廠和玩的工廠,而在抽象工廠中,直接把這兩個具體工廠直接抽象出來,合二爲一。
缺點:
- 要求抽象的多個產品,之前有邏輯關係
- 後續需要生產喝的東西,需要修改抽象工廠,同時各個具體工廠也需要修改
隔壁老王: “社長,在抽象工廠模式中,喫和玩都是捆綁的關係,你這是捆綁銷售,如果,我只想要實現喫,應該怎麼辦?”
社長: “如果只想實現喫,就可以使用工廠方法模式,應該根據具體問題,具體選擇,使用那個模式。”
總結:
- 不管是簡單工廠、工廠方法模式、抽象工廠模式,我們應該靈活運用,主要的目的,還是爲了解耦,寫出可擴展性、可讀的代碼。
原創不易,不要白嫖,覺得有用的社友,給我點贊,讓更多的老鐵看到這篇文章。
因技術能力有限,如文中有不合理的地方,希望各位大佬指出,在下方評論留言,謝謝,希望大家一起進步,一起成長。
作者:程序猿學社
原創公衆號:『程序猿學社』,專注於java技術棧,分享java各個技術系列專題,以及各個技術點的面試題。
原創不易,轉載請註明來源(註明:來源於公衆號:程序猿學社, 作者:程序猿學社)。