二十三种设计模式

devtools/2025/3/1 7:36:28/

2 工厂方法模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  • 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

设计模式》一书中,工厂模式被分为了三种:简单工厂、工厂方法和抽象工厂。(不过,在书中作者将简单工厂模式看作是工厂方法模式的一种特例。)

接下来我会介绍三种工厂模式的原理及使用

  • 简单工厂模式(不属于GOF的23种经典设计模式
  • 工厂方法模式
  • 抽象工厂模式

2.1 需求: 模拟发放奖品业务

需求: 为了让我们的案例更加贴近实际开发, 这里我们来模拟一下互联网电商中促销拉新下的业务场景, 新用户注册立即参与抽奖活动 ,奖品的种类有: 打折券, 免费优酷会员,小礼品.

2.2 原始开发方式

不考虑设计原则,不使用设计模式的方式进行开发

在不考虑任何代码的可扩展性的前提下,只为了尽快满足需求.我们可以这样去设计这个业务的代码结构:

1) 实体类

名称描述
AwardInfo获奖信息对应实体类
DiscountInfo打折券信息对应实体类
YouKuMember优酷会员对应实体类
SmallGiftInfo小礼品信息对应实体类
DiscountResult打折券操作响应结果封装类
java">public class AwardInfo {private String uid; //用户唯一IDprivate Integer awardType; //奖品类型: 1 打折券 ,2 优酷会员,3 小礼品private String awardNumber; //奖品编号Map<String, String> extMap; //额外信息}public class DiscountInfo {//属性信息省略......
}public class YouKuMember {//属性信息省略......
}public class SmallGiftInfo {private String userName;              // 用户姓名private String userPhone;             // 用户手机private String orderId;               // 订单IDprivate String relAddress;            // 收货地址}public class DiscountResult {private String status; // 状态码private String message; // 信息
}

2) 服务层

名称功能描述
DiscountServiceDiscountResult sendDiscount(String uid,String number)模拟打折券服务
YouKuMemberServicevoid openMember(String bindMobile , String number)模拟赠送优酷会员服务
SmallGiftServiceBoolean giveSmallGift(SmallGiftInfo smallGiftInfo)模拟礼品服务
java">public class DiscountService {public DiscountResult sendDiscount(String uid, String number){System.out.println("向用户发放打折券一张: " + uid + " , " + number);return new DiscountResult("200","发放打折券成功");}
}public class YouKuMemberService {public void openMember(String bindMobile , String number){System.out.println("发放优酷会员: " + bindMobile + " , " + number);}
}public class SmallGiftService {public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo){System.out.println("小礼品已发货,获奖用户注意查收! " + JSON.toJSON(smallGiftInfo));return true;}
}

3) 控制层

名称功能描述
DeliverControllerResponseResult awardToUser(AwardInfo awardInfo)按照类型的不同发放商品
奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
java">public class DeliverController {/*** 按照类型的不同发放商品*     奖品类型: 1 打折券 ,2 优酷会员,3 小礼品*/public void awardToUser(AwardInfo awardInfo){if(awardInfo.getAwardType() == 1){ //打折券DiscountService discountService = new DiscountService();DiscountResult result = discountService.sendDiscount(awardInfo.getUid(), awardInfo.getAwardNumber());System.out.println("打折券发放成功!"+ JSON.toJSON(result));}else if(awardInfo.getAwardType() == 2){ //优酷会员//获取用户手机号String bindMobile = awardInfo.getExtMap().get("phone");//调用serviceYouKuMemberService youKuMemberService = new YouKuMemberService();youKuMemberService.openMember(bindMobile,awardInfo.getAwardNumber());System.out.println("优酷会员发放成功!");}else if(awardInfo.getAwardType() == 3){ /*小礼品封装收货用户信息*/SmallGiftInfo smallGiftInfo = new SmallGiftInfo();smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));smallGiftInfo.setOrderId(UUID.randomUUID().toString());smallGiftInfo.setRelAddress(awardInfo.getExtMap().get("adderss"));SmallGiftService smallGiftService = new SmallGiftService();Boolean isSuccess = smallGiftService.giveSmallGift(smallGiftInfo);System.out.println("小礼品发放成功!" + isSuccess);}}}

4) 测试

通过单元测试,来对上面的接口进行测试,验证代码质量.

