设计模式1-工厂模式

不废话,直接show code

1、没有使用工厂

如下,有一个运算类父类,有加减乘除四个子类实现

/**
 * 运算类
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Data
public abstract class Operation {
    private double number1 = 0;
    private double number2 = 0;

    protected Operation(double number1, double number2) {
        this.number1 = number1;
        this.number2 = number2;
    }

    /**
     * 获取运算结果
     *
     * @return
     */
    public abstract double getResult();
}

/**
 * 加法运算
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class OperationAdd extends Operation{
    protected OperationAdd(double number1, double number2) {
        super(number1, number2);
    }

    @Override
    public double getResult() {
        return getNumber1() + getNumber2();
    }
}

/**
 * 减法运算
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class OperationSub extends Operation{
    protected OperationSub(double number1, double number2) {
        super(number1, number2);
    }

    @Override
    public double getResult() {
        return getNumber1() - getNumber2();
    }
}

/**
 * 乘法运算
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class OperationMul extends Operation {
    protected OperationMul(double number1, double number2) {
        super(number1, number2);
    }

    @Override
    public double getResult() {
        return getNumber1() * getNumber2();
    }
}

/**
 * 除法运算
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class OperationDiv extends Operation {
    protected OperationDiv(double number1, double number2) {
        super(number1, number2);
    }

    @Override
    public double getResult() {
        return getNumber1() / getNumber2();
    }
}

客户端直接创建具体的运算子类实现运算功能


/**
 * 客户端-没有使用工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        double number1 = 4;
        double number2 = 2;

        //加法
        OperationAdd operationAdd = new OperationAdd(number1, number2);
        log.info("{} + {} = {}", number1, number2, operationAdd.getResult());

        //减法
        OperationSub operationSub = new OperationSub(number1, number2);
        log.info("{} - {} = {}", number1, number2, operationSub.getResult());

        //乘法
        OperationMul operationMul = new OperationMul(number1, number2);
        log.info("{} x {} = {}", number1, number2, operationMul.getResult());

        //除法
        OperationDiv operationDiv = new OperationDiv(number1, number2);
        log.info("{} / {} = {}", number1, number2, operationDiv.getResult());
    }
}

//运行结果
2021-11-27 15:13:46.038 INFO  [ ] [top.ccheng.design.learn.factory.v1.Client.main(Client.java:19)] - 4.0 + 2.0 = 6.0
2021-11-27 15:13:46.045 INFO  [ ] [top.ccheng.design.learn.factory.v1.Client.main(Client.java:23)] - 4.0 - 2.0 = 2.0
2021-11-27 15:13:46.046 INFO  [ ] [top.ccheng.design.learn.factory.v1.Client.main(Client.java:27)] - 4.0 x 2.0 = 8.0
2021-11-27 15:13:46.048 INFO  [ ] [top.ccheng.design.learn.factory.v1.Client.main(Client.java:31)] - 4.0 / 2.0 = 2.0

类图如下:
image.png

总结:

客户端需要知道所有的运算类,并直接与所有具体运算子类打交道,增加了客户端的使用/学习负担

2、简单工厂模式(Simple Factory Pattern)

简单工厂模式是指由一个工厂对象决定创建出哪一种产品类的实例,不属于GOF的23种设计模式之中。

如下,对上面没有工厂的例子增加一个简单工厂

/**
 * 运算工厂类
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class OperationFactory {
    public enum OperationType {
        /**
         * 加法
         */
        ADD,
        /**
         * 减法
         */
        SUB,
        /**
         * 乘法
         */
        MUL,
        /**
         * 除法
         */
        DIV
    }

    public static Operation createOperate(double number1, double number2, OperationType operationType) {
        Operation operation = null;
        switch (operationType) {
            case ADD:
                operation = new OperationAdd(number1, number2);
                break;
            case SUB:
                operation = new OperationSub(number1, number2);
                break;
            case MUL:
                operation = new OperationMul(number1, number2);
                break;
            case DIV:
                operation = new OperationDiv(number1, number2);
                break;
            default:
                throw new IllegalArgumentException("暂不支持的运算类型");
        }
        return operation;
    }
}

客户端只需要知道运算的类型即可,隐藏了具体的加减乘除算法实现类

