设计模式之美
王争
前 Google 工程师,《数据结构与算法之美》专栏作者
123425 人已学习
新⼈⾸单¥98
登录后,你可以任选6讲全文学习
课程目录
已完结/共 113 讲
设计模式与范式:行为型 (18讲)
设计模式之美
15
15
1.0x
00:00/00:00
登录|注册

44 | 工厂模式(上):我为什么说没事不要随便用工厂模式创建对象?

课堂讨论
总结
抽象工厂
工厂方法
简单工厂
工厂模式

该思维导图由 AI 生成,仅供参考

上几节课我们讲了单例模式,今天我们再来讲另外一个比较常用的创建型模式:工厂模式(Factory Design Pattern)。
一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。不过,在 GoF 的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见,所以,在今天的讲解中,我们沿用第一种分类方法。
在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。所以,我们今天讲解的重点是前两种工厂模式。对于抽象工厂,你稍微了解一下即可。
除此之外,我们讲解的重点也不是原理和实现,因为这些都很简单,重点还是带你搞清楚应用场景:什么时候该用工厂模式?相对于直接 new 来创建对象,用工厂模式来创建究竟有什么好处呢?
话不多说,让我们正式开始今天的学习吧!

简单工厂(Simple Factory)

首先,我们来看,什么是简单工厂模式。我们通过一个例子来解释一下。
在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

工厂模式是一种常用的创建型设计模式,包括简单工厂、工厂方法和抽象工厂。本文重点介绍了简单工厂和工厂方法。简单工厂通过创建一个工厂类来根据条件创建不同的对象,提高了代码的可读性和灵活性。工厂方法则将对象的创建延迟到子类中,更符合开闭原则。文章还介绍了简单工厂模式的两种实现方法,以及对于开闭原则和多态的讨论。工厂方法模式比起简单工厂模式更加符合开闭原则。但在某些场景下,简单工厂模式更加合适。抽象工厂模式则适用于类有多种分类方式的特殊场景。通过让一个工厂负责创建多个不同类型的对象,可以有效地减少工厂类的个数。文章通过具体的代码实现和对比分析,帮助读者理解工厂模式的应用场景和选择原则。在实际应用中,工厂模式的作用主要体现在封装变化、代码复用、隔离复杂性和控制复杂度上。读者需要重点掌握简单工厂和工厂方法的应用场景,以及在何种情况下选择哪种模式。文章还提出了两种情况下的建议使用方式,并举例说明了工厂模式的实际应用。同时,读者还可以参与课堂讨论,深入交流和分享工厂模式的实际应用和设计原则。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》
新⼈⾸单¥98
立即购买
登录 后留言

