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

 摘要

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

背景

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

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

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

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

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

tips:如何学习设计模式?

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

总体原则

单一职责

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

开放-封闭原则

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

依赖倒置原则

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

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

里氏替换原则

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

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

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

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

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

面向对象三大特性:封装、继承、多态

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

简单工厂模式

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

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

 需求举例

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

 分析

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

Java实现

抽象操作符父类

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

public abstract class Operation {private double numberA = 0;private double numberB = 0;/**省略get和set方法*/public double getResult(){return 0;}
}

加法子类

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

public class AddOperation extends Operation{@Overridepublic double getResult(){return super.getNumberA() + super.getNumberB();}
}

简单工厂类

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

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

客户端调用

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

扩展新功能

扩展减法子类

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

public class SubOperation extends Operation{@Overridepublic double getResult(){return super.getNumberA() - super.getNumberB();}
}

修改简单工厂类

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

    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实现

策略接口

public interface IStrategy {double algorithm(double money);
}

业务策略1

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;}@Overridepublic double algorithm(double money) {if(money > moneyCondition){return money - Math.floor( money/moneyCondition) * moneyReturn;}return money;}
}

业务策略2

public class CashRebate implements IStrategy{private double rebate = 1d;public CashRebate(double rebate){this.rebate = rebate;}@Overridepublic double algorithm(double money) {return money*rebate;}
}

策略上下文类

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调用

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

public abstract class IOperation {public double numA = 0;public double numB = 0;abstract double getResult();
}

加法子类

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

public class AddOperation extends IOperation{public AddOperation(double numA,double numB){super.numA = numA;super.numB = numB;}@Overridepublic double getResult() {return numA + numB;}
}

工厂方法接口

定义工厂类接口createOperation()

public interface IFactory {IOperation createOperation(double numA,double numB);
}

加法工厂子类

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

public class AddFactory implements IFactory{@Overridepublic IOperation createOperation(double numA,double numB) {return new AddOperation(numA,numB);}
}

客户端调用

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

扩展新功能

减法子类

public class SubOperation extends IOperation{public SubOperation(double numA,double numB){super.numA = numA;super.numB = numB;}@Overridepublic double getResult() {return numA - numB;}
}

减法工厂子类

