工厂模式再思考

2022-09-30 08:56:52 浏览数 (2)

工厂方法有简单工厂、工厂方法、抽象工厂一些说法,而且还不统一,在对照着看了几遍《设计模式之禅》和《Head First 设计模式》后,我觉得《设计模式之禅》 的抽象工厂其实是《Head First 设计模式》里的简单工厂(它们都是二维的),而《Head Fisrt 设计模式》的抽象工厂其实更复杂些(其实是三维了)。

所以为了更方便自己理解,我从维度的角度去看,看产品需要考虑几个维度。

一维

工厂内选择

最简单的产品,比如面条一个维度是它的种类,要生产牛肉面(BeefNoodles)、哨子面(PorkNoodles)、云吞面(WontonNoodles)等。

首先有产品面条(Noodles)

代码语言:javascript复制
public interface Noodles {}
public class BeefNoodles implements Noodles {}
public class PorkNoodles implements Noodles {}
public class WontonNoodles implements Noodles {}

然后需要工厂(Factory)

代码语言:javascript复制
public interface Factory {
    public Noodles makeNoodles(String type);
}
public class NoodlesFactory implements Factory {
    public Noodles makeNoodles(String type) {
        if ("Beef".equals(type)) {
            return new BeefNoodles();
        } else if ("Pork".equals(type)) {
            return new PorkNoodles();
        } else if ("Wonton".equals(type)) {
            return new WontonNoodles();
        }
    }
}

然后高层店铺根据参数选择生产不同的产品。

代码语言:javascript复制
public class NoodlesStore {
    public void test() {
        Noodles beefNoodles = (new NoodlesFactory()).makeNoodles("Beef")
        // or
        Noodles porkNoodles = (new NoodlesFactory()).makeNoodles(PorkNoodles.class);
    }
}

缺点在于每新增一种产品,比如加了个油泼面,就需要修改工厂方法内部代码,显然者太不符合开闭原则了。有些时候说这都不配称为工厂模式。

反射简化

代码语言:javascript复制
public interface Factory {
    public <T extends Noodles> T makeNoodles(Class<T> c);
}
public class NoodlesFactory implements Factory {
    public <T extends Noodles> T makeNoodles(Class<T> c) {
        Noodles noodles = null;
        try {
            noodles = (T)Class.forName(c.getName()).newInstance(); 
        } catch (Exception e) {

        }
    }
}
public class NoodlesStore {
    public void test() {
        Noodles beefNoodles = (new NoodlesFactory()).makeNoodles(BeefNoodles.class);
    }
}

反射总感觉是无招可使时的不得已的手段,代码上简化,但理念其实是一致的,还是在工厂的方法内部,根据不同的参数生产不同的产品。所以就将它们当做一种方式。

一个工厂生产一类产品

代码语言:javascript复制
public interface Factory {
    public Noodles makeNoodles(); // 不需要参数了
}
public class BeefNoodlesFactory implements Factory {
    public Noodles makeNoodles() {
        return new BeefNoodles();
    }
}
// 其它工厂类似

public class NoodlesStore {
    public void test() {
        Noodles beefNoodles = (new BeefNoodlesFactory()).makeNoodles();
    }
}

可以看作是将工厂内部的选择判断,上移然后平铺到工厂本身上面,变成了有多个工厂。

这样改造后,底层的扩展性强,增加了油泼面,就增加一个油泼面工厂。

其实后面更复杂的更多维度的情况,最基本的两种骨架已经出来了,一种是工厂内部的选择,一种是工厂本身的选择。

二维

多一个维度,比如放辣椒的(Red)、清汤的(White),同样的牛肉面,既可以做辣椒牛肉面,又可以做清汤牛肉面。

也就是《Head First 设计模式》里讲的简单工厂和《设计模式之禅》讲的抽象工厂。

一族产品类

首先将产品类按照两种维度组合起来。那么就需要定义 RedBeefNoodles、WhiteBeefNoodles、RedPorkNoodles、WhiteBeefNoodles、RedWontonNoodles、WhiteWontonNoodles 六种产品类。

在《Head First 设计模式》里,举的例子可以认为将 Store 作为一个维度,并且 Store 当做工厂。

选择分支控制一个维度

工厂任意选择一个维度,比如依然用原来维度,那么工厂还是 BeefNoodlesFactory、PorkNoodlesFactory、WontonNoodlesFactory。

代码语言:javascript复制
public interface Factory {
    public Noodles makeNoodles(String flavor); // 需要另一个维度的参数
}
public class BeefNoodlesFactory implements Factory {
    public Noodles makeNoodles(String flavor) {
        if (flavor.equals("red")) {
            return new RedBeefNoodles();
        } else {
            return new WhiteBeefNoodles();
        }
    }
}

public class NoodlesStore {
    public void test() {
        Noodles redBeefNoodles = (new BeefNoodlesFactory()).makeNoodles("red");
    }
}
方法控制一个维度

