likes
comments
collection
share

几分钟时间,试着学一下抽象工厂模式

作者站长头像
站长
· 阅读数 11

几分钟时间,试着学一下抽象工厂模式

简介

本文准备介绍一下,工厂模式中的最后一种模式——抽象工厂模式。之前介绍了,工厂方法模式感兴趣的童鞋可以看下这篇文章,讲个故事,看看能不能理解工厂方法模式

本文通过讲故事的方法,来讲解抽象工厂模式,其中包含了大量的代码,但是代码内容都不难,很容易理解。希望您能耐心看完。

工厂模式的种类

几分钟时间,试着学一下抽象工厂模式

定义

提取产品的共同特征,并将其分类。具有相同特征的产品为同一产品族。同一个产品族公用一个工厂类。即所有产品可以通过多个维度进行分类。相比于工厂方法模式,减少了具体工厂类的数量。

光看定义肯定太抽象了。接下通过具体需求,来理解一下抽象工厂模式的使用场景。

新的需求

2023年,新能源汽车市场前景一片大好。毕业生小白通过组长的帮忙,学会了工厂方法模式的使用。同时写了一篇文章,讲个故事,看看能不能理解工厂方法模式

这次,组长又把他叫了过来,跟他说,丰田最近新出了一款电动车叫做BZ4X。比亚迪也新出了电动车汉EV和唐DMI。你啊,把这几款车,连同卡罗拉双擎,一起录入到程序里。让用户可以快速的查询到这几款车的相关信息。

解决办法

由于小白之前已经学会了工厂方法模式,这次直接现学现用,写出了工厂方法模式的写法。

工厂方法的解决办法

抽象产品 ICar

小白抽取了所有汽车产品中的共同特性(价格,名称,种类),将他们抽象到了一个接口ICar中。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 17:39
 * Detail(详情):抽象产品
 */
public interface ICar {
    void name();
    void price();
    void kind();

    default void detail(){
        System.out.println("-------分割线-------");
        name();
        kind();
        price();
    }
}

具体产品 丰田BZ4X

让丰田的电动车BZ4X,实现了ICar接口。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:02
 * Detail(详情):丰田BZ4X,电动车
 */
public class BZ4X implements ICar {
    @Override
    public void name() {
        System.out.println("丰田-BZ4X");
    }

    @Override
    public void price() {
        System.out.println("23.48w");
    }

    @Override
    public void kind() {
        System.out.println("电动汽车");
    }

    public void biggestContinueVoyageCourse(){
        System.out.println("最大续航:615km");
    }
}

具体产品 丰田卡罗拉双擎

让丰田的混动车卡罗拉双擎,实现了ICar接口。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:08
 * Detail(详情):丰田卡罗拉双擎,混动车
 */
public class CorollaHybrid implements ICar {
    @Override
    public void name() {
        System.out.println("丰田-卡罗拉双擎");
    }

    @Override
    public void price() {
        System.out.println("13.98w");
    }

    @Override
    public void kind() {
        System.out.println("混动汽车");
    }

    public void fuelConsumption(){
        System.out.println("油耗4.2L/100km");
    }
}

具体产品 比亚迪唐DMI

让比亚迪的混动车唐DMI,实现了ICar接口。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 17:59
 * Detail(详情):比亚迪唐DMI,混动车
 */
public class TangDMI implements ICar {
    @Override
    public void name() {
        System.out.println("比亚迪-唐DMI");
    }

    @Override
    public void price() {
        System.out.println("28.18w");
    }

    @Override
    public void kind() {
        System.out.println("混动汽车");
    }

    public void fuelConsumption(){
        System.out.println("油耗3.64L/100km");
    }
}

具体产品 比亚迪汉EV

让比亚迪的电动车汉EV,实现了ICar接口。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 17:39
 * Detail(详情):比亚迪汉EV,电动车
 */
public class HanEV implements ICar {

    @Override
    public void name() {
        System.out.println("比亚迪-汉EV");
    }

