设计模式之工厂模式,史上最强,不服来辩!
共 11208字,需浏览 23分钟
·
2021-07-12 14:30
设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,如果设计模式没学会,抽象能力肯定就不会太强。常见的设计模式有 23 种,今天我们只聊最简单的工厂模式。
工厂模式是属于创建型模式的,通过工厂获取一个一个的新对象。说白了,工厂就是用来 new(创建)对象的,因此把它化分到创建型这一类中。
简单工厂模式
简单工厂模式,正如其名,和名字一样简单,非常简单。下面先看一段代码:
public class FoodFactory {
public static Food makeFood(String name) {
if ("kuaican".equals(name)) {
Food kuaican = new KuaiCanFood();
kuaican.setPrice(20.00);
return kuaican;
}
if ("hamburger".equals(name)) {
Food hamburger = new HamburgerFood();
hamburger.setPrice(22.00);
hamburger.setMeat("beef");
return hamburger;
}
// ......
return new RuntimeException("not food");
}
}
需要注意的是,上面中的 KuaiCanFood 和 HamburgerFood 都继承自 Food。
简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。
我们强调职责单一原则
,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。
Java 各种框架中,简单工厂模式是非常常见的,比如JedisConnectionFactory
。
基本上 Java 中需要创建连接的框架,都是用了工厂模式。
工厂模式
简单工厂模式很简单,在一般情况下,它都能满足我们的需要。但是,在某些特殊场景,它就满足不了我们的需求了。还是拿 Redis 为例,Redis 有单机模式,主从模式,哨兵模式,Cluster 模式。每种模式的连接都不一样,因此我们需要引入多个工厂。
这种情况下,就需要引入多个简单工厂模式。比如:JedisConnectionFactory
、JredisConnectionFactory
、LettuceConnectionFactory
、SrpConnectionFactory
等等。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。
还是以 Food 为例。
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if ("套餐A".equals(name)) {
Food kuaican = new KuaiCanFood();
kuaican.setPrice(20.00);
kuaican.setName("快餐");
return kuaican;
}
if ("套餐B".equals(name)) {
Food mifan = new MiFanFood();
mifan.setPrice(18.00);
mifan.setName("五常大米");
return mifan;
}
return null;
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if ("套餐A".equals(name)) {
Food hamburger = new HamburgerFood();
hamburger.setPrice(22.00);
hamburger.setName("hamburger");
return hamburger;
}
if ("套餐B".equals(name)) {
Food sandwich = new SandwichFood();
sandwich.setPrice(25.00);
sandwich.setName("三明治");
return sandwich;
}
return null;
}
}
其中,KuaiCanFood、MiFanFood、HamburgerFood、SandwichFood 都派生自 Food。
消费者调用:
public class Consumer {
public static void main(String[] args) {
// 先选择一个具体的工厂
FoodFactory factory = new ChineseFoodFactory();
// 由选择的工厂产生具体的对象,不同的工厂造出不一样的对象
Food food = factory.makeFood("套餐A");
}
}
虽然都是调用 makeFood("套餐A") 制作套餐 A,但是,不同的工厂生产出来的完全不一样。
第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。
核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 FileFactory 接口,实现类有 AliyunOssFactory 和 TencentCosFactory,分别对应将文件写入阿里云 OSS 和腾讯云 COS 中。很显然,我们客户端或者消费者第一步就需要决定到底要实例化 AliyunOssFactory 还是 TencentCosFactory,这将决定之后的所有的操作。
工厂模式非常简单,我把他们都画到一张图上,希望大家能够看图就会:
现在会了还不是真会,一定要在实践代码中使用,不然过不了多久就会忘记!
抽象工厂模式
有了简单工厂模式,和工厂模式,为什么还有抽象工厂模式?
这就是当涉及到产品族的时候,就需要引入抽象工厂模式了。当一个产品族,存在多个不同类型的产品(比如我上面列举的 Redis 连接工厂)情况下,不同架构模式,选择不同的连接工厂的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。
RedisConnectionFactory
的例子,我就不在多说了,建议spring-data-redis
中的源码多看几遍。下面我再列举一个生活中的经典的例子,就是造一台手机。我们先不引入抽象工厂模式,看看怎么实现。
因为手机是由许多的构件组成的,我们将处理器 CPU 和主板 Board 进行抽象,然后 CPU 由 CpuFactory 生产,主板由 MainBoardFactory 生产。然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:
这个时候的如果需要手机产品,只需这样调用:
public class Phone {
private Cpu cpu;
private MainBoard mainBoard;
public Phone(Cpu cpu, MainBoard mainBoard){
this.cpu = cpu;
this.mainBoard = mainBoard;
}
public static void main(String[] args) {
// 得到华为的处理器
CpuFactory KirinFactory = new HuaweiCpuFactory();
Cpu cpu = KirinFactory.getCpu();
// 得到华为的主板
MainBoardFactory mainBoardFactory = new HuaweiMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.getMainBoard();
// 组装手机CPU和主板
Phone huaweiMate = new Phone(cpu, mainBoard);
}
}
单独看处理器 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给手机加配件的话,只需要加一个 XxxFactory 和相应的实现即可,不需要修改现有的工厂。
但是,这种方式有一个问题,那就是如果苹果🍎家产的 CPU 和华为产的主板不能兼容使用,因此不能出现随意组合。这就是产品族的概念,它代表了组成某个产品的一系列配件的集合。
当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、OS 工厂、显示屏工厂等等,我们直接定义手机工厂,每个手机工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
public interface PhoneFactory {
Cpu getCpu();
MainBoard getMainBord();
Display getDisplay();
}
public class HuaweiPhoneFactory implements PhoneFactory {
@Override
public Cpu getCpu() {
return new HuaweiCpu();
}
@Override
public MainBoard getMainBord() {
return new HuaweiMainBoard();
}
@Override
public Display getDisplay() {
return new BoeDisplay();
}
}
public class XiaomiPhoneFactory implements PhoneFactory {
@Override
public Cpu getCpu() {
return new HuaweiCpu();
}
@Override
public MainBoard getMainBord() {
return new XiaomiMainBoard();
}
@Override
public Display getDisplay() {
return new VisionoxDisplay();
}
}
这种情况下,对于生产来说,不再需要单独挑选 CPU 厂商、显示屏厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。
public class Test {
public static void main(String[] args) {
// 第一步就要选定一个“大品牌工厂”
PhoneFactory factory = new HuaweiPhoneFactory();
// 从这个大厂设计制造CPU
Cpu cpu = factory.getCpu();
// 从这个大厂生产手机主板
MainBoard board = factory.getMainBord();
// 从这个大厂生产手机显示屏
Display boeDisplay = factory.getDisplay();
// 生产一个华为 Mete 40 手机
Phone huaweiMete40 = new Phone(cpu, board, boeDisplay);
}
}
这样一个抽象工厂的代码和案例就讲完了,如果还有不懂的,我下次再拿我们现在生产项目中的案例给大家讲解!
最后总结一下,功能模式根据需求和功能的不同,你可以选择对应的简单工厂,普通工厂,和抽象工厂。抽象工厂看起来比较抽象,它暴露的问题也是显而易见的,比如我们要加个 NFC 模块,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则。但也不要完全照搬设计原则,毕竟有时候是需要打破原则,不是吗?比如在电商系统中的一些反范式设计等。