简单工厂、工厂方法、抽象工厂和策略模式
摘要
本文简单介绍软件开发过程中面临的痛点和几个总体原则。详细介绍了简单工厂、工厂方法、抽象工厂和策略模式的实现,以及各种模式之间的相似、区别。
背景
开发面临哪些问题(痛点)?
相信做过大型软件开发的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方式,以及其他表
分析
目的是方便替换不同访问数据库的方式,因此需要在访问数据库方式上使用继承和多态特性。需要抽象出顶层接口,创建不同方式访问对象。具体实现思路:
- 抽象出user、department表行为接口,分别以sqlserver、access方式实现上述的表接口。
- 工厂模式创建user、department对象
- 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分支语句,再+配置文件,可以将反射需要的变量配置在配置文件中。这种方式解决了工厂类冗余问题,又满足开闭原则
觉得不错,点个👍吧,(*^▽^*)
相关文章:
简单工厂、工厂方法、抽象工厂和策略模式
摘要 本文简单介绍软件开发过程中面临的痛点和几个总体原则。详细介绍了简单工厂、工厂方法、抽象工厂和策略模式的实现,以及各种模式之间的相似、区别。 背景 开发面临哪些问题(痛点)? 相信做过大型软件开发的tx都遇到过以下类似…...
junit mocktio request打桩
Controller下request组装参数 HttpServletRequest request new MockHttpServletRequest(); ((MockHttpServletRequest) request).addHeader("router","login"); ((MockHttpServletRequest) request).addParameter("test","wwww"); …...
第十四节TypeScript 联合类型
1、简介 联合类型可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。 注意:只能赋值指定的类型,如果赋值其它类型就会报错的。 2、创建联合类型的语法格式: Type1|Type2|Type3 实例&a…...
[x86汇编语言]从实模式到保护模式第二版
下载汇编器:https://www.nasm.us/pub/nasm/releasebuilds/2.16.02rc6/win64/ mov ax, 0x3f add bx,ax add cx,ax 编译: C:\Users\HP>cd D:\BaiduNetdiskDownload\01b站\lizhong\myasm C:\Users\HP>D: D:\BaiduNetdiskDownload\01b站\lizhong…...
基本的逻辑门
前言 本篇文章介绍基本的逻辑门,然后给出C语言描述 逻辑门是在集成电路上的基本组件。简单的逻辑门可由晶体管组成。这些晶体管的组合可以使代表两种信号的高低电平在通过它们之后产生高电平或者低电平的信号。高、低电平可以分别代表逻辑上的“真”与“假”或二进…...
云原生系列3-Kubernetes
1、Kubernetes概述 k8s缩写是因为k和s之间有八个字符。k8s是基于容器技术的分布式架构方案。官网:https://kubernetes.io/zh-cn/ Google在 2014年开源了Kubernetes项目,Kubernetes是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。同样类似的…...
R-列表、矩阵、数组转化为向量
目录 一、c()函数 二、unlist()函数 一、c()函数 c():对应的英文是combine. 当你使用c()函数时,它会将输入的对象连接成一个向量。因此,无论输入是矩阵、数组还是列表,c()函数都会将它们连接成一个简单的向量。因此ÿ…...
算法通关村-番外篇排序算法
大家好我是苏麟 , 今天带来番外篇 . 冒泡排序 BubbleSort 最基本的排序算法,最常用的排序算法 . 我们以关键字序列{26,53,48,11,13,48,32,15}看一下排序过程: 动画演示 : 代码如下 : (基础版) class Solution {public int[] sortArray(int[] nums) {for(int i …...
三种方式简单搭建http本地文件服务
有时候想写一个简单的html文件,然后加上一些image、js、css文件用于测试。希望有一个简单的http服务,总结了如下三种方式,欢迎讨论更多高效的方式。 (一)使用Web Server for Chrome浏览器扩展 之前写过一篇博文&#x…...
设计模式--适配器模式
实验8:适配器模式 本次实验属于模仿型实验,通过本次实验学生将掌握以下内容: 1、理解适配器模式的动机,掌握该模式的结构; 2、能够利用适配器模式解决实际问题。 [实验任务]:双向适配器 实现一个双向…...
Node.js教程-express框架
概述 Express是基于Node.js平台(建立在Node.js内置的http模块上),快速、开放、极简的Web开发框架。 中文官网 http://www.expressjs.com.cn/。 Github地址:https://github.com/orgs/expressjs。 Express核心特性: 可设置中间件来响应 HTTP…...
location.origin兼容
if (!window.location.origin) {window.location.origin window.location.protocol "//" window.location.hostname (window.location.port ? : window.location.port: );}...
spring boot集成mybatis和springsecurity实现权限控制功能
上一篇已经实现了登录认证功能,这一篇继续实现权限控制功能,文中代码只贴出来和上一篇不一样的修改的地方,完整代码可结合上一篇一起整理spring boot集成mybatis和springsecurity实现登录认证功能-CSDN博客 数据库建表 权限控制的意思就是根…...
按键修饰符
在键盘监听事件时,我们经常需要判断详细的按键,此时,可以为键盘相关的事件添加按键修饰符,例如: 键盘修饰符案例:...
新版IDEA中Git的使用(一)
说明:本文介绍如何在新版IDEA中使用Git 创建项目 首先,在GitLab里面创建一个项目(git_demo),克隆到桌面上。 然后在IDEA中创建一个项目,项目路径放在这个Git文件夹里面。 Git界面 当前分支&Commit …...
【性能测试】真实企业,性能测试流程总结分析(一)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 性能测试什么时候…...
20231224解决outcommit_id.xml1 parser error Document is empty的问题
20231224解决outcommit_id.xml1 parser error Document is empty的问题 2023/12/24 18:13 在开发RK3399的Android10的时候,出现:rootrootrootroot-X99-Turbo:~/3TB/Rockchip_Android10.0_SDK_Release$ make installclean PLATFORM_VERSION_CODENAMEREL…...
电子电器架构刷写方案——General Flash Bootloader
电子电器架构刷写方案——General Flash Bootloader 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 注:文章1万字左右,深度思考者入!!! 老规矩,分享一段喜欢的文字,避免…...
【Linux】僵尸与孤儿 进程等待
目录 一,僵尸进程 1,僵尸进程 2,僵尸进程的危害 二,孤儿进程 1,孤儿进程 三,进程等待 1,进程等待的必要性 2,wait 方法 3,waitpid 方法 4,回收小结…...
Java小案例-Sentinel的实现原理
前言 Sentinel是阿里开源的一款面向分布式、多语言异构化服务架构的流量治理组件。 主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 核心概念 要想理解一个新的技…...
Qwen2.5-VL应用指南:如何用它做智能客服、文档分析和内容创作
Qwen2.5-VL应用指南:如何用它做智能客服、文档分析和内容创作 1. 引言:认识Qwen2.5-VL的强大能力 Qwen2.5-VL是通义千问团队推出的最新视觉-语言多模态模型,相比前代产品有了显著提升。这个7B参数的模型不仅能理解图像内容,还能…...
洛雪音乐音源项目:免费高品质音乐资源获取的终极方案
洛雪音乐音源项目:免费高品质音乐资源获取的终极方案 【免费下载链接】lxmusic- lxmusic(洛雪音乐)全网最新最全音源 项目地址: https://gitcode.com/gh_mirrors/lx/lxmusic- 1 价值定位:重新定义音乐资源获取体验 洛雪音乐音源项目作为一款开源…...
pvr.iptvsimple技术解构:IPTV直播系统构建的底层逻辑与实践指南
pvr.iptvsimple技术解构:IPTV直播系统构建的底层逻辑与实践指南 【免费下载链接】pvr.iptvsimple IPTV Simple client for Kodi PVR 项目地址: https://gitcode.com/gh_mirrors/pv/pvr.iptvsimple 问题定位:IPTV直播系统的技术痛点与架构挑战 IP…...
AI推动SEO关键词优化的全新策略与实践明晰
在当前数字营销环境中,AI技术为SEO关键词优化带来了前所未有的变革。它通过自动化的数据分析与挖掘工具,能够帮助企业更准确地识别用户需求与搜索趋势。通过AI的支持,关键词挖掘变得更加高效和精准,企业可以快速获取相关关键词并优…...
大麦智能抢票系统:告别手速极限的终极解决方案
大麦智能抢票系统:告别手速极限的终极解决方案 【免费下载链接】ticket-purchase 大麦自动抢票,支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase 还在为抢不到热门演唱会门票而烦恼吗&…...
THE LEATHER ARCHIVE快速体验:一键生成杂志级AI皮衣大片,小白也能当设计师
THE LEATHER ARCHIVE快速体验:一键生成杂志级AI皮衣大片,小白也能当设计师 1. 项目介绍与核心价值 想象一下,你不需要专业的设计技能,就能创造出媲美时尚杂志封面的皮衣设计作品。THE LEATHER ARCHIVE正是这样一个让创意触手可及…...
新手福音:用快马AI生成带详解注释的Arduino交通灯实验代码
作为一个刚接触单片机的新手,第一次看到Arduino开发板时既兴奋又迷茫。那些闪烁的LED灯和蜂鸣器背后到底藏着什么秘密?今天我就用InsCode(快马)平台来探索一个有趣的交通灯模拟项目,整个过程比想象中简单多了。 项目构思 我想做一个能模拟真实…...
小觅相机‘凉了’之后,我们如何用它的SDK和开源工具链构建自己的SLAM数据集?
从废弃硬件到研究利器:小觅相机SDK与开源工具链的SLAM数据集构建指南 当一款硬件产品的厂商突然消失,官网关闭、技术支持中断,那些被遗弃的设备往往会被贴上"电子垃圾"的标签。但作为一名SLAM研究者或爱好者,你是否想过…...
数据驱动决策的基石:Awesome Public Datasets实用探索手册
数据驱动决策的基石:Awesome Public Datasets实用探索手册 【免费下载链接】awesome-public-datasets A topic-centric list of HQ open datasets. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-public-datasets 在数据驱动决策日益成为商业竞…...
Android tinyalsa深度解析之pcm_params_get_periods_min调用流程与实战(一百七十三)
简介: CSDN博客专家、《Android系统多媒体进阶实战》作者 博主新书推荐:《Android系统多媒体进阶实战》🚀 Android Audio工程师专栏地址: Audio工程师进阶系列【原创干货持续更新中……】🚀 Android多媒体专栏地址&a…...
