第四章 4.2 工厂方法模式

分类:01_设计模式

标签:

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

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

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

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

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

42.jpg

4.2.2 原始开发方式

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

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

1) 实体类

名称描述
AwardInfo获奖信息对应实体类
DiscountInfo打折券信息对应实体类
YouKuMember优酷会员对应实体类
SmallGiftInfo小礼品信息对应实体类
DiscountResult打折券操作响应结果封装类

public class AwardInfo {

   private String uid; //用户唯一ID

   private 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;               // 订单ID
   private 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)模拟礼品服务

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)按照类型的不同发放商品<br />奖品类型: 1 打折券 ,2 优酷会员,3 小礼品

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");

           //调用service
           YouKuMemberService 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) 测试

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


public class TestApi01 {

   //测试发放奖品接口
   @Test
   public 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肯定还会增加.到时候接手这段代码的研发将会十分痛苦.


4.2.3 简单工厂模式

4.2.3.1 简单工厂模式介绍

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

实现方式:

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

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

4.2.3.2 简单工厂原理

简单工厂包含如下角色:

 43.jpg

4.2.3.3 简单工厂模式重构代码

1) service


/**
* 免费商品发放接口
* @author spikeCong
* @date 2022/9/8
**/
public interface IFreeGoods {

   ResponseResult sendFreeGoods(AwardInfo awardInfo);

}

/**
* 模拟打折券服务
* @author spikeCong
* @date 2022/9/8
**/
public class DiscountFreeGoods implements IFreeGoods {

   @Override
   public 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 {

   @Override
   public 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 {

   @Override
   public ResponseResult sendFreeGoods(AwardInfo awardInfo) {

       String phone = awardInfo.getExtMap().get("phone");
       System.out.println("发放优酷会员成功,绑定手机号: " + phone);
       return new ResponseResult("200","优酷会员发放成功!");
   }
}

2) factory


/**
* 具体工厂: 生成免费商品
* @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


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) 测试

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


public class TestApi02 {

   DeliverController deliverController = new DeliverController();

   @Test
   public 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);

   }

   @Test
   public 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);

   }

   @Test
   public 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);
   }
}

4.2.3.4 简单工厂模式总结

优点:

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

缺点:

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


4.2.4 工厂方法模式

4.2.4.1 工厂方法模式介绍

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

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

4.2.4.2 工厂方法模式原理

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

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

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

44.jpg

4.2.4.3 工厂方法模式重构代码

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

抽象工厂


public interface FreeGoodsFactory {

   IFreeGoods getInstance();
}

具体工厂


public class DiscountFreeGoodsFactory implements FreeGoodsFactory {

   @Override
   public IFreeGoods getInstance() {
       return new DiscountFreeGoods();
   }
}

public class SmallGiftFreeGoodsFactory implements FreeGoodsFactory {
   @Override
   public IFreeGoods getInstance() {

       return new SmallGiftFreeGoods();
   }
}

Controller


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() 方法中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。

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

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


/**
* 用简单方法模式实现: 工厂的工厂,作用是不需要每次创建新的工厂对象
* @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


/**
* 发放奖品接口
* @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中即可,需要改动的代码改动的非常少.

4.2.4.4 工厂方法模式总结

工厂方法模优缺点

优点:

缺点:

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

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

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

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

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

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


4.2.2 原始开发方式

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

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

1) 实体类

名称描述
AwardInfo获奖信息对应实体类
DiscountInfo打折券信息对应实体类
YouKuMember优酷会员对应实体类
SmallGiftInfo小礼品信息对应实体类
DiscountResult打折券操作响应结果封装类

public class AwardInfo {

   private String uid; //用户唯一ID

   private 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;               // 订单ID
   private 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)模拟礼品服务

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)按照类型的不同发放商品<br />奖品类型: 1 打折券 ,2 优酷会员,3 小礼品

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");

           //调用service
           YouKuMemberService 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) 测试

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


public class TestApi01 {

   //测试发放奖品接口
   @Test
   public 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肯定还会增加.到时候接手这段代码的研发将会十分痛苦.


4.2.3 简单工厂模式

4.2.3.1 简单工厂模式介绍

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

实现方式:

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

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

4.2.3.2 简单工厂原理

简单工厂包含如下角色:

 

4.2.3.3 简单工厂模式重构代码

1) service


/**
* 免费商品发放接口
* @author spikeCong
* @date 2022/9/8
**/
public interface IFreeGoods {

   ResponseResult sendFreeGoods(AwardInfo awardInfo);

}

/**
* 模拟打折券服务
* @author spikeCong
* @date 2022/9/8
**/
public class DiscountFreeGoods implements IFreeGoods {

   @Override
   public 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 {

   @Override
   public 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 {

   @Override
   public ResponseResult sendFreeGoods(AwardInfo awardInfo) {

       String phone = awardInfo.getExtMap().get("phone");
       System.out.println("发放优酷会员成功,绑定手机号: " + phone);
       return new ResponseResult("200","优酷会员发放成功!");
   }
}

2) factory


/**
* 具体工厂: 生成免费商品
* @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


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) 测试

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


public class TestApi02 {

   DeliverController deliverController = new DeliverController();

   @Test
   public 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);

   }

   @Test
   public 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);

   }

   @Test
   public 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);
   }
}

4.2.3.4 简单工厂模式总结

优点:

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

缺点:

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


4.2.4 工厂方法模式

4.2.4.1 工厂方法模式介绍

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

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

4.2.4.2 工厂方法模式原理

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

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

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

4.2.4.3 工厂方法模式重构代码

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

抽象工厂


public interface FreeGoodsFactory {

   IFreeGoods getInstance();
}

具体工厂


public class DiscountFreeGoodsFactory implements FreeGoodsFactory {

   @Override
   public IFreeGoods getInstance() {
       return new DiscountFreeGoods();
   }
}

public class SmallGiftFreeGoodsFactory implements FreeGoodsFactory {
   @Override
   public IFreeGoods getInstance() {

       return new SmallGiftFreeGoods();
   }
}

Controller


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() 方法中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。

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

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


/**
* 用简单方法模式实现: 工厂的工厂,作用是不需要每次创建新的工厂对象
* @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


/**
* 发放奖品接口
* @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中即可,需要改动的代码改动的非常少.

4.2.4.4 工厂方法模式总结

工厂方法模优缺点

优点:

缺点:

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


修改内容