java">public class TestApi01 {//测试发放奖品接口@Testpublic void test01(){DeliverController deliverController = new DeliverController();//1. 发放打折券优惠AwardInfo info1 = new AwardInfo();info1.setUid("1001");info1.setAwardType(1);info1.setAwardNumber("DEL12345");deliverController.awardToUser(info1);//2. 发放优酷会员AwardInfo info2 = new AwardInfo();info2.setUid("1002");info2.setAwardType(2);info2.setAwardNumber("DW12345");Map<String,String> map = new HashMap<>();map.put("phone","13512341234");info2.setExtMap(map);deliverController.awardToUser(info2);//2. 发放小礼品AwardInfo info3 = new AwardInfo();info3.setUid("1003");info3.setAwardType(3);info3.setAwardNumber("SM12345");Map<String,String> map2 = new HashMap<>();map2.put("username","大远");map2.put("phone","13512341234");map2.put("address","北京天安门");info3.setExtMap(map2);deliverController.awardToUser(info3);}
}

对于上面的实现方式,如果我们有想要添加的新的奖品时,势必要改动DeliverController的代码,违反开闭原则.而且如果有的抽奖接口出现问题,那么对其进行重构的成本会非常高.

除此之外代码中有一组if分支判断逻辑,现在看起来还可以,但是如果经历几次迭代和拓展,后续ifelse肯定还会增加.到时候接手这段代码的研发将会十分痛苦.

2.3 简单工厂模式

2.3.1 简单工厂模式介绍

简单工厂不是一种设计模式,反而比较像是一种编程习惯。简单工厂模式又叫做静态工厂方法模式(static Factory Method pattern),它是通过使用静态方法接收不同的参数来返回不同的实例对象.

实现方式:

​ 定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

适用场景:
  (1)需要创建的对象较少。
  (2)客户端不关心对象的创建过程。

2.3.2 简单工厂原理

简单工厂包含如下角色:

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品 :实现或者继承抽象产品的子类
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。

2.3.3 简单工厂模式重构代码

1) service

java">/*** 免费商品发放接口* @author spikeCong* @date 2022/9/8**/
public interface IFreeGoods {ResponseResult sendFreeGoods(AwardInfo awardInfo);}
java">/*** 模拟打折券服务* @author spikeCong* @date 2022/9/8**/
public class DiscountFreeGoods implements IFreeGoods {@Overridepublic ResponseResult sendFreeGoods(AwardInfo awardInfo) {System.out.println("向用户发放一张打折券: " + awardInfo.getUid() + " , " + awardInfo.getAwardNumber());return new ResponseResult("200","打折券发放成功!");}
}/*** 小礼品发放服务* @author spikeCong* @date 2022/9/8**/
public class SmallGiftFreeGoods implements IFreeGoods {@Overridepublic ResponseResult sendFreeGoods(AwardInfo awardInfo) {SmallGiftInfo smallGiftInfo = new SmallGiftInfo();smallGiftInfo.setUserPhone(awardInfo.getExtMap().get("phone"));smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));smallGiftInfo.setAddress(awardInfo.getExtMap().get("address"));smallGiftInfo.setOrderId(UUID.randomUUID().toString());System.out.println("小礼品发放成,请注意查收: " + JSON.toJSON(smallGiftInfo));return new ResponseResult("200","小礼品发送成功",smallGiftInfo);}
}/*** 优酷 会员服务* @author spikeCong* @date 2022/9/8**/
public class YouKuMemberFreeGoods implements IFreeGoods {@Overridepublic ResponseResult sendFreeGoods(AwardInfo awardInfo) {String phone = awardInfo.getExtMap().get("phone");System.out.println("发放优酷会员成功,绑定手机号: " + phone);return new ResponseResult("200","优酷会员发放成功!");}
}

2) factory

java">/*** 具体工厂: 生成免费商品* @author spikeCong* @date 2022/9/9**/
public class FreeGoodsFactory {public static IFreeGoods getInstance(Integer awardType){IFreeGoods iFreeGoods = null;if(awardType == 1){  //打折券iFreeGoods = new DiscountFreeGoods();}else if(awardType == 2){ //优酷会员iFreeGoods = new YouKuMemberFreeGoods();}else if(awardType == 3){ //小礼品iFreeGoods = new SmallGiftFreeGoods();}return iFreeGoods;}
}

3)controller

java">public class DeliverController {//发放奖品public ResponseResult awardToUser(AwardInfo awardInfo){try {IFreeGoods freeGoods = FreeGoodsFactory.getInstance(awardInfo.getAwardTypes());ResponseResult responseResult = freeGoods.sendFreeGoods(awardInfo);return responseResult;} catch (Exception e) {e.printStackTrace();return new ResponseResult("201","奖品发放失败!");}}
}

