设计模式之工厂模式

一:工厂模式介绍

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设
计模式属于创建型模式,它提供了一种创建对象的最佳方式。《设计模式》一书中,工厂模式被分为了三种:简单工厂、工厂方法和抽象工厂。(不过,在书中作者将简单工厂模式看作是工厂方法模式的一种特例。)
此文章将从具体的业务出发,进行工厂模式的讲解。

二:需求及设计

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

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

2.2 不利用设计模式进行开发

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

名称描述
AwardInfo获奖信息对应实体类
DiscountInfo打折券具体实体类
DiscountInfo打折券具体实体类
YouKuMember优酷会员信息实体类
SmallGiftInfo小礼物对应实体类
SmallGiftInfo小礼物对应实体类
SmallGiftInfo打折券操作响应结果封装类
public class AwardInfo {
    private String uid; //用户唯一ID
    private Integer awardType; //奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
    private String awardNumber; //奖品编号
    Map<String, String> extMap; //额外信息

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public Integer getAwardType() {
        return awardType;
    }

    public void setAwardType(Integer awardType) {
        this.awardType = awardType;
    }

    public String getAwardNumber() {
        return awardNumber;
    }

    public void setAwardNumber(String awardNumber) {
        this.awardNumber = awardNumber;
    }

    public Map<String, String> getExtMap() {
        return extMap;
    }

    public void setExtMap(Map<String, String> extMap) {
        this.extMap = extMap;
    }
}

public class DiscountInfo {
}
`package factory;

public class DiscountResult {
    private String status; // 状态码
    private String message; // 信息

    public DiscountResult() {
    }

    public DiscountResult(String status, String message) {
        this.status = status;
        this.message = message;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
``

```java
package factory;

public class SmallGiftInfo {
    private String userName; // 用户姓名
    private String userPhone; // 用户手机
    private String orderId; // 订单ID
    private String Address; // 收货地址

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPhone() {
        return userPhone;
    }

    public void setUserPhone(String userPhone) {
        this.userPhone = userPhone;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getAddress() {
        return Address;
    }

    public void setAddress(String address) {
        Address = address;
    }
}

package factory;

public class YouKuMember {
}

具体服务类:

import factory.DiscountResult;
// 模拟打折券服务
public class DiscountService {
    public DiscountResult sendDiscount(String uid, String
            number){
        System.out.println("向用户发放打折券一张: " + uid + " , " + number);
        return new DiscountResult("200","发放打折券成功");
    }
}

//模拟礼品服务
public class SmallGiftService {
    public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo)
    {
        System.out.println("小礼品已发货,获奖用户注意查收! " + JSON.toJSON(smallGiftInfo));
        return true;
    }
}

这里需要导入依赖:

		 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
public class YouKuMemberService {
    public void openMember(String bindMobile , String number)
    {
        System.out.println("发放优酷会员: " + bindMobile + " , " + number);
    }
}

控制层

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 phone = awardInfo.getExtMap().get("phone");
            YouKuMemberService youKuMemberService=new YouKuMemberService();
            youKuMemberService.openMember(phone,awardInfo.getAwardNumber());
            System.out.println("优酷会员发放成功");
        } else if (awardInfo.getAwardType() == 3) {
            SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
            SmallGiftService smallGiftService=new SmallGiftService();
            String username = awardInfo.getExtMap().get("username");
            smallGiftInfo.setUserName(username);
            smallGiftInfo.setOrderId(UUID.randomUUID().toString());
            smallGiftInfo.setAddress(awardInfo.getExtMap().get("address"));
            Boolean aBoolean = smallGiftService.giveSmallGift(smallGiftInfo);
            System.out.println("小礼品发放成功"+aBoolean);
        }
    }
}

思考:DeliverController的代码,违反开闭原则.而且如果有的抽奖接口出现问题,那么对进行重构的成本会非常高。除此之外代码中有一组if分支判断逻辑,现在看起来还可以,但是如果经历几次迭代和拓展,后续ifelse肯定还会增加.到时候接手这段代码的研发将会十分痛苦。

三:简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。简单工厂模式又叫做静态工厂方法模式(static Factory Method pattern),它是通过使用静态方法接收不同的参数来返回不同的实例对象。
实现方式:
定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共
同的父类或接口。
适用场景:   (1)需要创建的对象较少。  (2)客户端不关心对象的创
建过程。
简单工厂包含如下角色:
抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
具体产品 :实现或者继承抽象产品的子类
具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
下图是模拟奖品发放的UML图:

3.1简单工厂模式重构上面代码:

3.1.1 重构service
//免费商品发放接口
public interface IFreeGoods {
    DiscountResult sendFreeGoods(AwardInfo awardInfo);
}
// 模拟打折服务
public class DiscountFreeGoods implements IFreeGoods{

