iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Software Development

也該是時候學學 Design Pattern 了系列 第 7

Day 07: Creational patterns - Abstract Factory

目的

假如產品之間有可以負責聯繫的元素,那依賴該元素找出共同點後建立關聯,進而減少工廠的數量,卻可以維持更多的生產。

說明

昨天提到 Factory Method,建立許多工廠來負責生產產品。讓我們試想一個情境,現在行銷部門決定要建立多種口味的飲料,因此會有櫻桃口味、檸檬口味與加鹽,套用在現有的三個產品(可樂、礦泉水、柳橙汁),會有九種結果,此時,必須建立多達九個的 Factory 來處理這件事。

可樂 礦泉水 柳橙汁
櫻桃口味 櫻桃口味可樂 人工櫻桃風味礦泉水 櫻桃柳橙汁
檸檬口味 檸檬口味可樂 檸檬礦泉水 檸檬柳橙汁
加鹽 加鹽可樂 加鹽礦泉水 加鹽柳橙汁

此時,可以使用 Abstract Factory 減少工廠的數量,作法是:

  1. 將原有產品轉變成虛擬層,視為親代,可以用 Abstract ClassInterface
  2. 原有產品每一個都建立三個口味的子代。
  3. 建立工廠的親代,可以用 Abstract ClassInterface,負責要求每間口味工廠需要生產三種原有產品。
  4. 建立一系列的口味工廠,每間工廠都可以產出以原有口味為基底的新飲料。
  5. 決策者依據口味使用對應的工廠,好取得對應的新飲料。

UML 圖

Abstract Factory Pattern UML Diagram

使用 Java 實作

產品祖代:PETBottle

public abstract class PETBottle {
    protected String smell = "";
    protected String color = "";

    protected PETBottle(String smell, String color) {
        this.smell = smell;
        this.color = color;
    }

    public abstract String getTaste();
}

產品親代-可樂:Coke

public abstract class Coke extends PETBottle {
    protected Coke(String smell) {
        super(smell, "黑色");
    }

    @Override
    public String getTaste() {
        return "這瓶可樂的顏色:" + color + ",香味是:" + smell;
    }
}

產品子代-可樂相關口味系列:CherryCokeLemonCokeSaltCoke

public class CherryCoke extends Coke {
    public CherryCoke() {
        super("櫻桃味道");
    }

    @Override
    public String getTaste() {
        return "這瓶櫻桃可樂的顏色:" + color + ",香味是:" + smell;
    }
}

public class LemonCoke extends Coke {
    public LemonCoke() {
        super("清新的酸味");
    }

    @Override
    public String getTaste() {
        return "這瓶檸檬可樂的顏色:" + color + ",香味是:" + smell;
    }
}

public class SaltCoke extends Coke {
    public SaltCoke() {
        super("鹹鹹的");
    }

    @Override
    public String getTaste() {
        return "這瓶加鹽可樂的顏色:" + color + ",香味是:" + smell;
    }
}

產品親代-礦泉水:Water

public abstract class Water extends PETBottle {
    protected Water(String smell) {
        super(smell, "透明");
    }

    @Override
    public String getTaste() {
        return "這瓶水的顏色:" + color + ",香味是:" + smell;
    }
}

產品子代-礦泉水相關口味系列:CherryWaterLemonWaterSaltWater

public class CherryWater extends Water {
    public CherryWater() {
        super("櫻桃味道");
    }

    @Override
    public String getTaste() {
        return "這瓶人工櫻桃風味礦泉水的顏色:" + color + ",香味是:" + smell;
    }
}

public class LemonWater extends Water {
    public LemonWater() {
        super("清新的酸味");
    }

    @Override
    public String getTaste() {
        return "這瓶檸檬礦泉水的顏色:" + color + ",香味是:" + smell;
    }
}

public class SaltWater extends Water {
    public SaltWater() {
        super("鹹鹹的");
    }

    @Override
    public String getTaste() {
        return "這瓶加鹽礦泉水的顏色:" + color + ",香味是:" + smell;
    }
}

產品親代-柳橙汁:OrangeJuice

public abstract class OrangeJuice extends PETBottle {
    protected OrangeJuice(String smell) {
        super(smell, "橘色");
    }

    @Override
    public String getTaste() {
        return "這瓶柳橙汁的顏色:" + color + ",香味是:" + smell;
    }
}

產品子代-柳橙汁相關口味系列:CherryOrangeJuiceLemonOrangeJuiceSaltOrangeJuice

public class CherryOrangeJuice extends OrangeJuice {
    public CherryOrangeJuice() {
        super("櫻桃味道");
    }

    @Override
    public String getTaste() {
        return "這瓶櫻桃柳橙汁的顏色:" + color + ",香味是:" + smell;
    }
}

public class LemonOrangeJuice extends OrangeJuice {
    public LemonOrangeJuice() {
        super("柑橘類特有的酸味");
    }