/**
 * 客户端-使用了简单工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        double number1 = 4;
        double number2 = 2;

        //加法
        Operation operationAdd = OperationFactory.createOperate(number1, number2, OperationFactory.OperationType.ADD);
        log.info("{} + {} = {}", number1, number2, operationAdd.getResult());

        //减法
        Operation operationSub = OperationFactory.createOperate(number1, number2, OperationFactory.OperationType.SUB);
        log.info("{} - {} = {}", number1, number2, operationSub.getResult());

        //乘法
        Operation operationMul = OperationFactory.createOperate(number1, number2, OperationFactory.OperationType.MUL);
        log.info("{} x {} = {}", number1, number2, operationMul.getResult());

        //除法
        Operation operationDiv = OperationFactory.createOperate(number1, number2, OperationFactory.OperationType.DIV);
        log.info("{} / {} = {}", number1, number2, operationDiv.getResult());
    }
}

//运算结果
2021-11-27 15:28:47.493 INFO  [ ] [top.ccheng.design.learn.factory.v2.Client.main(Client.java:19)] - 4.0 + 2.0 = 6.0
2021-11-27 15:28:47.499 INFO  [ ] [top.ccheng.design.learn.factory.v2.Client.main(Client.java:23)] - 4.0 - 2.0 = 2.0
2021-11-27 15:28:47.499 INFO  [ ] [top.ccheng.design.learn.factory.v2.Client.main(Client.java:27)] - 4.0 x 2.0 = 8.0
2021-11-27 15:28:47.500 INFO  [ ] [top.ccheng.design.learn.factory.v2.Client.main(Client.java:31)] - 4.0 / 2.0 = 2.0

类图如下:
image.png

jdk中的简单工厂:

  • 日历类 java.util.Calendar 的 createCalendar 方法,根据类型创建不同的日历

image.png

总结:

客户端调用变得非常简单,但工厂类的职责相对过重,不易于扩展过于复杂的产品结构。

简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。

3、工厂方法模式(Factory Method Pattern)

工厂方法模式是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
在工厂方法模式中用户只需要关心所需产品对应的工厂,无需关心创建产品的细节。

如下,一个支付的例子

/**
 * 支付接口
 *
 * @author ccheng
 * @date 2021/11/27
 */
public interface IPay {
    /**
     * 支付
     * @param amount 金额
     */
    void pay(double amount);
}

/**
 * 微信支付
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class WeixinPay implements IPay {
    @Override
    public void pay(double amount) {
        log.info("使用微信支付{}元", amount);
    }
}

/**
 * 支付宝支付
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class AliPay implements IPay {
    @Override
    public void pay(double amount) {
        log.info("使用支付宝支付{}元", amount);
    }
}

对工厂本身也做一个抽象

/**
 * 支付接口工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
public interface IPayFactory {
    /**
     * 创建支付实现
     *
     * @return
     */
    IPay createPay();
}

/**
 * 微信支付工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class WeixinPayFactory implements IPayFactory {
    @Override
    public IPay createPay() {
        WeixinPay weixinPay = new WeixinPay();
        //初始化微信支付证书等等......
        return weixinPay;
    }
}

/**
 * 支付宝支付工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class AliPayFactory implements IPayFactory {
    @Override
    public IPay createPay() {
        AliPay aliPay = new AliPay();
        //初始化支付宝支付证书等等......
        return aliPay;
    }
}

客户端调用,只需要关心支付对应的工厂,无需关心支付创建的细节

/**
 * 客户端-工厂方法模式测试
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class Client {
    public static void main(String[] args) {
        double amount = 11.11;
        //微信支付
        IPay weixinPay = new WeixinPayFactory().createPay();
        weixinPay.pay(amount);
        //支付宝支付
        IPay aliPay = new AliPayFactory().createPay();
        aliPay.pay(amount);
    }
}

//运行结果
2021-11-27 16:33:52.905 INFO  [ ] [top.ccheng.design.learn.factory.v3.WeixinPay.pay(WeixinPay.java:15)] - 使用微信支付11.11元
2021-11-27 16:33:52.912 INFO  [ ] [top.ccheng.design.learn.factory.v3.AliPay.pay(AliPay.java:15)] - 使用支付宝支付11.11元

类图如下:
image.png

jdk中的工厂方法:

  • 日期格式化类 java.text.DateFormat 的 get(sun.util.locale.provider.LocaleProviderAdapter, int, int, java.util.Locale) 方法

image.png

工厂方法适用场景:

  1. 创建对象需要大量重复的代码
  2. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
  3. 一个类通过其子类来指定创建哪个对象

工厂方法缺点:

  1. 类的个数容易过多,增加复杂度
  2. 增加了系统的抽象性和理解难度

总结:

工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,唯一的工厂类职责会变得越来越多,不便于维护。
工厂方法模式依据单一职责原则,将工厂的职能进行拆分,对工厂本身也做一个抽象。

可能你已经发现了,本来简单工厂模式就是把子类选择权从客户端移去,而工厂方法模式又把子类选择权交还给客户端了。但交还的实际上是工厂类,具体的实现类还是对客户端隐藏的,真实场景中实现类可能是非常复杂的,比如微信支付还需要加载支付证书等等。

如果场景较少且不多变,选择简单工厂模式即可;如果场景多变,可以选择工厂方法模式,比如以后还可能扩展各个银行的支付实现。

4、抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类,强调的是一系列相关的产品对象一起使用创建对象需要大量重复的代码。

如下,视频资料及子类实现

/**
 * 视频资料
 *
 * @author ccheng
 * @date 2021/11/27
 */