《设计模式之禅》的理论应该是这样写

代码语言:javascript复制
public interface Factory {
    public Noodles makeRedNoodles();
    public Noodles makeWhiteNoodles();
}
public class BeefNoodlesFactory implements Factory {
    public Noodles makeRedNoodles() {
        return new RedBeefNoodles();
    }

    public Noodles makeWhiteNoodles() {
        return new WhiteBeefNoodles();
    }
}

public class NoodlesStore {
    public void test() {
        Noodles redBeefNoodles = (new BeefNoodlesFactory()).makeRedNoodles();
    }
}

就是不通过参数去控制一个维度的变量,而是让工厂通过提供多个方法来控制,我认为者两者的本质是一样的,都是在工厂内部去构建另一个维度。

假设增加了一种藤椒发麻(Green)的口味,对于第一种,每一个工厂都要加一个 else 分支,对于第二种,每一个工厂都要加一个 makeGreenNoodles 方法,相比较而言,第二种通过方法定义好规则来说可控性更强。

再回溯回去,对于一维来说,也可以这样写,但是由于只要一个工厂,不像二维的有多个,所以增加种类时,代码修改区别不大。

两族产品类

参考《Head First 设置模式》的抽象工厂的思路,其实产品类也可以不必平铺。就是产品类依然只有一个维度的判断,即还是 BeefNoodles、PorkNoodles、WontonNoodles,那么如果区分出来是 Red 还是 White 的,通过产品类构造时传入不同的工厂来控制,这种工厂不生产最终的产品,只负责自己维度的生产。

首先创建口味调料产品(Flavor),可能就是汤吧。

代码语言:javascript复制
public interface Flavor {}
public class RedFlavor implements Flavor {}
public class WhiteFlavor implements Flavor {}

区别在于不是只有最终的产品类了,还将一个维度的参数变成一种中间产品。

然后使用一维的工厂方法去创建生产 Flavor 的工厂。

代码语言:javascript复制
public interface FlavorFactory {
    public Flavor createFlavor();
}
public class RedFlavorFactory implements FlavorFactory {
    public Flavor createFlavor() {
        return new RedFlavor();
    }
}
public class WhiteFlavorFactory implements FlavorFactory {
    public Flavor createFlavor() {
        return new WhiteFlavor();
    }
}

修改原来维度的面条类,参数用 FlavorFactory。

代码语言:javascript复制
public class BeefNoodles implements Noodles {
    private Flavor flavor;
    public BeefNoodles(FlavorFactory flavorFactory) {
        this.flavor = flavorFactory.createFlavor(); // 组合了另一个维度
    }
}

这样当传入的 FlavorFactory 生产的是 Red 的,那么就是辣椒牛肉面,生产的是 White 的,就是清汤牛肉面。

代码语言:javascript复制
public class NoodlesStore {
    public void test() {
        FlavorFactory factory = new RedFlavorFactory();
        Noodles redBeefNoodles = new BeefNoodles(factory);
    }
}

总结:

  • 第一种:
    • 最终产品按照两种维度写出所有子类
    • 多个工厂本身作为一个维度
    • 工厂内部通过选择分支或者多个方法当做另一个维度
  • 第二种:
    • 每一个维度都写独立的一个产品族
    • 按照写一维的方法去写一个维度产品的工厂
    • 将这种工厂作为另一个维度产品的参数,类似数学的复合函数,这样组合出多个维度的产品

从最少知识原则角度来考虑,还是第一种好,作为高层调用的 NoodlesStore,只要知道一个工厂就行了,而第二种,需要知道一个维度的工厂,还要负责创建另一个维度的产品。

三维至多维

假设多了一个维度,比如是热的(Hot)还是冷的加冰的(Cold)。按照二维的思路来。

产品类再平铺开来

那么现在 Noodles 类就是下面这样。

1.jpg

可见维度越多,一族产品类深度越深,类越多。

代码语言:javascript复制
public interface Factory {
    public Noodles makeNoodles(String temp);
}
public class HotNoodlesFactory implements Factory {
    public Noodles makeNoodles(String type, String flavor) {
        if (type.equals("beef")) {
            if (flavor.equals("red")) {
                return new HotRedBeefNoodles();
            } else {
                return new HotWhiteBeefNoodles();
            }
        } else if (type.equals("pork")) {
            if (flavor.equals("red")) {
                ...
            } else {
                ...
            }
        } else if (type.equals("wonton")) {
            ...
        }
    }
}

或者

代码语言:javascript复制
public interface Factory {
    public Noodles makeBeefNoodles(String flavor);
    public Noodles makePorkNoodles(String flavor);
    public Noodles makeWontonNoodles(String flavor);
}

