简单工厂、工厂方法、抽象工厂和策略模式

摘要

本文简单介绍软件开发过程中面临的痛点和几个总体原则。详细介绍了简单工厂、工厂方法、抽象工厂和策略模式的实现,以及各种模式之间的相似、区别。

背景

开发面临哪些问题(痛点)?

相信做过大型软件开发的tx都遇到过以下类似问题。在原有代码基础上扩展新功能,需修改历史代码,又会影响已交付功能。且还需同步修改测试用例。debug时则陷入"无尽"的方法调用、分支逻辑判断中。

软件开发大致包括扩展 软件新功能、维护 旧功能、测试debug 。如何减少上述过程耗费的时间精力,是设计模式方法论的最终目的降低 软件开发、测试、变更、维护成本。

代码需具备哪些特性(需求)?

代码需具备高内聚,低耦合 总特性才能减缓软件开发中面临的痛点。代码结构清晰易看懂易维护、可复用容易扩展 ,且灵活性好。

tips:如何学习设计模式?

  • 学习过程中,一定要按照自己对知识的理解,独立输出一份可以表达某一种模式思想的toy代码。
  • 有开发经验的同学,可以从软件开发过程中遇到的痛点、需求出发,尝试用不同的设计模式解决相同的问题,思考总结 出各种设计模式之间的联系和区别,而不是生搬硬套。

总体原则

单一职责

一个类/方法应该仅有一个引起它变化的原因

开放-封闭原则

软件实体(类、模块、函数等)应该可以扩展,但不可修改。已完成的抽象应尽可能覆盖后续可能的变化

依赖倒置原则

高层不应该依赖底层模模块,两者都应依赖抽象接口

抽象不应该依赖细节。细节应该依赖抽象。针对接口编程而非实现编程

里氏替换原则

子类必须能替换它们的父类

只有当子类可以替换父类,父类才能真正被复用,子类也能够在父类基础上增加新的行为。例如:在软件设计领域,鸟会飞,企鹅不会飞,企鹅不是鸟的子类,两者没有继承关系

tips:实现总体原则的方法论:24个设计模式

23 种设计模式详解(全23种) - 知乎

设计模式的目的是开发出高内聚、低耦合的软件系统,高内聚、低耦合离不开面向对象中封装、继承、多态特性。封装、继承、多态在java中的实现则是类、接口父子类、接口引用不同实现对象。
面向对象三大特性:封装、继承、多态

  • 封装,即隐藏对象的属性和实现细节,仅对外公开接口。将数据(属性)和操作数据的行为(方法)组合为类。
  • 继承,即已有的类中派生出新的类,新的类具备已有类的数据属性和行为,并能扩展新的能力
  • 多态,即可以用一种类型的变量引用另一种类型的对象,例如在java中,接口可以引用不同接口实现类对象。

简单工厂模式

简单工厂类通过输入参数创建具体对象的模式,简化client调用负担。简单工厂类不必单独创建,可结合在其他类中。

适用于接口/父类有多个实现/子类 、且可能继续扩展的**创建对象(关注点在创建对象)**的场景

需求举例

实现加法、减法、乘法、除法,后续还可能扩展根号、乘方等

分析

定义操作符父类,实现不同加、减等子类,简单工厂方法根据输入参数创建并返回不同的操作符子类。后续扩展 ,只需新增 子类,并在简单工厂类新增条件分支

Java实现

抽象操作符父类

定义操作符父类,抽象出getResult()方法

java 复制代码
public abstract class Operation {
    private double numberA = 0;
    private double numberB = 0;

/**省略get和set方法*/
    public double getResult(){
        return 0;
    }
}

加法子类

加法子类实现操作符父类的getResult()方法

java 复制代码
public class AddOperation extends Operation{
    @Override
    public double getResult(){
        return super.getNumberA() + super.getNumberB();
    }
}

简单工厂类

简单工厂类封装client需要判断的逻辑,根据传入参数返回不同的操作符子类对象。

java 复制代码
public class OperationFactory {
    public static Operation createOperation(String opr){
        Operation operation = null;
        switch (opr){
            case "+":
                operation = new AddOperation();
                break;
            default:
        }
        return operation;
    }
}

客户端调用

java 复制代码
 public static void main(String[]args){
        Operation opr ;
        opr = OperationFactory.createOperation("+");
        opr.setNumberA(1);
        opr.setNumberB(2);
        System.out.println(opr.getResult());
    }

扩展新功能

扩展减法子类

减法子类实现操作符父类的getResult()方法

java 复制代码
public class SubOperation extends Operation{
    @Override
    public double getResult(){
        return super.getNumberA() - super.getNumberB();
    }
}

修改简单工厂类

简单工厂类扩展case条件分支,提供创建减法操作对象功能

