Loading

设计模式之工厂方法模式

设计模式总结


什么是设计模式?

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。我们使用设计模式可以让代码更容易被重用,更容易让人们理解的,让代码实现高内聚低耦合。应用成熟的设计模式能够增强代码的可复用性、可扩展性与可维护性。

设计模式的分类

GoF(《设计模式:可复用面向对象软件的基础》的作者)总结的设计模式虽然只有23种,但是现在的设计模式已经超过了23种,我们目前先把23种设计模式学好就可以了。

设计模式我们能不能用上呢?

  • 如果上街买菜,但是不会数学计算,能买到菜,但是有可能会被黑心的商家坑了
  • 当买房子装修时,高中学的立体几何就有用了
  • ... ...

设计模式也是一样的,如果你的代码多了却没用用设计模式,那么代码将一团糟,很难维护,别人想修改代码却无从下手,那么代码将牵一发动全身,这时候,设计模式就很重要了。

  • 创建型:除了直接new来实例化对象外,提供了多种隐藏创建逻辑的生成对象的方法

  • 结构型:通过对象和类的组合,得到新的结构和功能

  • 行为型:解决对象之间的通行和功能职责分配

常用设计模式

graph LR root(常用设计模式) --> A(工厂方法模式) root(常用设计模式) --> B(抽象工厂模式) root(常用设计模式) --> C(策略模式) root(常用设计模式) --> D(装饰者模式) root(常用设计模式) --> E(板方法模式) root(常用设计模式) --> F(外观模式) root(常用设计模式) --> G(状态模式) root(常用设计模式) --> H(单例模式) root(常用设计模式) --> I(....)

我们今天讲一讲关于工厂的设计模式...

几个原则

在此开始之前,先了解几个原则:

  1. 单一职责原则:对于一个类而言,应该仅有一个引起它变化的原因
  2. 开闭原则:软件实体应该(类、模块、函数)应该可以扩展,但是不可以修改(可扩展,不可修改)
  3. 依赖倒转原则:
    • 高层模块不应该依赖低层模块,两个都应该依赖抽象
    • 抽象不应该依赖细节,细节应该依赖抽象
  4. 里氏代换原则:子类型必须能够替换掉他们的父类型(子类可以以父类的身份出现,用父类的引用指向子类对象)
  5. 迪米特法则:如果两个类不必彼此直接通信,那这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用

简单工厂模式

现在我们有一个需求,要我们做一个计算器出来,可能会这么写:

package top.linzeliang.designpattern

import java.util.Scanner;

public class Program {
    static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //输入数据
        System.out.println("请输入数字A: ");
        double a = sc.nextDouble();
        System.out.println("请选择运算符(+ - * /): ");
        String operator = sc.next();
        System.out.println("请输入数字B: ");
        double b = sc.nextIntDouble();
        double result = 0;
        //Java7之后switch中支持使用String
        //选择运算符
        switch (operator) {
            case "+":
                result = a + b;
                break;
            case "-":
                result  = a - b;
                break;
            case "*":
                result = a * b;
                break;
            case "/":
                if (b != 0) {
                    result = a / b;
                } else {
                    System.out.println("除数不能为0");
                }
                break;
        }
        //输出
        System.out.println("计算结果是:" + result);
    }
}

这样子写时没错,我们之前学C的时候就是这么写的,但是这个程序只能满足当前的需求,不容易维护,也不容易拓展,如果我要增加求余功能呢,那这个代码又得重新改了(违背了开闭原则)。

在三国时期,发明了刻版印刷,为印刷书方便了很多,不用再手抄字了,只要一个刻版印刷,就可以制作出很多的书,但是,如果在刻版印刷做完了之后,发现文章需要改动,或者文章写错了写字,那就得需要重头开始再刻一遍了,于是后来出现了活字印刷,只要把需要替换的字拿出来即可,效率大大提升

于是我们可以考虑使用封装继承多态把程序的耦合度降低,要可维护、可复用、可扩展