public class HotNoodlesFactory implements Factory {
    public Noodles makeBeefNoodles(String flavor) {
        if (flavor.equals("red")) {
            return new HotRedBeefNoodles();
        } else {
            return new HotWhiteBeefNoodles();
        }
    }
    public Noodles makePorkNoodles(String flavor) {
        ...
    }
}

再或者

代码语言:javascript复制
public interface Factory {
    public Noodles makeRedBeefNoodles();
    public Noodles makeWhiteBeefNoodles();
    public Noodles makeRedPorkNoodles();
    public Noodles makeWhitePorkNoodles();
    public Noodles makeRedWontonNoodles();
    public Noodles makeWhiteWontonNoodles();
}

反正就是各种基础的思路进行复合,排列组合。

还可以将之前的二维的工厂当场一个一维和现在多出的一维复合成二维。

这是二维时的工厂

代码语言:javascript复制
public interface FlavorFactory {
    public Noodles makeNoodles(String flavor); // 需要另一个维度的参数
}
public class BeefNoodlesFactory implements FlavorFactory {
    public Noodles makeNoodles(String flavor) {
        if (flavor.equals("red")) {
            return new RedBeefNoodles();
        } else {
            return new WhiteBeefNoodles();
        }
    }
}

下面进行复合

代码语言:javascript复制
public interface TempFactory {
    public Noodles makeNoodles(FlavorFactory flavor); 
}
public class HotNoodlesFactory implements TempFactory {
    public Noodles makeNoodles(FlavorFactory flavor) {
        if (flavor instanceOf RedBeefNoodles) {
            return new HotRedBeefNoodles();
        } else {
            return new HotWhiteBeefNoodles();
        }
    }
}

两族产品类

2.png

温度的维度不做产品类,而是单独创建一族产品。

代码语言:javascript复制
public interface Temp {}
public class HotTemp implements Temp {}
public class ColdTemp implements Temp {}

这一族产品的工厂,就用选择判断来返回不同对象

代码语言:javascript复制
public interface TempFactory {
    public Temp createTemp();
}
public class HotTempFactory implements TempFactory {
    public Temp createTemp() {
        return new HotTemp();
    }
}
public class ColdTempFactory implements TempFactory {
    public Temp createTemp() {
        return new ColdTemp();
    }
}

然后所有产品类中加个参数 TempFactory,比如

代码语言:javascript复制
public class RedBeefNoodles implements BeefNoodles {
    private Temp temp;
    public RedBeefNoodles(TempFactory tempFactory) {
        // 自己本身包含 Red,Beef 两个维度,加上参数传进来的工厂生产第三个维度
        this.temp = tempFactory.createTemp();
    }
}

public class NoodlesStore {
    public void test() {
        TempFactory factory = new HotTempFactory();
        Noodles hotRedBeefNoodles = new RedBeefNoodles(factory);
    }
}

和二维的两组产品类可以认为是一样的。

三族产品类

代码语言:javascript复制
public interface Temp {}
public class HotTemp implements Temp {

}

public interface Flavor {}
pulbic class RedFlavor implements Flavor {
    private Temp temp;
    public RedFlavor(TempFactory tempFactory) {
        // 用另一个维度的工厂作为参数,来将一个维度组合成两个维度
        this.temp = tempFactory.createTemp();
    }
}

public interface Noodles {}
public class BeefNoodles implements Noodles {
    private Flavor flavor;
    // 本身产品一个维度 Beef
    public BeefNoodles(FlavorFactory flavorFactory, TempFactory tempFactory) {
        // FlavorFactory 返回第二个维度
        this.flavor = flavorFactory.createFlavor(tempFactory);
        // flavor 内部有第三个维度的 temp
    }
}

每一族产品都有对应的工厂。

代码语言:javascript复制
public inteface TempFactory {
    public Temp createTemp();
}
public class HotTempFactory implements TempFactory {
    public Temp createTemp() {
        return new HotTemp();
    } 
}

public inteface FlavorFactory {
    public Flavor createFlavor(TempFactory factory);
}
public class RedFlavorFactory implements FlavorFactory {
    public Flavor createFlavor(TempFactory factory) {
        return new RedFlavor(factory);
    } 
}

上层使用

代码语言:javascript复制
public class NoodlesStore {
    public void test() {
        TemmpFactory tempFactory = new HotTempFactory();
        FlavorFactory flavorFactory = new RedFlavorFactory();
        Noodles hotRedNoodles = new BeefNoodles(flavorFactory, tempFactory);
        
    }
}

就是单个方法进行组合。产品族全部分开,虽然上面说不符合最少知识原则,但是产品类不至于都塞在一族里,看着会很复杂,假设维度更多,那更不得了。而这种每个维度单独创建一类产品,每一类产品各有一类工厂,然后进行复合,就是用起来有点绕。

按照上面思路下来,无论多少个维度都可以这样去做,只不过维度越多越复杂,不大可能遇到。

0 人点赞