iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
Software Development

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

Day 06: Creational patterns - Factory Method

目的

不再限制一個工廠(物件)建立許多產品(物件),而是轉變成一個產品(物件)對應一個工廠(物件),而決定生產哪個產品(物件)則轉交給另一個決策者(物件)。

說明

昨天提到,每次新增一個產品時,Simple Factory Method 內負責管理生產的工廠就必須「變動 switch-case」,此項舉動違反 SOLIDOCP。Factory Method 改良這一點,讓每項產品擁有各自的工廠。實際決定要使用哪間工廠的決策者,則轉交給其他物件負責,本身不涉入生產判定。

Factory Method 的作法是:

  1. 先決定要建立的產品,該產品視為親代,可以用 ClassAbstract ClassInterface
  2. 建立一系列的產品子代,子代負責實作細節。
  3. 建立工廠的親代,使用 Interface
  4. 建立一系列的的工廠子代,工廠與產品的關係是一對一,一個產品只有一間工廠,負責實作細節。
  5. 決策者決定使用哪間工廠得到想要的產品。

UML 圖

Factory Method UML Diagram

使用 Java 實作

產品親代:PETBottle

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

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

    public abstract String getTaste();
}

產品子代:CokeWaterOrangeJuice

public class Coke extends PETBottle {
    public Coke(String smell, String color) {
        super(smell, color);
    }

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

public class Water extends PETBottle {
    public Water(String smell, String color) {
        super(smell, color);
    }

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

public class OrangeJuice extends PETBottle {
    public OrangeJuice(String smell, String color) {
        super(smell, color);
    }

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

工廠親代:PETBottleFactory

public interface PETBottleFactory {
    public PETBottle getPET_Bottle();
}

工廠子代:CokeFactoryWaterFactoryOrangeJuiceFactory

public class CokeFactory implements PETBottleFactory {
    @Override
    public PETBottle getPET_Bottle() {
        System.out.println("機器選定可樂瓶子");
        System.out.println("黑色液體衝入瓶子內");
        System.out.println("轉上瓶蓋,完成");
        return new Coke("甜甜的", "黑色");
    }
}

public class OrangeJuiceFactory implements PETBottleFactory {
    @Override
    public PETBottle getPET_Bottle() {
        System.out.println("機器選定礦泉柳橙汁瓶子");
        System.out.println("橘色液體衝入瓶子內");
        System.out.println("轉上瓶蓋,完成");
        return new OrangeJuice("酸酸的", "橘色");
    }
}

public class WaterFactory implements PETBottleFactory {
    @Override
    public PETBottle getPET_Bottle() {
        System.out.println("機器選定礦泉水瓶子");
        System.out.println("透明液體衝入瓶子內");
        System.out.println("轉上瓶蓋,完成");
        return new Water("無味", "透明的");
    }
}

決策者:

public class PETBottleFactoryMethodSample {
    public static void main(String[] args) {
        System.out.println("今天我想來點,可樂!");
        PETBottleFactory cokeFactory = new CokeFactory();
        PETBottle coke = cokeFactory.getPET_Bottle();
        System.out.println("得到一瓶可樂!");
        System.out.println(coke.getTaste());
        System.out.println("---咳咳咳---");

        System.out.println("有點甜,我想喝點,礦泉水!");
        PETBottleFactory waterFactory = new WaterFactory();
        PETBottle water = waterFactory.getPET_Bottle();
        System.out.println("得到一瓶礦泉水!");
        System.out.println(water.getTaste());

        System.out.println("---咕嚕咕嚕---");

        System.out.println("清爽!最來來點,柳橙汁!");
        PETBottleFactory orangeJuiceFactory = new OrangeJuiceFactory();
        PETBottle orangeJuice = orangeJuiceFactory.getPET_Bottle();
        System.out.println("得到一瓶柳橙汁!");
        System.out.println(orangeJuice.getTaste());
    }
}

使用 JavaScript 實作

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

產品親代:PETBottle

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

  getTaste() { return; }
}

產品子代:CokeWaterOrangeJuice

class Coke extends PETBottle {
  constructor(smell, color) {
    super(smell, color);
  }

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

class Water extends PETBottle {
  constructor(smell, color) {
    super(smell, color);
  }

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

class OrangeJuice extends PETBottle {
  constructor(smell, color) {
    super(smell, color);
  }

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

工廠親代:PETBottleFactory

/** @interface */
class PETBottleFactory {
  getPET_Bottle() { return; }
}

工廠子代:CokeFactoryWaterFactoryOrangeJuiceFactory

class CokeFactory extends PETBottleFactory {
  /** @override */
  getPET_Bottle() {
    console.log("機器選定可樂瓶子");
    console.log("黑色液體衝入瓶子內");
    console.log("轉上瓶蓋,完成");
    return new Coke("甜甜的", "黑色");
  }
}

class WaterFactory extends PETBottleFactory {
  /** @override */
  getPET_Bottle() {
    console.log("機器選定礦泉水瓶子");
    console.log("透明液體衝入瓶子內");
    console.log("轉上瓶蓋,完成");
    return new Coke("甜甜的", "黑色");
  }
}

class OrangeJuiceFactory extends PETBottleFactory {
  /** @override */
  getPET_Bottle() {
    console.log("機器選定礦泉柳橙汁瓶子");
    console.log("橘色液體衝入瓶子內");
    console.log("轉上瓶蓋,完成");
    return new OrangeJuice("酸酸的", "橘色");
  }
}

決策者:

const PETBottleFactoryMethodSample = () => {
  console.log("今天我想來點,可樂!");
  const cokeFactory = new CokeFactory();
  const coke = cokeFactory.getPET_Bottle();
  console.log("得到一瓶可樂!");
  console.log(coke.getTaste());
  console.log("---咳咳咳---");

  console.log("有點甜,我想喝點,礦泉水!");
  const waterFactory = new WaterFactory();
  const water = waterFactory.getPET_Bottle();
  console.log("得到一瓶礦泉水!");
  console.log(water.getTaste());

  console.log("---咕嚕咕嚕---");

  console.log("清爽!最來來點,柳橙汁!");
  const orangeJuiceFactory = new OrangeJuiceFactory();
  const orangeJuice = orangeJuiceFactory.getPET_Bottle();
  console.log("得到一瓶柳橙汁!");
  console.log(orangeJuice.getTaste());
};

PETBottleFactoryMethodSample();

總結

Factory Method 改善了 Simple Factory Method 無法遵守 SOLID 的問題,將「選擇」交給決策者,本身不涉及選擇,只負責生產。

然而,這個生產是直線關係,工廠對應產品,假如生產的產品數量增加非常多時,整體程式會顯得笨重、重複。此時,如果能找出產品之間的共通點,那就能抽離出來,重構後變成新的 Factory 模式。

明天,將介紹 Abstract Factory 模式。


上一篇
Day 05: Creational patterns - Simple Factory Method
下一篇
Day 07: Creational patterns - Abstract Factory
系列文
也該是時候學學 Design Pattern 了31

尚未有邦友留言

立即登入留言