java 复制代码
    public static Operation createOperation(String opr){
        Operation operation = null;
        switch (opr){
            case "+":
                operation = new AddOperation();
                break;
            case "-"://添加减法类
                operation = new SubOperation();
                break;
            default:

        }
        return operation;
    }

存在的问题?

新增操作符子类,需要修改简单工厂类 ,添加case语句。扩展新业务需要修改历史业务代码,不符合"开闭原则" 。后续工厂方法模式,通过抽象工厂类接口,满足"开闭原则"。

策略模式

定义不同算法簇并分别封装,不同算法之间可相互替换,算法的变化不会影响使用算法的人

适用于同一个类中出现不同的业务规则场景,通过策略模式封装处理不同变化。
不同的行为堆砌在一个类中,很难避免用条件语句选择合适行为,将行为封装在独立的strategy类中,可在类中消除条件语句

Java实现

策略接口

java 复制代码
public interface IStrategy {
    double algorithm(double money);
}

业务策略1

java 复制代码
public class CashReturn implements IStrategy{
    private double moneyCondition = 0.d;
    private double moneyReturn  = 0.d;
    public CashReturn(double moneyCondition,double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }
    @Override
    public double algorithm(double money) {
        if(money > moneyCondition){
            return money - Math.floor( money/moneyCondition) * moneyReturn;
        }
        return money;
    }
}

业务策略2

java 复制代码
public class CashRebate implements IStrategy{
    private double rebate = 1d;

    public CashRebate(double rebate){
        this.rebate = rebate;
    }

    @Override
    public double algorithm(double money) {
        return money*rebate;
    }
}

策略上下文类

java 复制代码
public class CashContext {
    IStrategy strategy;
    public CashContext(String type){
        switch (type){
            case "discount":
                strategy = new CashRebate(0.8);
                break;
            case "return":
                strategy = new CashReturn(200,30);
                break;
            default:
        }
    }

    public double getResult(double money){
        return strategy.algorithm(money);
    }
}

client调用

java 复制代码
    public static void main(String[]args){
        double inputMoney = 100;
        String type = "discount";
        //1.简单工厂方法:类型为参数通过工厂方法返回不同计算对象,对象计算最终值。
        //client需要使用工厂类  +  不同计算类
        //2.类型作为参数,通过上下文对象,直接获取最终值。将不同计算对象封装入上下文对象。
        //client只需要使用上下文类。
        double outputMoney = new CashContext(type).getResult(inputMoney);
        System.out.println(outputMoney);
    }

简单工厂和策略模式区别

策略模式将简单工厂创建对象过程封装在上下文类内部,不再对外暴露具体对象,降低了client使用类的难度。本质是在继承、多态的基础上新增了一层上下文类,达到client和业务逻辑类之间的解耦。
具体选择 :从业务角度考虑,如果仅创建对象,使用工厂模式 。如果业务存在不同的规则 ,使用策略模式

从侧重点理解:

  • 简单工厂关注对象的创建。例如实例化不同对象。
  • 策略模式关注业务行为封装。例如实现某一系列算法。

从client角度理解:

  • 简单工厂先实例化对象,再调用对象的方法
  • 策略模式直接调用上下文对象的方法。实例化对象放在了上下文类中。

从测试角度理解:

  • 简单工厂模式需要测试对象的每个方法
  • 策略模式只需要调用上下文对象的方法即可

工厂方法模式

定义创建对象的工厂接口,让类的实例化延迟到工厂子类。工厂方法模式,通过抽象出工厂接口 ,解决了简单工厂模式无法满足"开闭原则"的问题。

需求举例

同样以简单工厂模式中介绍的加、减操作实现为例。

Java实现

操作符父类

定义操作符父类抽象方法getResult()

java 复制代码
public abstract class IOperation {
    public double numA = 0;
    public double numB = 0;
    abstract double getResult();
}

加法子类

定义加法操作符子类,并实现getResult()

java 复制代码
public class AddOperation extends IOperation{
    public AddOperation(double numA,double numB){
        super.numA = numA;
        super.numB = numB;
    }
    @Override
    public double getResult() {
        return numA + numB;
    }
}

工厂方法接口

定义工厂类接口createOperation()

java 复制代码
public interface IFactory {
    IOperation createOperation(double numA,double numB);
}

加法工厂子类

定义加法工厂子类,并实现createOperation()方法

java 复制代码
public class AddFactory implements IFactory{
    @Override
    public IOperation createOperation(double numA,double numB) {
        return new AddOperation(numA,numB);
    }
}

客户端调用

java 复制代码
public static void main(String[]args){
        IOperation opr = new AddFactory().createOperation(1,3);
        System.out.println(opr.getResult());
    }

扩展新功能

减法子类

