几分钟时间,试着学一下抽象工厂模式
几分钟时间,试着学一下抽象工厂模式
简介
本文准备介绍一下,工厂模式中的最后一种模式——抽象工厂模式。之前介绍了,工厂方法模式感兴趣的童鞋可以看下这篇文章,讲个故事,看看能不能理解工厂方法模式。
本文通过讲故事的方法,来讲解抽象工厂模式,其中包含了大量的代码,但是代码内容都不难,很容易理解。希望您能耐心看完。
工厂模式的种类
定义
提取产品的共同特征,并将其分类。具有相同特征的产品为同一产品族。同一个产品族公用一个工厂类。即所有产品可以通过多个维度进行分类。相比于工厂方法模式,减少了具体工厂类的数量。
光看定义肯定太抽象了。接下通过具体需求,来理解一下抽象工厂模式的使用场景。
新的需求
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个角色。
- 抽象产品:ICar
- 抽象工厂:IFactory
- 实际产品:BZ4X,TangDMI,HanEV等。实际产品实现了ICar接口。
- 实际工厂:BZ4XFactory,HanEVFactory等。实际工厂实现了IFactory接口。
工厂方法模式特点
每新增一个产品,就会出现一个相对应的工厂。这样的好处是,每次新增产品不会修改已有代码,满足了开闭原则。 可是缺点就是会造成太多的类冗余:比如说,我要新增一个小鹏P7的类,就会出现一个XiaoPengP7对象,和一个XiaoPengP7Factory工厂。如果我要新增10000个产品,就会出现10000个相关类和10000个相对应的工厂。这样会造成Class文件太多。
抽象工厂模式角色
相较于工厂方法模式大量产品类,类的总数过多的情况。抽象工厂模式可以一定程度上的解决这个问题。
- 抽象产品:IEV,IHEV
- 抽象工厂:IFactory
- 实际产品:BZ4X,TangDMI,HanEV等。实际产品中根据种类区别,分别实现了IEV接口以及IHEV接口
- 实际工厂:BYDFactory,ToyotaFactory等。实际工厂实现了IFactory接口。
抽象工厂将所有产品进行了更细一步的划分,将车型分成了电动车EV,混动车HEV。并且出现了产品族的概念。
所有比亚迪的电动车和混动车可以看作是一个比亚迪汽车产品族。
丰田的电动车和混动车是一个丰田汽车的产品族。
同一品牌的汽车共用一个Factory工厂,这个Factory中可以生产EV车型和HEV车型。
总结
抽象工厂可以一定程度上的解决工厂方法模式类数量过多的问题。所以可以把它看作工厂方法模式在特定情况下的一种特殊写法。产品族的概念,就是将所有产品进行分类的一种方式。这个产品族没有特殊的定义,可以根据实际情况进行分类。比如上面例子中,我是以车企进行的分类。但是换个思路,也可以通过电动车或者混动车进行分类。那样的话,出现的就不是BYDFactory和ToyotaFactory而是EVFactory和BYDFactory了。具体代码不再细写,大家理解就好。
后记
接下来是讨口子环节。
本文收录在学习设计模式的方法,少走弯路,这篇文章中。 相关代码在Github:DesignPattern, 如果感兴趣可以给项目一个Star关注一下。
写文章不易,如果您觉得对您有那么一丢丢的帮助的话,希望您能点赞支持下哈。感谢您的支持。下篇文章我们不见不散。
转载自:https://juejin.cn/post/7200297595473395773