设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
1、开闭原则
软件实体应当对扩展开放,对修改关闭
理解:类、模块、函数,可以去扩展,但不要去修改。如果要修改代码,尽量用继承或组合的方式来扩展类的功能,而不是直接修改类的代码。当然,如果能保证对整个架构不会产生任何影响,那就没必要搞的那么复杂,直接改这个类吧。
开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
2、里式替换原则
1 | 继承必须确保超类所拥有的性质在子类中仍然成立 |
理解:父类可被子类替换,但反之不一定成立。也就是说,代码中可以将父类全部替换为子类,程序不会出现异常,但反过来就不一定了。
总结:
根据上述理解,对里氏替换原则的定义可以总结如下:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
子类中可以增加自己特有的方法
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
3、单一职责原则
1 | 一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分.这里的职责是指类变化的原因 |
理解:不同的类具备不同的职责,各司其职。做系统设计是,如果发现有一个类拥有了两种职责,那么就要问一个问题:可以将这个类分成两个类吗?如果真的有必要,那就分开,千万不要让一个类干的事情太多。
总结:一个类只承担一个职责
4、迪米特法则
1 | 只与你的直接朋友交谈,不跟“陌生人”说话 |
理解:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。总结:一定要做到:低耦合、高内聚。
A认识B,B认识C,A想完成的事情只有C能完成,但是迪米特原则则要求类A中不能出现C的身影。
5、接口隔离原则
1 | 一个类对另一个类的依赖应该建立在最小的接口上 |
理解:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
总结:不要对外暴露没有实际意义的接口。
6、依赖倒置原则
1 | 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。也就是要面向接口编程,不要面向实现编程。 |
理解:高层模块不应该依赖于底层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。应该面向接口编程,不该面向实现类编程。面向实现类编程相当于就事论事,那是正向依赖;面向接口编程,相当于透过现象看本质,抓住事务的共性,那就是反向依赖,即依赖倒置。
总结:面向接口编程,提取出事务的本质和共性。
7、合成复用原则
1 | 它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。 |
对象行为模式:
1、模板方法
这个比较容易理解,有时候我们完成一件事情可能有一些流程A、B、C,分析流程得特点:1、流程执行得顺序。2、流程在每次执行是否可变。 所以基于这种流程有种模式:模板方法,即指定固定流程并且分离出可变的流程给具体子类实现
2、策略模式
这个也比较容易理解:有时候我们需要根据不同得条件调用不同得方法,这时候会产生大量的if…else…缺点是显而易见得如果方法是很长得逻辑并且有大量的if,那么这个类很可能会很臃肿,怎么解决呢策略方法是个很好的方案。最简单得理解策略模式思想是把,方法装在一个map里,map<条件,Method>然后使用得时候就很简单了,只要有条件map.get(条件)得到方法后执行就可以了。注意:一个条件只能对应一个方法,也就是说不能在多个类出现同一条件,也就是说条件a可以调用eat(),a就不能调用sleep()方法
还是很好得,例如别人可能有n种方式来调用我的方法,那么我要对n种方式进行if-else,太sb了,运用此模式可以使程序更优雅
所以这里面涉及了几个类。
抽象策略Strategy:这里应该有个条件列表和抽象方法。
具体策略Concrete Strategy:抽象策略得实现
环境类Context:也就是装map得类
策略类:
具体策略
环境
使用比较简单了:创建环境类和具体策略,把具体策略注册进环境,环境调用choose即可。
1 | InspectionSolver solver = chooser.choose(taskType); |
3、命令模式
这个理解也蛮简单得如果说“方法的请求者”与“方法的实现者”之间存在紧密的耦合关系,我们应该怎么解耦。
策略模式含义是我给你条件,你给我相应得方法,我去调用。
命令模式更复杂一点在于,我先指定好有哪些命令,放到一个环境里面,环境有调用不同命令的方法。命令内部会自动创建接收者,并且在环境调用命令的时候,通知接收者做一些事情。这样就类似于我操作电视,不需要我直接操作电视,我可以通过命令环境遥控器来操作电视。
简单地说命令模式比策略模式做的更多,比如命令模式调用命令后,命令会去通知receiver,receiver做相应的事情.策略仅仅相当于有命令,得到命令解决方案,并不知道通知谁去做什么或者说由解决方案自己定义后面的逻辑。
所以这里面涉及了几个类。
环境Context(也称为调用者、请求者 Invoker):存在一些具体命令,负责调用命令对象执行请求command.execute()
命令: 声明一个给所有具体命令类的抽象接口。execute()
具体命令:定义一个接收者和行为之间的弱耦合;实现execute()
方法,负责调用接收者的相应操作。execute()
方法通常叫做执行方法。
接收者角色(Receiver):负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
1 | //调用者 |
4、责任链模式
一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但是像请假那么,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度,如果用责任链模式都能很好解决。
所以说责任链模式,处于链上的对象如果处理不了请求那么就应该交给下一个对象来处理。由此可以知道当前对象应该知道下一个对象信息。
涉及的角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
1 | package chainOfResponsibility; |
5、观察者模式
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。例如某件事发生了我应该去通知所有关注这件事的人,然后大家根据自己的情况做出相应的反应。
观察者模式是一种对象行为型模式,其主要优点如下。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
涉及的角色:
分析:事情发生后通知所有关注这件事的人,然后大家根据自己的情况处理。所以这里涉及:人(观察者)根据情况有不同的反应所以这里有抽象观察者和具体观察者。然后事情发生通知所有人所以这里应该有个环境装人(观察者)并且可以通知所有人。这里把环境称为主题。由于事情发生后通知观察者,那么是什么事情发生通知观察者呢?不同的事情发生通知肯定也不一样所以这里存在抽象环境和具体环境。
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
1 | // 这个例子比较简单只是说明我填加观察者后可以通知到所有人,jdk里实现了两个方法setChanged()和clearChanged()用于标志事情发生了,然后通知其他人 |
1 | // 环境 |
6、中介者模式
这个也比较简单简单来说就是把大家都需要的信息收集起来,供给大家一起使用,ex:,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。再或者开发过程种如果没有在线文档的话大家一人修改文档的话需要告诉所有人,如果有线上协作的话就很好处理了,一人修改文档会自动通知所有人,无需我们个人去操作。
生活种例子也很多,租房中介,人才交流中心等等。
其实这种就是把“网状结构”改为“星形结构”的话,所有信息交给“星形结构”中心取管理。
分析:首先中介机构可能有很多所以这里应该有抽象中介和具体中介,再者应该有关注中介的人(colleague),每个人都应该知道中介信息,并且应该有互相与中介对话的功能。
中介者模式包含以下主要角色。
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
1 | package mediator; |
7、迭代器模式
这个也比较容易理解:首先我们清楚的是聚合对象有很多类有什么队列、链表、map、图、tree等等,然后每种有自己遍历方式,当然我们可以让具体聚合类创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码(例如可能发现更好得遍历图的算法),这违背了 “开闭原则”。能不能把遍历方法交给用户自己实现呢?首先这种方式是很蠢得,用户使用你个聚合类还要编写遍历方法。。。另外这样做无疑暴露了聚合类内部表示,使其数据安全性降低。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
迭代器模式主要包含以下角色。
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
1 | package iterator; |
8、访问者模式(多A对多B的情况下使用)
在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同。所以说不同角色对同一物品的看法很可能不同。
分析:西湖新十景可能对于不同游客感受是不一样的,也就是说不同游客对同一景点观察点不同。这里人会对新十景逐一观赏,并且有自己的看法。所以这里面有这种角色抽象访问者Visitor(人对于每个景色访问的抽象),具体访问者,具体人的感受。新十景,每个景色可能接受多个人观赏(这里并非说明这个景色包含多个人,是允许多个人观赏),所以每个景色有可能被不同人观赏,所以这里有抽象景色(抽象元素)和具体景色。最后还应该包含新十景的数据结构
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
1 | package visitor; |
访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。
(1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如【例1】中的对象结构是用 List 实现的,它通过 List 对象的 Itemtor() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。
(2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,其结构图如图 4 所示。
9、备忘录模式
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
分析:小明王者荣耀有个改名卡可以改名字,同时改完名字后三分钟可以撤销操作进行恢复之前的名字。小明可以恢复之前的名字所以有个角色用来保存之前的名字(备忘录Memento),有了备份之后,可能我们会备份多个,所以得有个管理备份得角色(管理者Caretaker),允许多个备份。小明要修改名字所以称小明为发起人(Originator)。
备忘录模式的主要角色如下。
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
1 | package memento; |
参考
1 | http://c.biancheng.net/design_pattern/ |
- 本文作者: 初心
- 本文链接: http://funzzz.fun/2020/12/30/设计模式---6大法则以及行为模式/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!