java 复制代码
public class SubOperation extends IOperation{
    public SubOperation(double numA,double numB){
        super.numA = numA;
        super.numB = numB;
    }
    @Override
    public double getResult() {
        return numA - numB;
    }
}

减法工厂子类

java 复制代码
public class SubFactory implements IFactory{
    @Override
    public IOperation createOperation(double numA,double numB) {
        return new SubOperation(numA,numB);
    }
}

优缺点

满足开放封闭原则

  • 扩展新业务,例如新增减法操作,只需新增减法子类减法工厂子类,无需修改历史代码。
  • client调用,仅需修改new AddFactory ()为new SubFactory()。

业务扩展产生过多工厂子类

不同于简单工厂模式,新增业务只需增加switch分支,工厂方法模式新增业务需要新增工厂子类。不断扩充业务时,工厂子类也会不断扩充冗余。

简单工厂与工厂方法区别

  • 简单工厂使用工厂类直接创建对象,而工厂方法则定义工厂接口,工厂子类负责创建对象。
  • 简单工厂不满足"开闭原则",工厂方法满足。
  • 简单工厂扩充业务无需新增工厂类,工厂方法会不断扩充工厂子类。

抽象工厂模式

抽象工厂模式,类似工厂方法模式,提供创建不同种类对象的接口方法。例如提供创建A对象、B对象方法。

需求举例

支持sqlserver、access类型数据库连接user表、department表,后续还可能扩展mysql方式,以及其他表

分析

目的是方便替换不同访问数据库的方式,因此需要在访问数据库方式上使用继承和多态特性。需要抽象出顶层接口,创建不同方式访问对象。具体实现思路:

  1. 抽象出user、department表行为接口,分别以sqlserver、access方式实现上述的表接口。
  2. 工厂模式创建user、department对象
  3. client利用工厂方法直接调用user、department行为接口

Java实现

user、department接口和实现

java 复制代码
//接口定义
public interface IUserService {
    User getUser();
}

//Access实现
public class AccessUser implements IUserService {
    @Override
    public User getUser() {
        System.out.println("访问access get user");
        return null;
    }
}

//Sqlserver实现
public class SqlserverUser implements IUserService {
    @Override
    public User getUser() {
        System.out.println("访问Sqlserver get user");
        return null;
    }
}
java 复制代码
//接口定义
public interface IDepartmentService {
    Department getDepartment();
}

//Access实现
public class AccessDepartment implements IDepartmentService {
    @Override
    public Department getDepartment() {
        System.out.println("访问access get department");
        return null;
    }
}
//Sqlserver实现
public class SqlserverDepartment implements IDepartmentService {

    @Override
    public Department getDepartment() {
        System.out.println("访问Sqlserver get department");
        return null;
    }
}

优化简单工厂类

使用反射 替代switch条件语句,且反射的参数值,还可以通过配置文件得到。达到了解耦+开闭原则。

java 复制代码
public class DataAccessFactory {
//根据变量的值,选择Access或Sqlserver方式访问数据库。该值可以写入配置文件中,更加方便
    String dbAccessMethod = "test.factory.abstfactory.impl.access.";
    public IUserService createUserService() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        String className =dbAccessMethod + "AccessUser";
        Class clazz = Class.forName(className);
        return  (IUserService) clazz.getDeclaredConstructor().newInstance();
    }

    public IDepartmentService creteDeparmentService() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        String className =dbAccessMethod + "AccessDepartment";
        Class clazz = Class.forName(className);
        return (IDepartmentService) clazz.getDeclaredConstructor().newInstance();
    }
}

client调用

java 复制代码
public static void main(String[]args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        DataAccessFactory dataAccess = new DataAccessFactory();

        IUserService userService = dataAccess.createUserService();
        userService.getUser();

        IDepartmentService departmentService = dataAccess.creteDeparmentService();
        departmentService.getDepartment();
    }

优缺点

抽象工厂类似工厂方法,但是抽象工厂提供多个接口。扩展业务时新增工厂子类,造成工厂类簇冗余,但是具备开闭原则。而简单工厂模式扩展业务时,只需在工厂类中新增判断分支,不需要新增工厂子类,但是不能满足开闭原则。

抽象工厂总结

在抽象工厂模式中,可以使用简单工厂模式中的工厂类 + 反射 技术,替换 简单工厂方法中switch 分支语句,再+配置文件 ,可以将反射需要的变量配置在配置文件中。这种方式解决了工厂类冗余问题,又满足开闭原则

觉得不错,点个👍吧,(*^▽^*)

相关推荐
芒果披萨5 分钟前
El表达式和JSTL
java·el
q567315236 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平31 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨34 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar43 分钟前
yelp数据集上识别潜在的热门商家
开发语言·python
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++