本文将重点介绍几种工厂设计模式:简单工厂、工厂方法模式、抽象工厂模式和建造者模式。这几种设计模式在生产制造的流程下层层递进,可以满足不同的使用场景。在实际运用时,没有一个万能的工厂模式可以套用,要结合具体业务场景选择合适的设计模式。大部分童鞋很容易将这几种工厂模式弄混淆,所以建议大家收藏+关注,以后要用到了方便查阅。
为什么需要工厂模式?
在面向对象的世界里,世间万物皆是制造(实例化和初始化)出来的。如果只是单单构造出一两个对象,代码相对来说并不复杂,可是一旦需要构造成百上千的对象,每种对象的构造过程又不尽相同,这个时候就需要利用工厂方法进行封装,达到降低系统耦合和代码复用的目的。
在现实生活中,我们可以找到很多这样的例子。例如,想要在飞行游戏里生产一辆坦克和一架飞机,假设即使靠我自己的能力也能生产出来,那也是想当复杂的一件事情。不仅如此,如果我需要在不同的时间,不同的地点生产100辆坦克和50架飞机,我是不是就需要将生产的过程重复150次?
有经验的程序员立刻就能想到将坦克和飞机的制造过程独立成一个方法,每次生产坦克或飞机的时候调用这个方法即可,这样就达到了代码复用的目的。没错,这正是简单工厂的雏形,只不过这个生产方法是交由一个工厂类来管理。
简单工厂
我们以飞行游戏生产敌人为示例,介绍一下简单工厂的实现原理。首先我们对游戏敌军建模。坦克和飞机作为敌军肯定存在一定的共性,它们都需要一个坐标属性用来描述位置,它们还需要一个在地图上绘制的行为属性。所以我们可以给它们设计一个敌人的抽象类,如图所示。
坦克类继承敌人这个抽象父类,实现代码如图所示。
飞机类继承敌人这个抽象父类,实现代码如图所示。
通过上述代码可以看出,坦克和飞机都继承自敌人这个抽象父类,共用了父类的坐标属性,并且实现了各自的绘制方法show()。现实中的游戏角色肯定没有这么简单,这个示例只是为了让大家更好的理解简单工厂的原理。
游戏敌人的建模完成以后,就需要对游戏敌人进行实例化和初始化了。显然这个过程需要两步:
1、构造敌人时初始化坐标位置;
2、根据坐标在地图上绘制敌人。
我们在客户端里实现这部分逻辑,如图所示。
客户端的代码非常简单,我们生产了一架飞机和一辆坦克,它们的横坐标是随机的,纵坐标是0,表示敌人登场亮相在地图的最上面。
看出来有什么问题了吗?对,前面我们已经提到了,生产一个敌人无所谓,但是如果要生产很多的敌人,为了代码的复用,我们可以将生产过程放到一个方法里,这个方法用一个工厂类来管理,通过传递敌人的类型返回敌人对象,这就是简单工厂。
有了简单工厂类,我们在客户端里的实现逻辑就可以省去不少重复代码。
这样看来代码确实更加简洁明了。以后我们想要在任何地方生产敌人,都可以通过直接调用简单工厂类的create()方法来实现。
小结
使用目的,是为了将生产流程抽离出来,达到复用和解耦的目的。
实现方式,设计一个工厂父类,实现一个生产方法,该方法通过判断工厂类型来决定执行哪个生产流程。
使用场景,是生产需求简单不多变时。同时,只生产一种产品时也没有必要使用简单工厂。
缺陷,虽然简单工厂可以共用产品的生产过程,但其扩展性不好。因为简单工厂的生产方法和产品类型产生了强耦合,所有生产方式堆积在一个简单工厂类中。
工厂方法模式
前面已经提到了简单工厂的缺点是扩展性不好。由于工厂类的生产方法与产品类型强耦合,所以每增加一个新的产品类型,就需要修改工厂类的生产方法。
那么,有没有什么好办法可以解决这个问题呢?当然有了,我们可以拆分生产方法,将产品模式抽象化、多态化,这就可以实现生产方法的多态性。怎么,看不懂我说的吗?下面我要说人话了。工厂方法模式的核心是定义一个工厂接口,以达到确定工业制造标准的目的。如果还是没明白什么意思,那我就举一个简单的例子你就会明白了。
小米、华为和联想都有自己的鼠标生产工厂,尽管它们生产出来的鼠标从外观上来看大小各异,色彩缤纷,但是它们生产出来的有线鼠标都需要遵守USB接口的工业制造标准,否则用户就用不了它们的鼠标。这里的标准映射到面向对象的设计模式当中,指的就是工厂接口。只要各个制造工厂都实现工厂接口,以后增加新的产品就不会影响到以前的产品及其工厂。
我们这次使用工厂方法模式实现上面的飞行游戏生产敌人的需求。假设现在我们除了要生产坦克和飞机以外,还要在每一关的最后增加一个大Boss,如果使用简单工厂来实现,需要修改简单工厂类的create()方法。根据开闭原则(对扩展开放,对修改关闭),我们应该避免修改以前的代码逻辑,所以我们这次使用工厂方法模式来实现。
首先制定工业制造标准:一个工厂接口。
基于工厂接口,实现坦克工厂。
基于工厂接口,实现飞机工厂。
基于敌人抽象类,实现大Boss。
基于工厂接口,实现大Boss工厂。可以发现在生产大Boss时和生产坦克、飞机的不同,大Boss的亮相是在屏幕顶部的中间。
下面是客户端实现代码,分别生产了5辆坦克,5架飞机和1个大Boss。
使用工厂方法模式之后的客户端,在生产各种不同的敌人时都采用了统一的生产标准,这样做的好处就是不但代码更加精炼,而且以后再生产新的类型的敌人也不会对已经实现的原有代码做任何调整。
小结
使用目的,为了增加产品的扩展性和灵活性。
实现方式,设计一个工厂制造接口作为生产方法的标准,满足了工厂的多态性。
使用场景,适用于需要不断增加新的产品的场景,并且每种产品的制造标准统一,没有差别。
缺陷,每增加一个产品就需要增加一个对应的工厂,容易造成工厂泛滥。
工厂方法模式可被视为对简单工厂模式的进一步升华与扩展,而工厂方法模式才真正称得上是设计模式。在工厂方法模式的结构中,不仅对产品类别进行了区分,同时对工厂也进行了分类处理。相较于将所有制造逻辑集中在一个单一的简单工厂类内部,更倾向于将这些生产方式分配到各个特定的子类工厂中去具体实现。这种设计方式极大地增强了工厂的抽象性和多态性,有效避免了因新增产品类导致频繁修改同一工厂类的问题,使得后期代码维护和扩展工作变得更加直观、便捷。
抽象工厂模式
如果说工厂方法是对制造方法的抽象化,那么抽象工厂就是对工厂本身的抽象化。为了调和工厂泛滥的矛盾,抽象工厂像工厂方法模式一样,通过接口实现产品的扩展性,但它们之间最大的不同在于抽象工厂不局限于生产一种产品,用人话来解释就是一个抽象工厂有多个工厂方法,每个工厂方法生产一种产品。所以,抽象工厂模式可以看作是工厂方法模式的加强版。
举个栗子,华为手机有P系列和Mate系列,苹果手机有iphone系列与iphone plus系列。如果采用工厂方法模式来生产华为和苹果手机,那么就需要P系列、Mate系列、iphone系列和iphone plus系列四个工厂来分别生产。但是如果改用抽象工厂模式,我们可以设计一个抽象工厂接口,该接口有两个工厂方法:小尺寸手机生产方法和大尺寸手机生产方法。
首先创建一个手机虚类。一个手机有长、宽、高和颜色属性,还有一个打电话的行为属性。
然后创建小尺寸手机虚类和大尺寸手机虚类。小尺寸手机长100,宽50,高10,大尺寸手机长150,宽70,高10,大尺寸手机屏幕更大。
分别继承小尺寸手机和大尺寸手机,得到华为和苹果的各两款系列机型。
有了手机产品之后,就要给对应的产品添加工厂。虽然这里我们生产了华为和苹果的各2款共4款手机,但是我们采用抽象工厂模式,只需要创建两个工厂就行。
就此我们已经完成了抽象工厂模式的架构,下面通过一个客户端对象学习一下如何使用。
工厂的工厂
理解了抽象工厂模式的原理,我们还可以更加深入一点理解一下什么是"工厂的工厂"。前面介绍的设计模式都在做一件事情,就是用工厂来生产产品。那么你是否有想过,工厂由谁来生产?也许你会说工厂在代码里面直接new就可以了嘛。对,这样当然没有错,但是有的时候工厂的创建也是需要多态性和扩展性的。
例如上面生产手机的例子。假设手机市场正在处于2023年和2024年过度阶段,要同时生产2023的老款和2024年的新款手机,也就是说我们除了要通过华为手机工厂和苹果手机工厂来区分手机品牌,还要通过手机工厂的工厂方法来区分手机尺寸(手机系列),甚至于我们还要告诉工厂是生产去年的老款还是今年的新款。所以,我们就需要再设计一个工厂,工厂的方法生产的是2023工厂或者2024工厂,这时就可以把品牌工厂(华为/苹果工厂)看作是产品。
工厂的工厂非常适合在代码里消除层层嵌套的if/else,往往在代码最开始的地方将代表产品性质的参数传入工厂构造函数,就能直接通过多态性得到我们想要的产品,而不会在代码里看到if/else判断。
小结
使用目的,防止工厂泛滥。
实现方式,一个工厂虚类,多个生产方法。工厂虚类的工厂实现类划分产品族,生产方法划分产品系列。
使用场景,适合于大规模的产品生产,特别是可以按族和系列来划分的产品。
缺陷,增加了代码的复杂度。
抽象工厂模式要求产品系列相对固定,而产品族可以灵活多变。
建造者模式
前面介绍的工厂设计模式系列,它们有一个共同点就是只能为一个产品提供一种制造方法。这和现实生活中的工厂生产产品如出一辙,李宁和安踏的工厂只能生产李宁和安踏的鞋子。
现在我们考虑这样一种场景,华为想要生产手机上使用的麒麟芯片,海思负责的芯片设计已经完成,需要一个芯片代工厂量产芯片。它们找到了台积电代工,刚开始一切顺利,芯片交付了几百万件,性能远超同类手机。美国眼看自家的苹果iphone手机干不过华为,想出阴招对台积电出口管制,不允许为华为代工芯片。无奈之下,华为只好加速国产化进程,在某些神秘国产代工厂的帮助下,华为Mate60手机实现了国产芯片全替代的壮举。为什么华为芯片的生产可以更换代工厂?
理解了前面介绍的工厂模式系列的原理,我们不难发现如果华为芯片制造使用的是这些工厂模式,那么华为寻找新的芯片代工工厂成本肯定会非常之大。那么华为是怎么做的呢?答案就是华为使用了建造者模式。建造者模式的核心就是将"制造工序标准化",从而达到"制造工艺多样化"的目的。在上述华为芯片的案例中,华为将制造工序的标准交给海思把控,这样不论是哪个代工厂以什么样的制造工艺来生产芯片都可以,前提是只要达到了海思制定的制造工序标准。
下面我们通过代码来感受一下建造者模式的魔力。这是一个关于房屋建造的案例,一家房地产公司需要建造房屋。为了简化房屋的建造,这里的房屋只由地基、墙体和房顶组成。
房屋的建造需要找一个施工方,为了保证质量,房地产公司会招投标寻找合适的施工方,同时房地产公司也提出了建造房屋的标准。标准非常简单,只需要建造地基、墙体和房顶即可,最后有一个验收的行为。
这时有两个施工方来招投标,一个是别野施工方。
还有一个是公寓施工方。
房地产商虽然制定了标准,但还是不放心施工方的质量,所以房地产商又找来了一个工程总监来把控施工流程的质量。工程总监并不关心由谁来完成房屋的建造,也不会关心施工方的制造工艺,他只需要保证施工方的建造工序。这就是前面说的"制造工序标准化,制造工艺多样化",即制造工序和制造工艺解耦。
最后,在进行招投标的时候进行成果展示。
查看执行结果。
小结
使用目的,在相同的建造工序场景下,生产出不同的产品。
实现方式,制定建造标准(Builder接口)。由指导者(Director)指导建造者实现类(ConcreteBuilder)执行标准,生产出产品(Product)。
使用场景,适用于产品复杂的场景,制造工序稳定而制造工艺多样。和工厂模式不同的是,工厂模式只需要生产出产品而不关心产品的组成,而建造者模式除了要生产出产品,还要关心产品的组成和生产过程。前者注重获得产品,后者注重如何建造并获得产品。
缺陷,生产产品的过程更加复杂,多了建造者和指导者的类。
严格来说,建造者模式不隶属于工厂设计模式家族,但在讨论中我们将两者共同探讨,主要原因在于它们均属于创建型设计模式这一大类。建造者模式,又名生成器模式,其核心作用在于处理复杂对象的构造与初始化过程,通过有序地逐个整合多个简单组件对象,最终形成一个结构复杂的完整对象实例。相较于工厂模式系列,建造者模式的独特之处在于它着重于将复杂的构建步骤从产品类和工厂类的具体实现中抽象出来,并使其独立运作。这样,一套标准化的构建流程可以灵活适应不同的产品配置,从而产生形态各异的产品实例。