全部留言(117)

  • 最新
  • 精选
  • zhengyu.nie
    个人意见,传统的工厂模式太麻烦了,除非业务真的很复杂,通常我会选择以下方案。 还是举文中的例子 1.将不同的RuleConfigParser实现按照约定格式指定beanName注入,比方说@Component(“XmlRuleConfigParser”),取的时候applicationContext.getBean(typeSuffix+RuleConfigParser)即可,拓展的话,自己写一个xxRuleConfigParser,就注入进去了,也不需要在map容器新增。 整个工厂方法就是 public RuleConfigParser getInstance(suffix){ return InstanceLocator.getBean(suffix+"RuleConfigParser"); } 2.直接用java.util.functional实现现代函数式编程范式的设计模式 像文中的例子,可以看作工厂,也可以看作获取一种parse策略。 可以有一个FunctionFactory内部维护一组Function<String,String>函数,再有一个Map容器 mapping type和Function的关系。这样是简化了类的数量,如果业务简单没必要整太多类,function铺在一个factory里可读性不会有什么问题。如果是没有返回值的操作,也可以用Consumer函数。打个比方 public BiConsumer<AbstractProductServiceRequest, Function<ProductServiceQueryRequest, ProductServiceQueryResponse>> operateConsumer() { switch (serviceOperationEnum) { case OPEN: return openConsumer(); case CLOSE: return closeConsumer(); default: throw new RuntimeException("not support OperationType"); } } 如果是对象,那更简单,Map<Supply>函数即可。 public class ShapeFactory { final static Map<String, Supplier<Shape>> map = new HashMap<>(); static { map.put("CIRCLE", Circle::new); map.put("RECTANGLE", Rectangle::new); } public Shape getShape(String shapeType){ Supplier<Shape> shape = map.get(shapeType.toUpperCase()); if(shape != null) { return shape.get(); } throw new IllegalArgumentException("No such shape " + shapeType.toUpperCase()); } } 以上个人意见,对于比较简单的场景,lambda function等方式代替类,会显得不那么臃肿,具体还是要看需求。至于OOP等原则,也不是完全要遵守的,就像争哥说的少量if可以不管,一样的道理,灵活运用。

    作者回复: 👍

    2020-04-24
    35
    194
  • Robin
    原文:简单工厂模式的实现方法,如果我们要添加新的 parser,那势必要改动到 RuleConfigParserFactory 的代码,那这是不是违反开闭原则呢?实际上,如果不是需要频繁地添加新的 parser,只是偶尔修改一下 RuleConfigParserFactory 代码,稍微不符合开闭原则,也是完全可以接受的。 原文:工厂方法:当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。 感觉说法有点牵强,添加一个类,简单工厂模式修改RuleConfigParserFactory, 工厂方法也要修改RuleConfigParserFactoryMap,也是会违背开闭原则。关键简单工厂模式(第二种方式)下添加的代码量一个是map.put,工厂方法也是一个map.put,然后说明工厂方法代码的改动非常少,基本上符合开闭原则?

    作者回复: 改动是不多呀������ 您有更好的设计思路建议吗?

    2020-07-25
    9
    11
  • 郑大钱
    传统的工厂模式确实很传统。 简单工厂是在一个工厂方法里通过流程控制语句创建不同的对象,适合创建简单的对象。 工厂方法和简单方法没有什么区别,只是用工厂对象再此封装了复杂对象的创建。工厂的工厂负责调用工厂的创建方法,每个工厂只创建一个对象,适合创建复杂的对象。 工厂模式是对创建方法的封装和抽象,创建的复杂度无法被抵消,只能被转移到工厂内部消化。

    作者回复: ������

    2020-11-17
    5
  • 御风
    掌握了使用工厂模式的本本质:封装变化(创建逻辑可能变化)、隔离复杂性、控制复杂度(让类职责更加单一)、代码复用。 如果创建的对象不能复用,又不想用if–else,就不能使用简单工厂模式。 这个可以在static代码块中使用反射?

    作者回复: 反射可以,但不能再static静态代码块中创建对象吧

    2020-08-08
  • 逍遥思
    复杂度无法被消除,只能被转移: - 不用工厂模式,if-else 逻辑、创建逻辑和业务代码耦合在一起 - 简单工厂是将不同创建逻辑放到一个工厂类中,if-else 逻辑在这个工厂类中 - 工厂方法是将不同创建逻辑放到不同工厂类中,先用一个工厂类的工厂来来得到某个工厂,再用这个工厂来创建,if-else 逻辑在工厂类的工厂中
    2020-02-12
    20
    414
  • 跳跳
    我觉得很多人被带跑偏了 工厂本身的重点不是解决if else 而是解决简单工厂的开闭原则,大家都在重点讨论if else 即使被省略了 也是map的功劳啊
    2020-08-10
    3
    61
  • 麦可
    我把Head First的定义贴过来,方便大家理解总结 工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类 抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类
    2020-02-12
    4
    56
  • 辣么大
    在JDK中工厂方法的命名有些规范: 1. valueOf() 返回与入参相等的对象 例如 Integer.valueOf() 2. getInstance() 返回单例对象 例如 Calendar.getInstance() 3. newInstance() 每次调用时返回新的对象 例如 HelloWorld.class.getConstructor().newInstance() 4 在反射中的工厂方法 例如 XXX.class.getField(String name) 返回成员 静态工厂方法的优点: 1. 静态工厂方法子类可以继承,但不能重写,这样返回类型就是确定的。可以返回对象类型或者primitive 类型。 2. 静态工厂方法的名字更有意义,例如Collections.synchronizedMap() 3. 静态工厂方法可以封装创建对象的逻辑,还可以做其他事情,让构造方法只初始化成员变量。 4. 静态工厂方法可以控制创建实例的个数。例如单例模式,或者多例模式,使用本质上是可以用静态工厂方法实现。
    2020-02-12
    6
    44
  • Jxin
    分歧: 1.文中说,创建对象不复杂的情况下用new,复杂的情况用工厂方法。这描述没问题,但工厂方法除了处理复杂对象创建这一职责,还有增加扩展点这优点。工厂方法,在可能有扩展需求,比如要加对象池,缓存,或其他业务需求时,可以提供扩展的地方。所以,除非明确确定该类只会有简单数据载体的职责(值对象),不然建议还是用工厂方法好点。new这种操作是没有扩展性的。 回答问题: 2.工厂方法要么归于类,要么归于实例。如果归于实例,那么第一个实例怎么来?而且实例创建出另一个实例,这种行为应该称为拷贝,或则拆分。是一个平级的复制或分裂的行为。而归于类,创建出实例,是一个父子关系,其创建的语义更强些。 我认为不影响测试。因为工厂方法不该包含业务,它只是new的一种更好的写法。所以你只需要用它,而并不该需要测它。如果你的静态工厂方法都需要测试,那么说明你这个方法不够“干净”。
    2020-02-13
    1
    28
  • Brian
    一、三种工厂模式 1. 简单工厂(Simple Factory) 使用场景: a. 当每个对象的创建逻辑都比较简单的时候,将多个对象的创建逻辑放到一个工厂类中。 实现: a. if else 创建不同的对象。 b. 用单例模式 + 简单工厂模式结合来实现。 2. 工厂方法(Factory Method) 使用场景: a. 当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类时,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。 b. 避免很多 if-else 分支逻辑时。 实现: a. 定义相应的ParserFactory接口,每个工厂定义一个实现类。这种方式使用会有多个if else 让使用更加复杂。 b. 创建工厂的工厂来,此方案可以解决上面的问题。 3. 抽象工厂(Abstract Factory)- 不常用 使用场景: a. 有多种分类方式,如方式要用一套工厂方法,方式二要用一套工厂方法,详见原文例子。 实现: 让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。 二、例子 刚好最近有这方面的应用场景,主要使用了 单例模式 + 工厂模式 + 策略模式,用于解化多过的if else的复杂性。 public class OrderOperateStrategyFactory { /** * 消费类型和策略对象映射。 */ private Map<CheckoutType, OrderOperateStrategy> map; /** * 构造策略列表。 */ private OrderOperateStrategyFactory() { List<OrderOperateStrategy> list = new ArrayList<>(); list.add(SpringContextHolder.getBean(ConsumptionOrderOperateStrategy.class)); list.add(SpringContextHolder.getBean(GroupServiceOrderOperateStrategy.class)); //... map = list.stream().collect(Collectors.toMap(OrderOperateStrategy::getCheckoutType, v -> v)); } /** * 通过消费类型获取订单操作策略。 * * @param checkoutType 消费类型 * @return 订单损我策略对象 */ public OrderOperateStrategy get(CheckoutType checkoutType) { return map.get(checkoutType); } /** * 静态内部类单例对象。 */ private static class Holder { private static OrderOperateStrategyFactory INSTANCE = new OrderOperateStrategyFactory(); } /** * 获取订单操作策略工厂类实例。 * * @return 单例实例。 */ public static OrderOperateStrategyFactory getInstance() { return Holder.INSTANCE; } } 使用: OrderOperateStrategy strategy = OrderOperateStrategyFactory.getInstance().get(checkoutType); strategy.complete(orderId);
    2020-02-13
    3
    27
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部