    @Override
    public String getTaste() {
        return "這瓶檸檬柳橙汁的顏色:" + color + ",香味是:" + smell;
    }
}

public class SaltOrangeJuice extends OrangeJuice {
    public SaltOrangeJuice() {
        super("鹹鹹的");
    }

    @Override
    public String getTaste() {
        return "這瓶加鹽柳橙汁的顏色:" + color + ",香味是:" + smell;
    }
}

工廠親代:BeverageFactory

public interface BeverageFactory {
    Coke produceCoke();

    Water produceWater();

    OrangeJuice produceOrangeJuice();
}

工廠子代-相關口味系列:CherryFlavorFactoryLemonFlavorFactorySaltFlavorFactory

public class CherryFlavorFactory implements BeverageFactory {
    @Override
    public Coke produceCoke() {
        return new CherryCoke();
    }

    @Override
    public Water produceWater() {
        return new CherryWater();
    }

    @Override
    public OrangeJuice produceOrangeJuice() {
        return new CherryOrangeJuice();
    }
}

public class LemonFlavorFactory implements BeverageFactory {
    @Override
    public Coke produceCoke() {
        return new LemonCoke();
    }

    @Override
    public Water produceWater() {
        return new LemonWater();
    }

    @Override
    public OrangeJuice produceOrangeJuice() {
        return new LemonOrangeJuice();
    }
}

public class SaltFlavorFactory implements BeverageFactory {
    @Override
    public Coke produceCoke() {
        return new SaltCoke();
    }

    @Override
    public Water produceWater() {
        return new SaltWater();
    }

    @Override
    public OrangeJuice produceOrangeJuice() {
        return new SaltOrangeJuice();
    }
}

決策者:

public class BeverageAbstractFactorySample {
    public static void main(String[] args) {
        System.out.println("準備開喝!");
        BeverageFactory saltFlavorFactory = new SaltFlavorFactory();
        Coke saltCoke = saltFlavorFactory.produceCoke();
        Water saltWater = saltFlavorFactory.produceWater();
        OrangeJuice saltOrangeJuice = saltFlavorFactory.produceOrangeJuice();

        System.out.println("鹽味派對!");
        System.out.println(saltCoke.getTaste());
        System.out.println(saltWater.getTaste());
        System.out.println(saltOrangeJuice.getTaste());

        System.out.println("\n口味更換");
        BeverageFactory lemonFlavorFactory = new LemonFlavorFactory();
        Coke lemonCoke = lemonFlavorFactory.produceCoke();
        Water lemonWater = lemonFlavorFactory.produceWater();
        OrangeJuice lemonOrangeJuice = lemonFlavorFactory.produceOrangeJuice();

        System.out.println("檸檬派對!");
        System.out.println(lemonCoke.getTaste());
        System.out.println(lemonWater.getTaste());
        System.out.println(lemonOrangeJuice.getTaste());

        System.out.println("\n最後一輪");
        BeverageFactory cherryFlavorFactory = new CherryFlavorFactory();
        Coke cherryCoke = cherryFlavorFactory.produceCoke();
        Water cherryWater = cherryFlavorFactory.produceWater();
        OrangeJuice cherryOrangeJuice = cherryFlavorFactory.produceOrangeJuice();

        System.out.println("櫻桃派對!");
        System.out.println(cherryCoke.getTaste());
        System.out.println(cherryWater.getTaste());
        System.out.println(cherryOrangeJuice.getTaste());
    }
}

使用 JavaScript 實作

受限於 JavaScript 沒有虛擬型別、無法限制型別。

產品祖代:PETBottle

/** @abstract */
class PETBottle {
  constructor(smell, color) {
    this.smell = smell;
    this.color = color;
  }

  getTaste() { return; }
}

產品親代-可樂:Coke

/** @abstract */
class Coke extends PETBottle {
  constructor(smell, color) {
    super(smell, "黑色");
  }

