一、简单工厂模式
驱动力 : 将复杂对象的创建、初始化逻辑,从业务代码中抽离出来,交给一个专门的工厂类全权负责。
案例:(图像、视频、按钮 三元素)
假设你在做一个平面编辑器,里面有三种元素:图像元素、视频元素、按钮元素。它们都继承自同一个基类"图形元素",而图形元素有一个尺寸属性。
当前项目的需求是这样的:图像元素创建后,默认尺寸得是 512×512;视频元素默认 1920×1080;按钮元素默认 160×90。
问题来了。如果你在代码各处直接 new,那么每创建一个元素,后面都得跟着一串尺寸赋值。这不仅麻烦,还埋下了隐患------万一哪天需求变了,比如图像元素的默认尺寸要改成 1080×1080,你就得满项目去找所有 new 图像元素 的地方,一个一个改。
而且,这不光是尺寸的事。将来还可能给元素加默认名字、默认位置等等初始化参数。每一次改动,都是一场"全局搜索"的噩梦。
为什么不直接改构造函数?
有小伙伴会想:那把默认参数直接写进构造函数里不就行了?其实也可以,但这会让数据对象变得过于定制化,难以复用。
这个项目要的默认参数,换一个项目可能完全不需要。如果把项目特有的初始化逻辑硬塞进构造函数里,这个类就被"绑死"在当前项目中了。下次想复用,还得再改回来。构造函数应该保持纯粹,只做对象本身必要的基础初始化。
引入简单工厂:
更好的做法是引入一个工厂,让它来专门负责创建这些元素。业务层只需要告诉工厂"我要一个图像元素"(比如传一个字符串或枚举),工厂就负责把对象 new 出来、把该设的默认尺寸、默认名字一并处理好,最后把一个"开箱即用"的成品对象返回。
这样一来,职责就清晰了:
· 数据对象层:只定义对象本身,不掺和项目特有的初始化。
· 工厂层:封装了当前项目所需要的创建逻辑和默认配置。
· 业务层:只负责拿对象来用,完全不关心对象是怎么造出来的。
这样改了需求,也只需要改工厂里的那一处创建逻辑,影响范围被牢牢控制住了。
二、工厂方法模式
驱动力: 简单工厂虽然好,但所有元素的创建逻辑都堆在一个工厂类里。当元素种类变多,或者每种元素的创建过程本身就非常复杂时,这个工厂类会变得臃肿不堪。工厂方法模式的核心思路是:把"创建"这件事本身也抽象出去,让每个具体元素都拥有自己专属的工厂,业务层只和抽象的工厂接口打交道。
还是那个平面编辑器的例子:
在简单工厂里,我们有一个工厂,业务层传个"图像元素"的字符串进去,它返回一个初始化好的对象。这在小规模下完全没问题。
但现在项目变大了,需求也更复杂了。图像元素的创建步骤多了起来:除了设尺寸,还得加载默认占位图、校验图片格式、设置初始图层顺序等等。视频元素也类似:要设尺寸、要加载首帧缩略图、要检查编码格式。按钮元素则有自己的一套配置:设尺寸、设默认文案、绑定默认点击事件。更麻烦的是,这些逻辑还在随着迭代不断变化。
如果这些都还挤在一个工厂类里,这个类会迅速膨胀成"超级工厂"。每加一种新元素,或者改一下创建逻辑,都要动这个类。它变成了整个系统的修改热点,违背了开闭原则------我们对修改不开放,对扩展才开放。
解决方案:让每种元素拥有自己的专属工厂。
我们把创建行为抽象成一个工厂接口 ,比如叫
元素工厂,里面只定义一个方法:创建元素()。然后,为每一种元素都写一个具体的工厂类:
图像元素工厂:实现创建元素(),在里面new出图像元素,把尺寸设成 512×512,加载默认占位图,做完那一套它独有的初始化逻辑。
视频元素工厂:实现创建元素(),同样负责视频元素那套专属的创建和初始化。
按钮元素工厂:实现创建元素(),负责按钮元素的定制化初始化。业务层怎么用呢?
业务层不再直接跟哪个具体的工厂类打交道。它只需要依赖那个抽象的
元素工厂接口。当它需要一个图像元素时,外部注入一个图像元素工厂的实例进来,业务层只调用工厂.创建元素(),拿到成品对象,完全不关心这个对象是怎么造出来的。好处在哪里?
符合开闭原则 :哪天产品说要新增一种"音频元素",你不需要去修改任何已有的工厂代码。只需要新建一个
音频元素工厂类,实现元素工厂接口,把音频元素的创建逻辑放进去就行了。对扩展开放,对修改关闭。职责更单一:每个具体工厂只负责一种对象的创建。逻辑集中,好维护,好测试。
更灵活地替换:业务层依赖的是接口。你可以在不同的场景下,注入不同的工厂实现。比如在测试时注入一个"测试用图像元素工厂",它创建的是不加载占位图的轻量对象,一切都在掌控之中。
一句话总结:
简单工厂:一个工厂包揽所有产品的创建,适合产品种类少、创建逻辑简单的场景。
工厂方法:把工厂也抽象出来,每种产品对应一个专属工厂,让系统的创建行为更容易扩展,而不是一直在老代码上修修补补。
三、抽象工厂模式
驱动力: 工厂方法解决了一种对象的创建问题,但现实中的对象往往是成族、成套出现的。比如平面编辑器里,不会只有"图像元素"这一种对象------你还有配套的 "工具栏"、"属性面板"。当你需要创建一整套互相配合的对象时,就得保证它们属于同一套对象,不能乱套。抽象工厂的核心就是:提供一个接口,用来创建一系列相关或相互依赖的对象,而不需要指定它们具体的类。
案例:(换一套主题)
经过工厂方法的改造,我们的编辑器已经很清晰了:每种元素有自己的专属工厂,扩展新元素很方便。
但需求又升级了。产品经理说,我们的编辑器要做多皮肤主题:一个浅色主题,一个深色主题。这意味着什么?
如果用户选的是浅色主题,那么创建出来的图像元素应该是浅色风格的样子,配套的工具栏是浅色底色的,属性面板也是浅色的。
如果用户选的是深色主题,所有这些东西都应该是深色风格。
问题来了。如果只是用工厂方法,你就得分别调用
图像元素工厂创建图像、工具栏工厂创建工具栏、属性面板工厂创建属性面板。万一哪天某个调用方不小心混搭了------用深色工厂创建了图像元素,却用浅色工厂创建了工具栏------整个界面就风格割裂了。我们需要一个更高层级的工厂,来保证"成套创建"的一致性。
这就是抽象工厂的舞台。
解决方案:把工厂也分家族。
我们定义一个抽象工厂接口 ,叫
UI工厂,里面不只有一个方法,而是一组方法:
创建图像元素()
创建工具栏()
创建属性面板()然后,为每个主题创建一套具体工厂:
浅色UI工厂:实现UI工厂接口。它内部创建的所有东西都是浅色风格------创建图像元素()返回一个浅色默认的图像元素,创建工具栏()返回浅色背景的工具栏,创建属性面板()返回浅色面板。
深色UI工厂:同样实现UI工厂接口。它内部创建的所有东西都是深色风格。业务层怎么用呢?
业务层只需要依赖那个抽象的
UI工厂接口。用户选了什么主题,系统就给它注入对应的具体工厂(浅色UI工厂或深色UI工厂)。业务层只管调用ui工厂.创建图像元素()、ui工厂.创建工具栏(),拿到的就是一套风格统一的对象,绝无混搭的可能。更进一步:产品族变了怎么办?
假设哪天产品经理说,我们要新增一个"夜间主题"。你只需要做两件事:
新建一个
夜间UI工厂类,实现UI工厂接口,在里面把图像元素、工具栏、属性面板都做成夜间风格。在主题选择的地方,加上"夜间主题→夜间UI工厂"的映射。
你没有动任何已有的工厂代码,也没有动业务层的逻辑。这就是抽象工厂的强大之处:它不仅能创建单个对象,还能保证产品族的一致性,并且对扩展开放、对修改关闭。
和工厂方法的关系:
抽象工厂内部,通常就是用工厂方法来实现的。
浅色UI工厂里的创建图像元素(),本质上就是一个工厂方法。所以抽象工厂常常是一组工厂方法的集合,这些方法负责创建属于同一个"家族"的不同"产品"。一句话总结:
简单工厂:一个工厂创建多种产品,封装了初始化细节。
工厂方法:每种产品配一个专属工厂,让创建行为容易扩展。
抽象工厂:提供一个工厂家族,保证创建出来的多个产品是配套的、风格统一的。
抽象工厂模式,为什么以【抽象】为开头:
强调这个工厂是抽象的 ------而不是一个具体的工厂类,它定义了一个抽象的规则 ,用来约束那一群具体的工厂 ,让他们必须生产出风格统一的产品族。
在编程里有一条金科玉律:(要依赖于抽象,不要依赖于具体。)
这就像你开一家咖啡馆,你签合同时签的是**"牛奶供应商"**(抽象),而不是具体的"蒙牛"或"伊利"(具体)。 这样当你发现蒙牛涨价时,你可以瞬间换成伊利,而你的咖啡机(业务逻辑)不需要改动任何接口,因为它只认"牛奶"这个抽象概念。