    @Override
    public void price() {
        System.out.println("23w");
    }

    @Override
    public void kind() {
        System.out.println("电动汽车");
    }

    public void biggestContinueVoyageCourse(){
        System.out.println("最大续航:605km");
    }
}

抽象工厂 IFactory

有了产品,就要有相对应的汽车工厂。抽象出一个接口,让这个接口可以生成ICar。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:17
 * Detail(详情):抽象工厂接口
 */
public interface IFactory {
    ICar buildCar();
}

具体工厂,BZ4X工厂

实现了抽象工厂接口的具体工厂,用来生产BZ4X。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:23
 * Detail(详情):BZ4X,具体工厂
 */
public class BZ4XFactory implements IFactory{
    @Override
    public ICar buildCar() {
        return new BZ4X();
    }
}

具体工厂,丰田卡罗拉工厂

实现了抽象工厂接口的卡罗拉工厂,用来生产丰田卡罗拉双擎。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:19
 * Detail(详情):丰田卡罗拉,具体工厂
 */
public class CorollaFactory implements IFactory {
    @Override
    public ICar buildCar() {
        return new CorollaHybrid();
    }
}

具体工厂,汉EV工厂

实现了抽象工厂接口的汉EV工厂,用来生产汉EV。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:19
 * Detail(详情):汉EV,具体工厂
 */
public class HanEVFactory implements IFactory {
    @Override
    public ICar buildCar() {
        return new HanEV();
    }
}

具体工厂,唐DMI工厂

唐DMI工厂,用来生产唐DMI。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:19
 * Detail(详情):唐DMI,具体工厂
 */
public class TangDMIFactory implements IFactory {
    @Override
    public ICar buildCar() {
        return new TangDMI();
    }
}

客户端代码

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:26
 * Detail(详情):客户端
 */
public class Client {
    public static void main(String[] args) {
        // 我要获取一辆汉EV
        IFactory hanEVFactory = new HanEVFactory();
        hanEVFactory.buildCar().detail();
        // 我要获取一辆唐DMI
        IFactory tangDMIFactory = new TangDMIFactory();
        tangDMIFactory.buildCar().detail();
        // 我要获取一辆卡罗拉
        IFactory corollaFactory = new CorollaFactory();
        corollaFactory.buildCar().detail();
        // 我要获取一辆BZ4X
        IFactory bz4XFactory = new BZ4XFactory();
        bz4XFactory.buildCar().detail();
    }
}

工厂方法客户端运行结果

上述代码的运行结果如下。 几分钟时间,试着学一下抽象工厂模式

工厂方法的窘境

小白,看着自己的代码,感觉很满意。这时组长大宇过来看了一眼,略带欣慰的说道,不错上次刚教你的工厂方法模式,这么快就学会了。 不过你这么写有一个小弊端,就是市面上那么多的车,你这得写多少个工厂啊? 小白听到这话,下意识的回答到,有多少车就写多少个工厂呗。刚说完这句话,他就明白为什么组长会问这个问题了。 过多的产品会导致出现过的工厂。会造成Class类文件过多,包的体积会变大。刚刚对自己很满意的小白,瞬间陷入了沉思。

特殊的工厂方法——抽象工厂

组长看着紧皱眉头的小白,笑着说道。”小白你先别急,工厂模式里,除了工厂方法模式不是还有一个抽象工厂呢嘛。抽象工厂就是为了解决你碰到的这个问题,而产生的。“

”你可以把这些汽车啊,进行分类,比如说把所有车通过品牌进行分类。这样你例子中的车就被分成比亚迪品牌(唐DMI和汉EV),丰田品牌(卡罗拉双擎,BZ4X)。“

”你再观察下,这4辆车中,唐DMI和卡罗拉双擎是混动车。BZ4X和汉EV是电动车。那么我们就可以根据他们的种类进行具体区分。“

”比亚迪品牌下的车可以区分为电动车(EV)和混动车(HEV)。丰田品牌下的车也可以分为电动车(EV)和混动车(HEV)。“