4) 测试

通过单元测试,来对上面的接口进行测试,验证代码质量.

java">public class TestApi02 {DeliverController deliverController = new DeliverController();@Testpublic void test01(){//1. 发放打折券优惠AwardInfo info1 = new AwardInfo();info1.setUid("1001");info1.setAwardTypes(1);info1.setAwardNumber("DEL12345");ResponseResult result = deliverController.awardToUser(info1);System.out.println(result);}@Testpublic void test02(){//2. 发放优酷会员AwardInfo info2 = new AwardInfo();info2.setUid("1002");info2.setAwardTypes(2);info2.setAwardNumber("DW12345");Map<String,String> map = new HashMap<>();map.put("phone","13512341234");info2.setExtMap(map);ResponseResult result1 = deliverController.awardToUser(info2);System.out.println(result1);}@Testpublic void test03(){//3. 发放小礼品AwardInfo info3 = new AwardInfo();info3.setUid("1003");info3.setAwardTypes(3);info3.setAwardNumber("SM12345");Map<String,String> map2 = new HashMap<>();map2.put("username","大远");map2.put("phone","13512341234");map2.put("address","北京天安门");info3.setExtMap(map2);ResponseResult result2 = deliverController.awardToUser(info3);System.out.println(result2);}
}
2.3.4 简单工厂模式总结

优点:

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点:

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

2.4 工厂方法模式

2.4.1 工厂方法模式介绍

工厂方法模式 Factory Method pattern,属于创建型模式.

概念: 定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

2.4.2 工厂方法模式原理

工厂方法模式的目的很简单,就是封装对象创建的过程,提升创建对象方法的可复用性

工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

我们直接来看看工厂方法模式的 UML 图:

2.4.3 工厂方法模式重构代码

为了提高代码扩展性,我们需要将简单工厂中的if分支逻辑去掉,通过增加抽象工厂(生产工厂的工厂)的方式,让具体工厂去进行实现,由具体工厂来决定实例化哪一个具体的产品对象.

抽象工厂

java">public interface FreeGoodsFactory {IFreeGoods getInstance();
}

具体工厂

java">public class DiscountFreeGoodsFactory implements FreeGoodsFactory {@Overridepublic IFreeGoods getInstance() {return new DiscountFreeGoods();}
}public class SmallGiftFreeGoodsFactory implements FreeGoodsFactory {@Overridepublic IFreeGoods getInstance() {return new SmallGiftFreeGoods();}
}

Controller

java">public class DeliverController {/*** 按照类型的不同发放商品*/public ResponseResult awardToUser(AwardInfo awardInfo){FreeGoodsFactory freeGoodsFactory = null;if(awardInfo.getAwardType() == 1){freeGoodsFactory = new DiscountFreeGoodsFactory();}else if(awardInfo.getAwardType() == 2){freeGoodsFactory = new SmallGiftFreeGoodsFactory();}IFreeGoods freeGoods = freeGoodsFactory.getInstance();System.out.println("=====工厂方法模式========");ResponseResult result = freeGoods.sendFreeGoods(awardInfo);return result;}}

从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 awardToUser() 方法中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。

那怎么 来解决这个问题呢?

我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

java">/*** 用简单方法模式实现: 工厂的工厂,作用是不需要每次创建新的工厂对象* @author spikeCong* @date 2022/9/9**/
public class FreeGoodsFactoryMap {private static final Map<Integer,FreeGoodsFactory> cachedFactories = new HashMap<>();static{cachedFactories.put(1, new DiscountFreeGoodsFactory());cachedFactories.put(2, new SmallGiftFreeGoodsFactory());}public static FreeGoodsFactory getParserFactory(Integer type){if(type == 1){FreeGoodsFactory freeGoodsFactory = cachedFactories.get(1);return freeGoodsFactory;}else if(type ==2){FreeGoodsFactory freeGoodsFactory = cachedFactories.get(2);return freeGoodsFactory;}return null;}
}

Controller

