設計模式 - 工廠模式 (Creational Patterns - Factory Pattern)

前言

這篇文章會介紹三個最常使用的工廠模式方法,分別是

  • Simple Factory (簡單工廠)
  • Abstract Factory (抽象工廠)
  • Factory Method (工廠方法)

其中最容易讓人搞混的是,Abstract Factory和Factory Method,不過對我來說何必太過糾結於這兩個的概念,工廠模式最主要的核心原理就是將你創造物件的邏輯隱藏起來,不向客戶端顯示,我覺得只要知道這個概念就夠了,接下來會介紹這三個種類的工廠模式。

Factory Pattern 工廠模式

Simple Factory 簡單工廠

簡單工廠是工廠模式的第一步,也是工廠模式最基本的核心概念實踐,主要解決的問題是

  • 將創建物件邏輯抽離
  • 解偶Class的複雜度

Class Diagram

  • 將封裝物件邏輯抽出寫成Simple Factory
  • 產出父類別物件的物件

範例

在這個範例我們希望將戰士與魔法師遊戲創造英雄的邏輯抽出並且寫成靜態工廠,戰士與魔法師都是英雄並且’目前’只能進行攻擊,程式碼如下

Interface Hero

public interface Hero {
    void attack();
}

Magician.class

public class Magician implements Hero {
    @Override
    public void attack() {
        System.out.println("Use magic to attack");
    }
}

Warrior.class

public class Warrior implements Hero {
    @Override
    public void attack() {
        System.out.println("Use sword to attack");
    }
}

HeroFactory.class

public class HeroFactory {
    public Hero makeHero(HeroType heroType) {
        if (heroType.equals(HeroType.MAGICIAN)) {
            return new Magician();
        } else {
            return new Warrior();
        }
    }
}

Client.class

public class Client {
    public static void main(String[] args) {
        HeroFactory heroFactory = new HeroFactory();
        Hero hero = heroFactory.makeHero(HeroType.MAGICIAN);
        hero.attack();
    }
}

就這麼簡單我們就完成了簡單工廠模式了

Abstract Factory 抽象工廠

抽象工廠是簡單工廠模式的維度擴展,何謂維度擴展,我們以上一個簡單工廠模式的戰士法師遊戲來說好了,原本這款遊戲我們只需要戰士和法師兩個角色,但是隨著遊戲業競爭激烈,老闆決定我們要讓角色更豐富,所以決定要將兩個英雄加上時下流行的冰與火屬性,這也代表了我們創建英雄時會複雜化。接下來的範例會介紹我們如何用抽象工廠讓創建邏輯維度擴展

抽象工廠解決的問題

  • 將簡單工廠的創建邏輯維度擴展

Class Diagram

範例

  • 將Simple Factory多型化
    • 創建冰與火的工廠
    • 工廠方法包含創建法師和創建英雄
  • 將英雄介面切分成戰士與法師介面 (目的讓英雄邏輯解偶)
    • 實踐戰士 火/冰 物件
    • 實踐法師 火/冰 物件

戰士

public interface Warrior {
    void swordAttack();
}

public class IceWarrior implements Warrior {
    @Override
    public void swordAttack() {
        System.out.println("Use ice sword to attack");
    }
}
public class FireWarrior implements Warrior {
    @Override
    public void swordAttack() {
        System.out.println("Use fire sword to attack");
    }
}

法師

public interface Magician {
    void magicAttack();
}
public class IceMagician implements Magician {

    @Override
    public void magicAttack() {
        System.out.println("Use ice ball to attack");
    }
}
public class FireMagician implements Magician {
    @Override
    public void magicAttack() {
        System.out.println("Use fire ball to attack");
    }
}

英雄工廠

public interface HeroFactory {
    Magician makeMagician();
    Warrior makeWarrior();
}
public class IceHeroFactory implements HeroFactory {
    @Override
    public Magician makeMagician() {
        return new IceMagician();
    }

    @Override
    public Warrior makeWarrior() {
        return new IceWarrior();
    }
}
public class FireHeroFactory implements HeroFactory {
    @Override
    public Magician makeMagician() {
        return new FireMagician();
    }

    @Override
    public Warrior makeWarrior() {
        return new FireWarrior();
    }
}

入口

public class FactoryProto {
    public static void main(String[] args) {
        HeroFactory factory = null;
        Scanner sc = new Scanner(System.in);
        String attribute = sc.next();

        if (attribute.equals("fire")) {
            factory = new FireHeroFactory();
        } else if (attribute.equals("ice")){
            factory = new IceHeroFactory();
        }

        String hero = sc.next();
        if (hero.equals("magician")) {
            factory.makeMagician().magicAttack();
        } else if (hero.equals("warrior")) {
            factory.makeWarrior().swordAttack();
        }
    }
}

敲黑板

根據上面的範例,我們先對英雄介面作解偶切出戰士與法師屬性,為何呢?
因為如果不切分的話英雄群集會越來越龐大,未來需求可能還包含種族或是其他特性,那這英雄介面就會變成瓶頸點。

這樣我們就完成了抽象工廠模式了

Factory Method 工廠方法

網路上太多文章想要講解工廠方法和抽象工廠的差別,但我認為工廠方法就是抽象工廠的工廠介面,其實我認為不管簡單抽象或是工廠方法,我們只需要保持住我們對工廠模式的理解那基本上就是可以駕馭工廠模式了,工廠模式最基礎的觀念就是封裝創建物件的邏輯!而抽象工廠和工廠方法就只是多型對於封裝物件邏輯的進階應用。

Class Diagram

我們可以看到Class Diagram,就如同我們抽象工廠的單條工廠支線的介面定義

小訣竅

我偶爾會使用工廠方法當作單純的定義應用,下面是複雜一點的範例,會搭配Template與限制符的應用

範例

法師定義

public interface Hero {
    void attack();
}
public interface Magician extends Hero {
    void magicAttack();
}

public class IceMagician implements Magician {

    @Override
    public void magicAttack() {
        System.out.println("Use ice ball to attack");
    }
}
public class FireMagician implements Magician {
    @Override
    public void magicAttack() {
        System.out.println("Use fire ball to attack");
    }
}

工廠方法

public interface HeroCreator<TYPE, HERO extends Hero> {
    HERO createHero(TYPE type);
}

看到上方的程式碼,其實如果是多人開發功能的話,開發同伴只要看到HeroCreator就可以知道這是一個工廠方法,而且後面的限制符也明確地訂出這是穿們給Hero種類,而TYPE的保持彈性讓要時做這介面的人決定如何撰寫TYPE。

實踐工廠方法


enum MagicianType {
    FIRE, ICE
}

public class MagicianCreator implements HeroCreator<MagicianType, Magician>{
    @Override
    public Magician createHero(MagicianType heroType) {
        if (MagicianType.ICE == heroType) {
            return new IceMagician();
        }
        return new FireMagician();
    }
}

總結

  • 工廠模式不管是甚麼類型核心目的都是封裝創建邏輯
  • 簡單工廠是最基本的工廠模式應用
  • 抽象工廠是簡單工廠的維度應用
  • 工廠方法是宣告工廠的介面,在創建抽象工廠時的工廠介面

以上的原始碼都可以參考GitHub的Factory Repository

點我查看原始碼 Git Hub Repository

留言

這個網誌中的熱門文章

Java Lambda Map篇

(InterviewBit) System Design - Design Cache System

設計模式 - 享元模式 (Structural Patterns - Flyweight Design Pattern)