”接下来,我们可以把同一品牌下不同种类的车型,看作一个产品族。这么说可能有点绕,我把代码发给你,你直接看代码理解下吧。“

抽象工厂代码

小白拿到代码后,对比着刚才大宇组长的话,思索起来。

抽象电动汽车接口 IEV

将所有产品进行分类,电动车抽象到IEV接口中。

public interface IEV {
    void name();
    void price();
    default void kind(){
        System.out.println("EV 电动车");
    }

    void biggestContinueVoyageCourse();
    default void detail(){
        System.out.println("-------分割线-------");
        name();
        kind();
        biggestContinueVoyageCourse();
        price();
    }
}

抽象混动汽车产品 IHEV

将所有产品进行分类,混动车抽象到IHEV接口中。

public interface IHEV {
    void name();
    void price();
    default void kind(){
        System.out.println("HEV(混动汽车)");
    }

    void fuelConsumption();

    default void detail(){
        System.out.println("-------分割线-------");
        name();
        kind();
        fuelConsumption();
        price();
    }
}

具体产品 丰田BZ4X

具体产品要实现抽象产品接口。丰田BZ4X是电动车,所以实现IEV接口。

public class BZ4X implements IEV {
    @Override
    public void name() {
        System.out.println("丰田-BZ4X");
    }

    @Override
    public void price() {
        System.out.println("23.48w");
    }

    @Override
    public void kind() {
        System.out.println("电动汽车");
    }

    @Override
    public void biggestContinueVoyageCourse(){
        System.out.println("最大续航:615km");
    }
}

具体产品 丰田卡罗拉双擎

丰田卡罗拉双擎是混动车,所以实现IHEV接口。

public class CorollaHybrid implements IHEV {
    @Override
    public void name() {
        System.out.println("丰田-卡罗拉双擎");
    }

    @Override
    public void price() {
        System.out.println("13.98w");
    }

    @Override
    public void kind() {
        System.out.println("混动汽车");
    }

    public void fuelConsumption(){
        System.out.println("油耗4.2L/100km");
    }
}

具体产品 比亚迪唐DMI

比亚迪唐DMI是混动车,所以实现IHEV接口。

public class TangDMI implements IHEV {
    @Override
    public void name() {
        System.out.println("比亚迪-唐DMI");
    }

    @Override
    public void price() {
        System.out.println("28.18w");
    }

    @Override
    public void kind() {
        System.out.println("混动汽车");
    }

    @Override
    public void fuelConsumption(){
        System.out.println("油耗3.64L/100km");
    }
}

具体产品 比亚迪汉EV

比亚迪汉EV是电动车,所以实现IEV接口。

public class HanEV implements IEV {

    @Override
    public void name() {
        System.out.println("比亚迪-汉EV");
    }

    @Override
    public void price() {
        System.out.println("23w");
    }

    @Override
    public void kind() {
        System.out.println("电动汽车");
    }

    @Override
    public void biggestContinueVoyageCourse(){
        System.out.println("最大续航:605km");
    }
}

抽象工厂接口

抽象出一个IFactory工厂接口,接口中可以生产EV车型和HEV车型。

/**
 * Author(作者):jtl
 * Date(日期):2023/2/14 18:17
 * Detail(详情):抽象工厂接口
 */
public interface IFactory {
    IEV creatEV();

    IHEV creatHEV();
}

具体工厂,BYD工厂

让具体工厂,BYDFactory实现IFactory接口。生产的EV产品为HanEV。生产的HEV产品为TangDMI。

public class BYDFactory implements IFactory{

    @Override
    public IEV creatEV() {
        return new HanEV();
    }

    @Override
    public IHEV creatHEV() {
        return new TangDMI();
    }
}

具体工厂,Toyota工厂

让具体工厂,ToyotaFactory实现IFactory接口。生产的EV产品为BZ4X。生产的HEV产品为CorollaHybrid。

