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 06 : 操作基础篇 3 - 认识「主题」与 CSS 客制化设定

>>:  [Day6] THM Basic Pentesting

Golang快速入门(Day4)

在这边要介绍一下go的基本用法 而这些用法在A Tour of Go也都有介绍 在下面的程序码如果有...

成员 6 人:一开始没订规矩,001号宇宙毁灭

「现在这个年代,带人要带心,公司架构尽量扁平化,跟同事打成一片最要紧。」 一个刚创业没多久的朋友跟我...

【Day19】Git 版本控制 - 多人协作 GitHub Flow

Git Flow 的缺点可以参考 git flow 实战经验谈 part1 - 别再让 gitflo...

2021-11-24 盘势分析

加权指数 完成W底後,在10/19站上颈线,直接一路狂奔直到11/19,历经1个月的多头格局, 在这...

Day15 - 中场休息时间 - 来看看htmlToCanvas的实作吧 - 成为Canvas Ninja ~ 理解2D渲染的精髓

经过了连续5篇复杂度略高的物理模拟系列,我在想看官们多少会有点疲乏~ 所以我在规划了几篇『中场休息』...