public class OperationFactory {
    public static void main(String[] args) {
        //通过工厂创建对象
        Operation oper = OperationFactory.createOperate("+");
        oper.setA(1);
        oper.setB(2);
        System.out.println(oper.getResult());
        //输出3
    }
}

class Operation {
    private double a;
    private double b;
    
    public double getA() {
        return a;
    }
    public void setA(double a) {
        this.a = a;
    }
    public double getB() {
        return b;
    }
    public void setB(double b) {
        this.b = b;
    }
    public double getReuslt() {
        double result = 0;
        return result;
    }
}

class OperationAdd extends Operation {
    //重写getResult方法
    @Override
    public double getReuslt() {
        double result = 0;
        //进行计算
        result = a + b;
        return result;
    }
}

class OperationSub extends Operation {
    //重写getResult方法
    @Override
    public double getReuslt() {
        double result = 0;
        //进行计算
        result = a - b;
        return result;
    }
}
...
    
/**
 * 工厂运算类
 */
public class OperationFactory {
    public static Operation createOperation(String operate) {
        Operation oper = null;
        switch (operate) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            case "*":
                oper = new OperationMul();
                break;
            case "/":
                oper = new OperationDiv();
                break;
        }
        return oper;
    }
}

这次改造之后,每个运算方法通过继承Operation类,用OperationFactory工厂来操控这些具体的实现类,避免了与具体产品的依赖

简单工厂模式优点:工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖

简单工厂模式缺点:违背了开闭原则

简单工厂模式的UML类图

classDiagram 运算类 <|-- 加法类 运算类 <|-- 减法类 运算类 <|-- 乘法类 运算类 <|-- 除法类 运算类: +a double 运算类: +b double 运算类: +getA() 运算类: +getB() 运算类: +setA() 运算类: +setB() 运算类: +getResult() 简单工厂类 --> 运算类 简单工厂类: +createOperate() 加法类: +getResult() 减法类: +getResult() 乘法类: +getResult() 除法类: +getResult()

工厂方法模式

对于上面的简单工厂模式来说,如果要增加一个就余的类,那么就先需要创建一个继承Operation的子类,然后修改工厂中的方法,再case中加入判断,然后还要再去更改客户端,这样子不但没有简化难度,而且害增加了许多类和方法,把问题复杂化了,而且违背了开闭原则,所以,可以考虑一下工厂方法模式了:

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

工厂方法模式的UML类图:

classDiagram 运算类 <|-- 加法类 运算类 <|-- 减法类 运算类 <|-- 乘法类 运算类 <|-- 除法类 运算类: +a double 运算类: +b double 运算类: +getA() 运算类: +getB() 运算类: +setA() 运算类: +setB() 运算类: +getResult() 加法类: +getResult() 减法类: +getResult() 乘法类: +getResult() 除法类: +getResult() 抽象工厂 <|-- 加法工厂 抽象工厂 <|-- 减法工厂 抽象工厂 <|-- 乘法工厂 抽象工厂 <|-- 除法工厂 抽象工厂 --> 运算类 加法工厂 --> 加法类 减法工厂 --> 减法类 乘法工厂 --> 乘法类 除法工厂 --> 除法类

即:

代码实现一下:

//新增一个OperationFactory接口
interface Operation {
    Operation createOperation();
}

class AddFactory implements OperationFactory {
    @Override
    public Operation createOperation() {
        return new OperationAdd();
    }
}
...
    
public class Test {
    public static void main(String[] args) {
        //创建加法工厂对象
        OperationFactory factory = new AddFactory();
        Operation operarion = factory.createOperation();
        operation.setA(1);
        operation.setB(2);
        System.out.println(operation.getResult());
        //结果3
    }
}

工厂方法模式克服了简单工厂模式的开闭原则,又保持了封装对象创建对象过程的优点,在要更换对象时,不需要做大的改动就可以实现。但是由于每增加一个产品,就需要增加一个产品工厂的类,增加了额外的开发量(利用反射技术可以解决这个问题

END

posted @ 2020-09-20 00:07  linzeliang  阅读(251)  评论(0)    收藏  举报