public class ToyotaFactory implements IFactory{

    @Override
    public IEV creatEV() {
        return new BZ4X();
    }

    @Override
    public IHEV creatHEV() {
        return new CorollaHybrid();
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 获取BYD工厂联系方式
        IFactory bydFactory = new BYDFactory();
        // BYD工厂生产电动车-汉EV
        bydFactory.creatEV().detail();
        // BYD工厂生产混动车-唐DMI
        bydFactory.creatHEV().detail();

        // 获取丰田工厂联系方式
        IFactory toyotaFactory = new ToyotaFactory();
        // 丰田工厂生产电动车-BZ4X
        toyotaFactory.creatEV().detail();
        // 丰田工厂生产混动车-卡罗拉双擎
        toyotaFactory.creatHEV().detail();
    }
}

抽象工厂运行结果

几分钟时间,试着学一下抽象工厂模式

看着运行结果,小白一脸佩服的看着大宇组长对他说道,这样就可以减少很多的工厂类了。大宇组长回答到,是的这样会减少很多工厂类。减少打包后的程序的体积。但是抽象工厂的前提,必须是在所有的产品可以进行区分,分出产品族的基础上。如果刚才的例子里,多了一个比亚迪的自行车,抽象工厂是不是就没法用了。小白恍然大明白的回答道,组长你说的好有道理啊。

工厂方法与抽象工厂的区别

工厂方法模式角色

通过上述例子可以看出,工厂方法模式中有4个角色。

  1. 抽象产品ICar
  2. 抽象工厂IFactory
  3. 实际产品BZ4XTangDMIHanEV等。实际产品实现了ICar接口
  4. 实际工厂BZ4XFactoryHanEVFactory等。实际工厂实现了IFactory接口

工厂方法模式特点

每新增一个产品,就会出现一个相对应的工厂。这样的好处是,每次新增产品不会修改已有代码,满足了开闭原则。 可是缺点就是会造成太多的类冗余:比如说,我要新增一个小鹏P7的类,就会出现一个XiaoPengP7对象,和一个XiaoPengP7Factory工厂。如果我要新增10000个产品,就会出现10000个相关类和10000个相对应的工厂。这样会造成Class文件太多。


抽象工厂模式角色

相较于工厂方法模式大量产品类,类的总数过多的情况。抽象工厂模式可以一定程度上的解决这个问题。

  1. 抽象产品IEVIHEV
  2. 抽象工厂IFactory
  3. 实际产品BZ4XTangDMIHanEV等。实际产品中根据种类区别,分别实现了IEV接口以及IHEV接口
  4. 实际工厂BYDFactoryToyotaFactory等。实际工厂实现了IFactory接口

抽象工厂将所有产品进行了更细一步的划分,将车型分成了电动车EV混动车HEV。并且出现了产品族的概念。

所有比亚迪的电动车混动车可以看作是一个比亚迪汽车产品族

丰田的电动车混动车是一个丰田汽车的产品族

同一品牌的汽车共用一个Factory工厂,这个Factory中可以生产EV车型HEV车型

总结

抽象工厂可以一定程度上的解决工厂方法模式类数量过多的问题。所以可以把它看作工厂方法模式在特定情况下的一种特殊写法。产品族的概念,就是将所有产品进行分类的一种方式。这个产品族没有特殊的定义,可以根据实际情况进行分类。比如上面例子中,我是以车企进行的分类。但是换个思路,也可以通过电动车或者混动车进行分类。那样的话,出现的就不是BYDFactoryToyotaFactory而是EVFactoryBYDFactory了。具体代码不再细写,大家理解就好。

后记

接下来是讨口子环节。

本文收录在学习设计模式的方法,少走弯路,这篇文章中。 相关代码在Github:DesignPattern, 如果感兴趣可以给项目一个Star关注一下。

写文章不易,如果您觉得对您有那么一丢丢的帮助的话,希望您能点赞支持下哈。感谢您的支持。下篇文章我们不见不散。