public interface IVideo {
    /**
     * 录制视频
     */
    void record();

    /**
     * 播放视频
     */
    void play();
}

/**
 * Java视频
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class JavaVideo implements IVideo {
    @Override
    public void record() {
        log.info("正在录制Java视频");
    }

    @Override
    public void play() {
        log.info("正在播放Java视频");
    }
}

/**
 * Python视频
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class PythonVideo implements IVideo {
    @Override
    public void record() {
        log.info("正在录制Python视频");
    }

    @Override
    public void play() {
        log.info("正在播放Python视频");
    }
}

如下,笔记及子类实现

/**
 * 笔记
 *
 * @author ccheng
 * @date 2021/11/27
 */
public interface INote {
    /**
     * 编写笔记
     */
    void write();

    /**
     * 阅读笔记
     */
    void read();
}

/**
 * Java笔记
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class JavaNote implements INote {
    @Override
    public void write() {
        log.info("正在编写Java笔记");
    }

    @Override
    public void read() {
        log.info("正在阅读Java笔记");
    }
}

/**
 * Python笔记
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class PythonNote implements INote {
    @Override
    public void write() {
        log.info("正在编写Python笔记");
    }

    @Override
    public void read() {
        log.info("正在阅读Python笔记");
    }
}

假设,一个视频和一个笔记,构成了一个课程

/**
 * 课程抽象工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
public interface ICourseFactory {

    /**
     * 创建视频资料
     *
     * @return 视频资料
     */
    IVideo createVideo();

    /**
     * 创建笔记资料
     *
     * @return 笔记资料
     */
    INote createNote();
}

/**
 * Java课程工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class JavaCourseFactory implements ICourseFactory {

    @Override
    public IVideo createVideo() {
        return new JavaVideo();
    }

    @Override
    public INote createNote() {
        return new JavaNote();
    }
}

/**
 * Python课程工厂
 *
 * @author ccheng
 * @date 2021/11/27
 */
@Slf4j
public class PythonCourseFactory implements ICourseFactory {

    @Override
    public IVideo createVideo() {
        return new PythonVideo();
    }

    @Override
    public INote createNote() {
        return new PythonNote();
    }
}

客户端调用

/**
 * 客户端-抽象工厂方法
 *
 * @author ccheng
 * @date 2021/11/27
 */
public class Client {
    public static void main(String[] args) {
        ICourseFactory courseFactory = new JavaCourseFactory();
        //只需要把 JavaCourseFactory 替换成 PythonCourseFactory 整个课程体系包括视频、笔记,都会从Java替换成Python的
//        ICourseFactory courseFactory = new PythonCourseFactory();
        IVideo video = courseFactory.createVideo();
        video.record();
        video.play();
        INote note = courseFactory.createNote();
        note.write();
        note.read();
    }
}

视频和笔记是配套的,即Java视频和Java笔记是一个课程,通过抽象工厂限制了客户端出现Java视频搭配Python笔记的情况。
同时只需要把 JavaCourseFactory 替换成 PythonCourseFactory 整个课程体系包括视频、笔记,都会从Java替换成Python的

类图如下:
image.png

jdk中的抽象工厂:

  • java.sql.Connection 相当于抽象工厂 ICourseFactory
  • 抽象方法的返回值java.sql.Statement和java.sql.PreparedStatement,则对应 IVideo 和 INote
  • 而对应的具体实现,则有MySQL、Oracle等产品实现

image.png

缺点:
假设现在需要将课后练习加入到课程中,那么我们的代码从抽象工厂,到具体工厂全部都要调整。

总结:

最大的好处是易于交换整个产品系列,最大的缺点增加功能需要大批量改动

posted on 2021-11-27 18:58  _ccheng  阅读(50)  评论(0)    收藏  举报