public class SubFactory implements IFactory{@Overridepublic 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接口和实现

//接口定义
public interface IUserService {User getUser();
}//Access实现
public class AccessUser implements IUserService {@Overridepublic User getUser() {System.out.println("访问access get user");return null;}
}//Sqlserver实现
public class SqlserverUser implements IUserService {@Overridepublic User getUser() {System.out.println("访问Sqlserver get user");return null;}
}
//接口定义
public interface IDepartmentService {Department getDepartment();
}//Access实现
public class AccessDepartment implements IDepartmentService {@Overridepublic Department getDepartment() {System.out.println("访问access get department");return null;}
}
//Sqlserver实现
public class SqlserverDepartment implements IDepartmentService {@Overridepublic Department getDepartment() {System.out.println("访问Sqlserver get department");return null;}
}

优化简单工厂类

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

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调用

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分支语句,再+配置文件,可以将反射需要的变量配置在配置文件中。这种方式解决了工厂类冗余问题,又满足开闭原则

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/585958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年年终总结 —— 致满载荣誉或满脸惆怅的你

开篇请允许我引用歌曲《西楼儿女》的一句歌词: 陌生的朋友你请听我讲,许多年前我也曾有梦想,想过满载荣誉回到家乡,这肆意的风压弯了海棠。 2023年即将过去,不管你这一年经历了多少,或是职场的得心应手、情…

微服务事务处理:CAP 定理和最终一致性的关系

CAP 定理和最终一致性 CAP 定理和最终一致性是两个密切相关的概念,但它们在范围和细节上有所不同。以下是比较: CAP 定理 **正式陈述:**在分布式系统中,最多只能同时满足以下三个保证中的两个:一致性、可用性和分区…

STL——遍历算法

1.for_each 函数原型&#xff1a; for_each(iterator beg, iterator end, _func);——// 遍历算法 遍历容器元素&#xff1b; beg 开始迭代器&#xff1b;end 结束迭代器&#xff1b; _func 函数或者函数对象 #include<iostream> using namespace std; #include<ve…

C++ 判断 float 或 double 数据小数点后位数的方法

文章目录 Method 1Method 2Method 3 Method 1 #include <cmath> int countDecimalPlaces(double num) { if (num 0) return 0; // 避免除以零错误 return std::floor(std::log10(std::abs(num - std::floor(num)))) 1; }Method 2 #include <string> #…

GBASE南大通用-小内存单机安装GBase 8c分布式数据库实践

* 这种小内存部署方式仅用于分布式数据库个人学习使用&#xff0c;不建议用于其他用途。 随着数据高并发复杂场景业务需求不断增多&#xff0c;信息数据呈现出爆炸式增长、多源多维、数据类型繁复等特征。在这一趋势下&#xff0c;目前分布式数据库因其架构的天然优势&#xf…

Creo各版本安装指南

下载链接 https://pan.baidu.com/s/1VyP0_185mJeBiorlpUJqrQ?pwd0531 1.鼠标右击【Creo10.0(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Creo10.0(64bit)】。 ​2.打开解压后的文件夹&#xff0c;鼠标右击【Crack】选择【解压到…

mysql 数据查重与查重分页

起因是公司的crm录入不规范,有重复数据。 之后考虑到需要手动处理,首先需要自动找出重复的数据 查重要求: 存在多个不允许重复的字段,任一字段重复,则判断为同一个客户。划分到同一重复组中。 查重sql如下 SELECT CONCAT(组, dense_rank() OVER (ORDER BY group_key)) …

Winform RDLC报表(数据库连接、报表函数使用、动态表头)

文章目录 NuGet安装库数据库连接报表设计报表引用添加报表 数据集设计方法一手动添加方法二——连接数据库添加 关联报表与数据集表格数据与数据集数据设计表格格式、字体设计报表数据字段绑定 Winform 使用报表控件数据库填充数据集从数据库获取与数据源相同字段的数据 动态表…

数据库一般会采取什么样的优化方法?

数据库一般会采取什么样的优化方法&#xff1f; 1、选取适合的字段属性 为了获取更好的性能&#xff0c;可以将表中的字段宽度设得尽可能小。 尽量把字段设置成not null 执行查询的时候&#xff0c;数据库不用去比较null值。 对某些省份或者性别字段&#xff0c;将他们定义为e…

【并发】AtomicInteger很安全

AtomicInteger 简介与常规用法 AtomicInteger 是 Java 中 java.util.concurrent.atomic 包下的一个类。用于实现原子操作的整数。它是一个基于CAS&#xff08;Compare-And-Swap&#xff09;实现的原子整数类。它提供了一系列的原子操作&#xff0c;确保对整数的操作是原子性的&…

JavaScript(ES6)数据结构与算法之树

6. 树 文章目录 6. 树6.1 概念6.2 二叉树6.3 二叉搜索树概念代码实现插入遍历获取最值搜索删除节点 6.4 红黑树红黑树概念红黑树规则平衡原理 6.1 概念 非线性结构 n(n>0)个节点构成的有限集合&#xff0c;n0时称为空树 对于任一非空树 有一个根节点其余节点可以构成子树 …

python 中断点调试 pdb 包的介绍及使用

pdb 的简介 pdb&#xff08;python debugger&#xff09; 是 python 中的一个命令行调试包&#xff0c;为 python 程序提供了一种交互的源代码调试功能&#xff0c;其官方使用文档链接为 pdb — Python 的调试器。 pdb 的主要功能包括设置断点、单步调试、进入函数调试、查看当…

Django、Python版本升级问题大汇总

Django3.0升级到4.1,Python3.8升级到3.11.6问题大汇总 报错1:ERROR: Could not build wheels for cffi, uWSGI, which is required to install pyproject.toml-based projects ERROR: Could not build wheels for cffi, uWSGI, which is required to install pyproject.tom…

golang 图片加水印,字体文件从哪里找

鼠标左键双击此电脑图标在此电脑文本框输入电脑默认字体地址&#xff1a;C:\Windows\Fonts找到需要用到的字体文件&#xff0c;复制到指定文件夹

python企业车辆车货信息平台 s05fw

车货信息平台系统可具体分为货源方、平台方、承运方三部分。其中前端要求包含货源方&#xff1a;发布货源信息、选择承运方、司机服务评价&#xff1b;平台方&#xff1a;账户管理、货主、司机资质审核、聊天功能&#xff1b;承运方&#xff1a;车辆信息上传、个人车主发布车源…

Ubuntu Desktop 死机处理

Ubuntu Desktop 死机处理 当 Ubuntu Desktop 死机时&#xff0c;除了长按电源键重启&#xff0c;还可以使用如下两种方式处理。 方式1&#xff1a;ctrlaltFn 使用 ctrl alt F3~F6: 切换到其他 tty 命令行。 执行 top 命令查看资源占用最多的进程&#xff0c;然后使用 kill…

HEX报文协议打包生成工具

本工具可以用于灵活定制各种格式的报文。以下是定制报文中每个字段的说明&#xff1a; isbig&#xff1a;指示报文中的字节顺序是否为大端序&#xff08;Big Endian&#xff09;。如果为true&#xff0c;则表示使用大端序&#xff1b;如果为false&#xff0c;则表示使用小端序…

centrifuge5.0.1版本请求websocket实例

目录 一、安转 二、快速开始 三、实例开始 centrifuge提供了一个客户端&#xff0c;可使用纯 WebSocket 或一种替代传输&#xff08;HTTP 流、SSE/EventSource、实验性 WebTransport&#xff09;从 Web 浏览器、ReactNative 或 NodeJS 环境连接到Centrifugo或任何基于 Cent…

WorkPlus局域网即时通讯软件的领航者,连接高效协作的利器

在快速发展的商业环境中&#xff0c;高效的内部沟通和协作对于企业的成功至关重要。而局域网即时通讯软件则成为实现内部高效沟通的必备工具。作为一款领航者级别的局域网即时通讯软件&#xff0c;WorkPlus通过卓越的性能和创新的技术&#xff0c;成为了众多企业的首选之一。 W…

ggplot2 | line plot 分组及均值线:聚类后的表达变化趋势图

1. 效果图 2. 预处理及绘图 # 输入数据 > head(dat)Species cid variable value 1 setosa 1 Sepal.Length 5.1 2 setosa 2 Sepal.Length 4.9 3 setosa 3 Sepal.Length 4.7 4 setosa 4 Sepal.Length 4.6 5 setosa 5 Sepal.Length 5.0 6 setos…