  /** @override */
  getTaste() {
    return "這瓶可樂的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

產品子代-可樂相關口味系列:CherryCokeLemonCokeSaltCoke

class CherryCoke extends Coke {
  constructor() {
    super("櫻桃風味");
  }

  /** @override */
  getTaste() {
    return "這瓶櫻桃可樂的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class LemonCoke extends Coke {
  constructor() {
    super("清新的酸味");
  }

  /** @override */
  getTaste() {
    return "這瓶檸檬可樂的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class SaltCoke extends Coke {
  constructor() {
    super("鹹鹹的");
  }

  /** @override */
  getTaste() {
    return "這瓶加鹽可樂的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

產品親代-礦泉水:Water

/** @abstract */
class Water extends PETBottle {
  constructor(smell) {
    super(smell, "透明");
  }

  /** @override */
  getTaste() {
    return "這瓶水的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

產品子代-礦泉水相關口味系列:CherryWaterLemonWaterSaltWater

class CherryWater extends Water {
  constructor() {
    super("櫻桃味道");
  }

  /** @override */
  getTaste() {
    return "這瓶人工櫻桃風味礦泉水的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class LemonWater extends Water {
  constructor() {
    super("清新的酸味");
  }

  /** @override */
  getTaste() {
    return "這瓶檸檬礦泉水的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class SaltWater extends Water {
  constructor() {
    super("鹹鹹的");
  }

  /** @override */
  getTaste() {
    return "這瓶加鹽礦泉水的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

產品親代-柳橙汁:OrangeJuice

/** @abstract */
class OrangeJuice extends PETBottle {
  constructor(smell) {
    super(smell, "橘色");
  }

  /** @override */
  getTaste() {
    return "這瓶柳橙汁的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

產品子代-柳橙汁相關口味系列:CherryOrangeJuiceLemonOrangeJuiceSaltOrangeJuice

class CherryOrangeJuice extends OrangeJuice {
  constructor() {
    super("櫻桃味道");
  }

  /** @override */
  getTaste() {
    return "這瓶櫻桃柳橙汁的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class LemonOrangeJuice extends OrangeJuice {
  constructor() {
    super("柑橘類特有的酸味");
  }

  /** @override */
  getTaste() {
    return "這瓶檸檬柳橙汁的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

class SaltOrangeJuice extends OrangeJuice {
  constructor() {
    super("鹹鹹的");
  }

  /** @override */
  getTaste() {
    return "這瓶加鹽柳橙汁的顏色:" + this.color + ",香味是:" + this.smell;
  }
}

工廠親代:BeverageFactory

/** @interface */
class BeverageFactory {
  produceCoke() { return; }

  produceWater() { return; }

  produceOrangeJuice() { return; }
}

工廠子代-相關口味系列:CherryFlavorFactoryLemonFlavorFactorySaltFlavorFactory

class CherryFlavorFactory extends BeverageFactory {
  /** @override */
  produceCoke() {
    return new CherryCoke();
  }

  /** @override */
  produceWater() {
    return new CherryWater();
  }

  /** @override */
  produceOrangeJuice() {
    return new CherryOrangeJuice();
  }
}

class LemonFlavorFactory extends BeverageFactory {
  /** @override */
  produceCoke() {
    return new LemonCoke();
  }

  /** @override */
  produceWater() {
    return new LemonWater();
  }

  /** @override */
  produceOrangeJuice() {
    return new LemonOrangeJuice();
  }
}

class SaltFlavorFactory extends BeverageFactory {
  /** @override */
  produceCoke() {
    return new SaltCoke();
  }

  /** @override */
  produceWater() {
    return new SaltWater();
  }

  /** @override */
  produceOrangeJuice() {
    return new SaltOrangeJuice();
  }
}

實作:

const beverageAbstractFactorySample = () => {
  console.log("準備開喝!");
  const saltFlavorFactory = new SaltFlavorFactory();
  const saltCoke = saltFlavorFactory.produceCoke();
  const saltWater = saltFlavorFactory.produceWater();
  const saltOrangeJuice = saltFlavorFactory.produceOrangeJuice();

  console.log("鹽味派對!");
  console.log(saltCoke.getTaste());
  console.log(saltWater.getTaste());
  console.log(saltOrangeJuice.getTaste());

  console.log("\n口味更換");
  const lemonFlavorFactory = new LemonFlavorFactory();
  const lemonCoke = lemonFlavorFactory.produceCoke();
  const lemonWater = lemonFlavorFactory.produceWater();
  const lemonOrangeJuice = lemonFlavorFactory.produceOrangeJuice();

  console.log("檸檬派對!");
  console.log(lemonCoke.getTaste());
  console.log(lemonWater.getTaste());
  console.log(lemonOrangeJuice.getTaste());

  console.log("\n最後一輪");
  const cherryFlavorFactory = new CherryFlavorFactory();
  const cherryCoke = cherryFlavorFactory.produceCoke();
  const cherryWater = cherryFlavorFactory.produceWater();
  const cherryOrangeJuice = cherryFlavorFactory.produceOrangeJuice();

  console.log("櫻桃派對!");
  console.log(cherryCoke.getTaste());
  console.log(cherryWater.getTaste());
  console.log(cherryOrangeJuice.getTaste());
};

beverageAbstractFactorySample();

總結

Abstract Factory 改善了 Factory Method 面對更多種產品時,必須建立各自的工廠導致工廠數量過多的問題。當然,缺點也很明顯,「必須找到多種產品之間有沒有關聯」,如果有才能抽離、轉換成關係、建立工廠、減少數量;如果沒有則無計可施,只能維持 Factory Method 多工廠的局面。

三個 Factory 模式介紹完畢,接著依照字母順序,介紹 Builder 模式。


上一篇
Day 06: Creational patterns - Factory Method
下一篇
Day 08: Creational patterns - Builder
系列文
也該是時候學學 Design Pattern 了31

尚未有邦友留言

立即登入留言