前面三篇我们通过从一些零散的例子,和简单应用来模糊的感受了下设计模式在编程中的智慧,从现在开始正式进入设计模式介绍,本篇将从设计模式的7大原则、设计模式的三大类型、与23种设计模式的进行总结,和描述具体意义。
设计模式体系结构图
七大原则
开闭原则(OCP,Open Close Principle )
官方解释:对扩展开放软件实体应当对修改关闭(Software entities should be open for extension,but closed for modification)
白话解读:合成复用原则、里氏替换原则相辅相成,都是开闭原则的具体实现规范
就是有新的业务的时候,尽可能扩展展新类而不是修改旧类,实际开发的时候大家基本上都会这么做,这样对之前的业务影响比较小,所以很多原则设计模式就贯穿在日常开发中。
里氏替换原则(LSP,Liskov Substitution Principle)
👀官方解释:继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that anyproperty proved about supertypeobjects also holds for subtype objects)
是不是完全看不明白,俺也一样。
大白话:其实就是当我们需要开发功能的时候尽量通过继承父类,扩展父类功能的;而不是直接在原本的父类上修改。
依赖倒置原则(DIP,Dependence Inversion Principle)
官方定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该以来抽象(High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions )
大白话:面向接口编程,而不是面向类编程:
例如:Controller里注入的是service接口而不是ServiceImpl,这样Controller就不用关心servcieImpl到底依赖的那些dao,同理可以推广的ServiceImpl依赖的是dao接口而不用关心具体实现。
单一原则(SRP,Single Responsibility Principle)
官方定义:一个类应该有且仅有一个引起他变化的原因,否则这个类应该被拆分(There should never be more than one reason for a class to change)
大白话:每个类只负责自己领域类的事情,而不是一篮子事情:当然要注意不是一个类只有一个方法。
例如:FileService可以处理文件上传下载方法,但是注册登陆等方法就不该放入该类中
接口隔离原则(ISP,Interface Segregation Principle)
官方定义:一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another on should depend on the smallest possible interface)
大白话:类应该建立自己专用的接口,而不是建立一个万能接口。
其实这个可以配合上面两个看,如果建立的万能接口,同时又要满足上面依赖倒置原则,那必定实现不了单一原则。
合成复用原则(CRP,Composite Reuse Principle)
由叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)
软件复用时,要尽量先使用组合或者聚合等关联关系实现,其次才考虑使用继承关系实现。
大白话:优先是用组合而不是继承,这个是通用原则了,主要还是继承扩展起来没那么方便。
迪米特法则(LOD,Law of Demeter)
低耦合,高内聚。
最少知识原则(Least Knowledge Principle,.LKP)
只与你的直接朋友交谈,不跟"陌生人"说话 (TaIk only to your immediate friends and not to strangers)
其实就是引入一个中间者来进行交互Controller层不直接和Dao层直接交互
设计模式与组件生命周期关系图
以Java为例,通常我们会以结构型设计模式去构建类的类信息,通过创建型设计模式去创建对象,通过行为模式去控制对象的行为。
23种设计模式概述
前面三篇文章,基本上都多少涉及到了一点,这里咱们做个简单归纳的归纳总结,后面咱们咱们会一个一个的拆分。
结构型设计模式
外观模式(Facade)
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
例子:在一个电商系统中,处理订单可能涉及多个子系统,如支付系统、库存系统、物流系统等。外观模式可以提供一个统一的接口来处理订单,从而简化客户端的调用。
适配器模式(Adapter)
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例子 :MyBatis中的ResultSetHandler
将JDBC的结果集转换为自定义的对象类型。这个过程就是适配器模式的应用,将不兼容的接口进行适配。
桥接模式(Bridge)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
例子:Spring的JdbcTemplate和各种具体的数据库实现之间的分离。JdbcTemplate提供抽象操作,而具体的数据库操作由不同的DataSource实现,二者可以独立变化。
组合模式(Composite)
将对象组合成树形结构以表示"部分-整体"的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
例子:在一个文件系统中,文件和文件夹都可以视为节点。文件夹可以包含文件和其他文件夹,这形成了一个树形结构。组合模式使得文件和文件夹的操作一致。这个例子可能有过文件处理系统的小伙伴可能才好理解,再举一个列子:一个管理系统他的菜单树结构的,子与父结构相同这样是不是就好理解了一级菜单二级菜单嘛,无限套娃就行。
装饰模式(Decorator)
动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类更为灵活。
例子:在一个图形编辑器中,可以对图形对象添加不同的装饰(如边框、阴影)。装饰模式可以动态地添加这些装饰,而不需要改变图形对象的类。
享元模式 (Flyweight)
运用共享技术有效地支持大量细粒度的对象。 例子:在一个文字处理系统中,每个字符都是一个对象。如果每个字符都单独存储,会占用大量内存。享元模式通过共享相同的字符对象,减少内存的使用。
代理模式(Proxy)
其他对象提供一种代理以控制对这个对象的访问。
例子:在一个远程服务调用系统中,客户端通过代理对象来访问远程服务。代理对象处理与远程服务的通信和数据传输,客户端只需调用代理对象的接口。SpringCloud种的Open Feign就是这个思想呢,当然代理我们一般分为静态代理和动态代理,但是现在很多时候静态代理和装饰器思想很接近,所以很多时候代理模式指的是动态代理,而java实现动态代理由离不开jdk或cglib,详细讨论的时候这一块将是重点。同样提到这里咱们就不得再提一下Spring种的Aop咯。
Spring的AOP也是代理模式的一个典型应用。通过创建代理对象,Spring AOP可以在方法调用前后添加额外的逻辑,如事务管理、权限检查等。
MyBatis也使用代理模式生成Mapper接口的实现类,调用实际的SQL操作。这个在框架中使用特别多,后面一定要好好说说。
创建型设计模式
创建模式主要的关注点是怎样将对象创建出来,将对象创建与使用进行剥离。像Spring框架就是依赖创建型设计模式,将我们开发者从对象的创建给剥离出来,对象的创建全部交给Spring托管,咱们安心写自己业务代码(方法的调用即可)。
单例模式(Singleton Pattern)
单例模式,在系统里,你要判断一下,如果有一些类,只需要一个实例对象就可以了,那就给那个类做成单例的模式(关键点就是构造方法私有化)Spring中的Bean对象默认都是单例的,后面在介绍具体的设计模式的时候在详细看代码。
简单工厂模式
工厂模式的核心思想,其实就是不要自己在代码里手动new一个实现类对象出来,因为那样的话,调用方就不是面向接口编程了,你还得自己去实现了。所以一般都是用工厂的思想来提供所有实现类实例,然后调用方面向接口来编程即可,接口不变,调用方代码不用变。
现在基本上很少去自己实现工厂模式了,因为有Spring框架,Spring的容器其实本质上就是个大工厂的概念,将你需要的对象都放到Spring容器里,需要的时候问Spirng取就完事。
工厂方法模式(Factory Method)
模板方法模式+简单工厂模式,简单工厂模式
抽象工厂模式(Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。在一个工厂中聚合多个同类产品的创建方法。简单理解为"工厂的工厂即可"。
建造者模式(Builder)
构造一个复杂的对象,很多的属性,有些属性构造的时候需要做一些校验,格式转换;
举个例子:当我们销售系统收到一笔订单的时候,在构建订单的时候需要对订单金额基础的校验、对产品的代码、产品类型做一些转换、供应商等。
原型模式(Prototype)
原型模式,以某一个对象为原型,然后对这个对象进行拷贝,得到拷贝后的另外一个对象
例子:很多BeanUtils之类的都有类似的功能,后面我们在分析具体的原理和对比他们的性能等。
行为型设计模式
责任链模式(Chain Of Responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。
在Spring框架中,过滤器链的实现就体现了责任链模式的应用。每个过滤器负责处理请求的一部分逻辑,然后将请求传递给链中的下一个过滤器。这样就形成了一条过滤器链,所有的过滤器按顺序依次执行。
策略模式(Strategy)
可以将一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化。
例子:在一个支付系统中,支持多种支付方式(如支付宝、微信、信用卡、PayPal、比特币等)。每种支付方式是一个策略,用户可以在支付时选择不同的支付方式。
模版方法(Template Method)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
例子:在一个文档处理系统中,可以有不同类型的文档(如Word文档、PDF文档等)的导出。导出的步骤大致相同:打开文档、读取内容、写入目标格式、关闭文档。具体的读取和写入步骤可以由子类实现。
命令模式(Command)
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
例子:在一个智能家居系统中,可以有各种命令(如小爱同学打开灯、关闭灯、调节温度等)。每个命令可以封装为一个对象,用户可以对命令进行操作,如执行、撤销等。
其实对于前后端分离项目,用户前端每次执行一个操作,对应后端的每个请求都可以看作是一个命令的执行,我们提供给前端的接口其实就是一个个封装的对象。
观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
例子:在一个股票交易系统中,投资者可以订阅股票价格的变化。当股票价格发生变化时,系统会通知所有订阅了该股票的投资者。
这个在Spring中的监听机制,其实就是可以看作是一个观察者模式,当模式泛化以后可以将消息中间件中的主题中的一组生产者一组消费者看作观察者模式。
访问者(Visitor)
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
例子:在一个编译器中,可以有不同的操作(如语法检查、代码优化、代码生成等)作用于语法树的各个节点。每个操作可以通过访问者模式实现。
状态模式(State)
允许对象在内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
例子:在一个订单处理系统中,订单可以有不同的状态(如新建、已支付、已发货、已完成等)。每种状态下订单的行为可能不同,比如在已支付状态下可以发货,但在新建状态下不可以。
这个状态模式和策略模式很想,不同的是状态模式有一个自动流转的状态机通过状态的不同来实现不同的算法。
解释器模式(Interpreter)
给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
例子:在一个数学表达式计算器中,可以定义表达式的文法,并使用解释器模式来解析和计算表达式的值。
这玩意不知道咋解释,实际项目种使用的比较少,之前在科技部门做个一个通用的消息触达平台,定义了一些简单的占位符,通过占位符规则替换一些数据,应该类似解释器。
泛化到语言层面如Java 语言的Javac命令可以将Java源代码解析成Class文件,抑或现在比较火的Transformer框架中的编码器与解码器,将各种结构化数据解析成一组组向量后在来处理相似性问题,这个似乎更满足解释器模式的概念。这个模式先放一放,如果有小伙伴有比较深入的研究欢迎私信一起交流。
迭代器模式(Iteration)
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
例子:在一个集合框架中,可以有各种集合(如数组、列表、集合等)。迭代器模式提供了一个统一的接口来遍历这些集合中的元素。
这种Java已经有很成熟的API了,咱们就不用自己去实现了,设计模式一定不能为了设计而设计,一定是为了解决实际问题。
中介者模式(Mediator)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
例子:在一个聊天系统中,用户之间的消息传递通过一个中介者对象(如聊天服务器)进行,用户不需要直接知道其他用户的存在。
备忘录模式(Memento)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复对象到先前的状态。
例子:在一个文本编辑器中,可以有撤销和恢复功能。编辑器可以在执行某个操作前保存当前状态,以便用户需要时可以恢复到该状态。
其实Java中的序列化与反序列化可以看作是这个思想的扩展,需要保留的时候序列化的文件中,
设计模式带来的一些思考:
1、为什么学习设计模式
其实回答这个问题的时候,很多培训机构的老师或者网上资料都说设计模式是源码的基础,只有学会了设计模式才能看懂源码之类的观点。
这里的观点可能不太一样,设计模式也好抑或其他框架也罢,都是为了我们解决实际问题而设计出来的,有了设计模式的思想很多时候会给我们软件设计带来一些启发,这种场景是否可以有xxx模式的思想来解决呢?
其次任何一个成熟的项目都不可能由一个人来完成,设计模式思想的运用可以在团队合作的时候来规范团队。举个例子:在参与基金销售系统开发的时候,他的交易可以各种业务如:基金的认购、申购、赎回、修改分红方式等,每一种业务都有对应的处理逻辑,但是他们都需要和基金管理公司交互而和基金公司交互都是通过文件的形式。这样他们共同点可以抽象出来,那就是文件的处理可以放到模板方法统一实现,文件解析后的数据,再有个各子域去实现。这样子域可以由不同的人员来开发,代码风格更加统一好维护。
总结
在软件开发中,设计模式不仅仅是技术上的工具,更是一种思维方式和解决问题的哲学。它们通过丰富的实践和理论积累,帮助开发者在面对复杂需求和变化时保持清晰的思路和高效的开发策略。无论是单例模式的实例唯一性,还是策略模式的算法灵活性,设计模式都在实际项目中展现出其独特的价值。通过不断学习和应用设计模式,我们能够更好地理解和运用面向对象设计的原则和技巧,从而打造出更加健壮和可维护的软件系统。设计模式的智慧不仅体现在代码中,更融入到整个软件开发的文化与实践中,成为提升团队能力和项目质量的重要支柱,同样切记不能为了设计而设计,目前各种语言与框架已经很成熟,使用这些设计模式的时候一定要采取审慎原则。拿不定主意的时候多和公司资深前辈沟通。