    @Override
    public DiscountResult sendFreeGoods(AwardInfo awardInfo) {
        System.out.println("向用户发送一张打折券 :"+awardInfo.getUid()+", " +awardInfo.getAwardNumber());

        return new DiscountResult("200","打折券发放成功");

    }
}
// 小礼品发放服务
public class SmallGiftFreeGoods implements IFreeGoods{
    @Override
    public DiscountResult 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 DiscountResult("200","小礼品发送成功");
    }
}
//优酷会员服务
public class YouKuMemberFreeGoods implements IFreeGoods{
    @Override
    public DiscountResult sendFreeGoods(AwardInfo awardInfo) {
        String phone = awardInfo.getExtMap().get("phone");
        System.out.println("发放优酷会员成功,绑定手机号: " +
                phone);
        return new DiscountResult("200","优酷会员发放成功!");
    }
}
3.1.2 factory
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.1.2 controller
public class DeliverControllerSimple {
    public DiscountResult awardToUser(AwardInfo awardInfo){
        IFreeGoods iFreeGoods = FreeGoodsFactory
                .getInstance(awardInfo.getAwardType());
        return iFreeGoods.sendFreeGoods(awardInfo);
    }
}
3.1.3简单工厂模式总结

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

四:工厂模式

属于创建者模式,概念: 定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
工厂方法模式的目的很简单,就是封装对象创建的过程,提升创建对象方法的
可复用性。
工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。

  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。

  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
    工厂模式的UML图如下:
    在这里插入图片描述

4.1 使用工厂模式重构代码

抽象工厂

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();
    }
}

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

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 getParseFactory(Integer type){
        return cachedFactories.get(type);
    }
}

Controller

public class DeliverController {
    public DiscountResult awardToUser(AwardInfo awardInfo){
        FreeGoodsFactory goodsFactory = FreeGoodsFactoryMap.getParseFactory(awardInfo.getAwardType());
        IFreeGoods iFreeGoods = goodsFactory.getInstance();
        return iFreeGoods.sendFreeGoods(awardInfo);
    }
}

4.2 工厂模式总结

优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
    缺点:
  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加 了系统的复杂度。
    什么时候使用工厂方法模式
  • 需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等
  • 创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权 token 信息,配置文件等。
  • 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
  • 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。

五:抽象工厂模式

抽象工厂模式比工厂方法模式的抽象程度更高. 在工厂方法模式中每一个具体工厂只需要生产一种具体产品,但是在抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这样一组产品被称为产品族.产品族中的每一个产品都分属于某一个产品继承等级结构。
抽象工厂模式的主要角色如下:
抽象工厂(Abstract Factory):它声明了一种用于创建一族产品的方法,每一个方法对应一种产品.
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建.
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

抽象产品

public interface TV {
}
public interface Freezer {
}

具体产品

public class HairFreezer implements AbstractFreezer {}
public class HisenseFreezer implements AbstractFreezer {}
public class HairTV implements AbstractTV {}
public class HisenseTV implements AbstractTV {}

抽象工厂

public interface ApplianceFactory {
    TV createTv();
    Freezer createFreezer();
}

具体工厂: 每一个具体工厂方法,可以返回一个特定的产品对象,而同一个具体工厂所创建的产品对象构成了一个产品族

public class HairFactory implements ApplianceFactory{
    @Override
    public TV createTv() {
        return new HairTV();
    }

    @Override
    public Freezer createFreezer() {
        return new HairFreezer();
    }
}

public class HisenseFactory implements ApplianceFactory{
    @Override
    public TV createTv() {
        return new HisenseTV();
    }

    @Override
    public Freezer createFreezer() {
        return new HisenseFreezer();
    }
}


客户端

public class Client {
    private TV tv;
    private Freezer freezer;
    public Client(ApplianceFactory applianceFactory){
        this.tv = applianceFactory.createTv();
        this.freezer = applianceFactory.createFreezer();
    }

    public TV getTv() {
        return tv;
    }

    public void setTv(TV tv) {
        this.tv = tv;
    }

    public Freezer getFreezer() {
        return freezer;
    }

    public void setFreezer(Freezer freezer) {
        this.freezer = freezer;
    }

    public static void main(String[] args) {
        Client client=new Client(new HairFactory());
        TV tv1 = client.getTv();
        System.out.println(tv1);
    }
}

抽象工厂模式总结:
从上面代码实现中我们可以看出,抽象工厂模式向使用(客户)方隐藏了下列
变化:

  • 程序所支持的实例集合(具体工厂)的数目;
  • 当前是使用的实例集合中的哪一个实例;
  • 在任意给定时刻被实例化的具体类型;
    所以说,在理解抽象工厂模式原理时,你一定要牢牢记住“如何找到某一个类产品的正确共性功能”这个重点。
    优点:
  1. 对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提
    升组件的复用性.
  2. 当需要提升代码的扩展性并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上
  3. 解决跨平台带来的兼容性问题
    缺点
    增加新的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大不变,违背了开闭原则.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值