java">/*** 发放奖品接口* @author spikeCong* @date 2022/9/7**/
public class DeliverController {/*** 按照类型的不同发放商品*/public ResponseResult awardToUser(AwardInfo awardInfo){//根据类型获取工厂FreeGoodsFactory goodsFactory = FreeGoodsFactoryMap.getParserFactory(awardInfo.getAwardType());//从工厂中获取对应实例IFreeGoods freeGoods = goodsFactory.getInstance();System.out.println("=====工厂方法模式========");ResponseResult result = freeGoods.sendFreeGoods(awardInfo);return result;}
}

现在我们的代码已经基本上符合了开闭原则,当有新增的产品时,我们需要做的事情包括:

  1. 创建新的产品类,并且让该产品实现抽象产品接口
  2. 创建产品类对应的具体工厂,并让具体工厂实现抽象工厂
  3. 将新的具体工厂对象,添加到FreeGoodsFactoryMap的cachedFactories中即可,需要改动的代码改动的非常少.
2.4.4 工厂方法模式总结

工厂方法模优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

什么时候使用工厂方法模式

  • 需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等。
  • 创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权 token 信息,配置文件等。
  • 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
  • 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。
  • 希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。

http://www.ppmy.cn/devtools/163554.html

相关文章

【每日论文】Towards Optimal Multi-draft Speculative Decoding

下载论文或阅读原文&#xff0c;请点击&#xff1a;LlamaFactory - huggingface daily paper - 每日论文解读 | LlamaFactory | LlamaFactory 摘要 大型语言模型&#xff08;LLMs&#xff09;已经成为自然语言处理任务中不可或缺的一部分。然而&#xff0c;自回归采样已成为效…

18440二维差分

18440二维差分 ⭐️难度&#xff1a;中等 &#x1f4d6; &#x1f4da; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();int m scanner.nextInt();int q scanne…

【Fusion Pro】Ubuntu24.04如何给虚拟机扩充磁盘

01 序言 在初始创建虚拟机时&#xff0c;往往不知道需要给虚拟机分配多少磁盘空间&#xff0c;常常出现虚拟机磁盘不足的情况。 在尝试扩容时&#xff0c;发现Mac版Fusion Pro上的虚拟机与Workstation上配置有些不太一样。本文将手把手展示如何增加硬盘空间并成功挂载。 我这…

通过查询hive的元数据库中的表分区清单,拼写出来删除表分区的sql脚本

以下是通过直接查询Hive元数据库生成分区删除SQL的脚本方案&#xff0c;支持MySQL/PostgreSQL元数据库类型&#xff0c;已考虑安全性和性能优化&#xff1a; #!/bin/bash # 脚本名称&#xff1a;metastore_drop_partitions.sh # 描述&#xff1a;通过Hive元数据库生成分区删除…

测试计划、测试用例、测试报告的示例文档

以下为你提供测试计划、测试用例、测试报告的示例文档&#xff0c;你可以根据实际项目情况进行修改和调整。 测试计划文档 [项目名称] 测试计划 一、引言 1.1 编写目的 本测试计划旨在明确 [项目名称] 的测试范围、测试方法、测试进度安排以及资源需求等&#xff0c;为测试…

【人工智能】数据挖掘与应用题库(201-300)

1、在LetNet5网络中,卷积核的大小是? 答案:5*5 2、LeNet5网络参数的数量约为? 答案:6万 3、AlexNet与LeNet5相比,使用了哪些机制来改进模型的训练过程? 答案: 数据增广Dropout抑制过拟合ReLU激活函数CUDA加速神经网络训练4、VGGNet使用的卷积核的大小是? 答案:…

微信小程序换行符真机不生效问题

标签必须使用text包裹 <text>你好你好{{"\n"}}你好你好</text>上面代码在模拟器正常&#xff0c;在真机上面原样显示/n文本了&#xff0c;没实现换行效果 <text>你好你好{{"\n"}}你好你好</text>改成这种格式的话&#xff0c;模…

【GESP】C++二级真题 luogu-B4037 [GESP202409 二级] 小杨的 N 字矩阵

GESP二级真题&#xff0c;多层循环和分支练习&#xff0c;难度★✮☆☆☆。 题目题解详见&#xff1a;https://www.coderli.com/gesp-2-luogu-b4037/ 【GESP】C二级真题 luogu-B4037 [GESP202409 二级] 小杨的 N 字矩阵 | OneCoderGESP二级真题&#xff0c;多层循环和分支练习…