-
设计原则
-
开闭原则
- 一个软件实体应当对扩展开放,对修改闭合
-
里氏代换原则
- 任何能使用父类的地方,一定能使用子类
-
依赖倒转原则
- 要依赖于抽象,不要依赖于实现
- 抽象不应该依赖于细节,细节应该依赖于抽象
-
合成聚合复用原则
- 尽量使用合成聚合而不是继承去实现复用
-
迪米特法则
- 一个软件实体应该尽可能少的和其他实体发生相互作用
-
接口隔离原则
- 应该为客户提供尽可能小的单独的接口,而不应该提供大的综合性的接口
-
创建模式
- 工厂模式
- 单例模式
- 建造模式
- 原型模式
-
结构模式
- 门面模式
- 装饰模式
- 合成模式
- 代理模式
- 适配器模式
- 桥梁模式
- 共享元类模式
-
行为模式
-
1.策略模式
-
概念
-
内容
- 指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法
-
解析
- 基于设计原则
- 分离变化的部分和不变的部分
- 不变的地方是这个行为,变化的地方是行为所需要的算法(策略)
-
应用场景
-
举例
- java君、Pascal君、Scala君三个人要去餐厅吃饭,可是呢,他们三个的口味不一样,java君想吃面向对象大餐,Pascal君想吃面向过程大餐,而Scala君想吃函数式大餐,这时候假设你是餐厅服务员,你给每个人点餐的时候,你就这样写了
- 以上做法是比较糟糕的,如果再来一个spring先生想要吃aop大餐,就要去改源码了
- 不符合“对修改关闭,对扩展开放”的原则
- 子主题 4
- 解决之道
- 分离变化和不变的东西
- 变化就是对不同的人要上不同的菜,从代码中抽离,进行分离。
- 利用多态,给每一个类人一个点某中菜的能力。
- 我们可以让所有人实现一个Order接口,这个接口中定义了点菜的这个方法,让每个人都去实现这个方法,然后在点菜的时候直接去调用每个人的order方法。
- 这样在新来人的话,我们不用去修改原来的代码,只需要让新人实现自己的order方法,这样就符合设计原则了
- 代码实现
- 如此,无论来汇编先生,还是python女士,都只需要实现Order接口就行了
- 使代码更具有扩展性,降低了耦合性
-
框架
- Struts
- 你url一大堆,然后由mapping映射获取到指定的action
- springmvc
-
jdk官方排序算法
- 排序的时候,由于不同的需要排序的类的排序规则不一样,也就是策略不一样,
- 所以我们把策略封装到客户部分,也就是具体需要排序的类中,也就是让具体需要排序的类实现Comparable接口,
- 然后在传入到排序的算法中的时候,排序的算法在比较两个元素的先后顺序的时候直接调用其compareTo方法,
- 根据多态,他调用的其实是具体传入的compareTo方法。也就是封装到子类中的具体的比较策略,这样就能实现了具体的排序
-
提炼总结
- 我们需要对若干个都可以实现我们所要的功能中选择一个最合适的
-
策略模式包含的角色
- Context
- 上下文类
- 上下文中有一个策略属性,还有一个设置策略的方法,一个工作方法
- 工作方法中实现的就是策略的工作方法
- Strategy
- 抽象策略类
- ConcreteStrategy
- 具体策略类
- 关系
- 具体的策略A和具体的策略B实现了策略接口,实现了work方法
- 这样在调用上下文的work方法,就是调用了实际设置的具体策略的work方法
- 从而实现了将执行策略与具体的策略解耦
- 有新的策略的时候,只需要再重新声明一个策略类就可以了
-
策略模式是一种非常简单的设计模式,策略模式是对不同算法的封装,把算法的责任与算法本身分离开来,将算法委派给不同的对象进行管理
- 实现一族算法,并将每一个算法都封装起来,使其相互交换
-
策略模式中,使用哪一种策略由用户来决定,提高了系统的灵活性,但在一定程度上,也增加了客户端的使用难度
- 因为客户端需要了解不同算之间的区别,选择合适的算法
-
设计原则
- 针对接口编程,而不是针对实现编程
- 在策略模式中,我们用一个接口代表行为,根据多态,上下文在工作时执行的行为,是这个接口的具体实现
-
JDK官方的排序方法
- Collections.sort (List<T> ,list)
- 传入的比较器是null
- 元素T也实现了Comparator接口
- Collections.sort(List<T> list ,Comparator<? super T> c )
- 传入了具体的比较器
- 概要
- 实现都是直接调用了List的sort方法
-
关系图
-
2.模板模式
-
概念
-
内容
- 模板方法模式定义了一个算法的步骤,并允许子类别为一个或者多个步骤提供其实践方式。在子类别在不改变算法架构的情况下,重新定义算法中的某些步骤(维基百科)
-
理解
- 在这个模式中,算法的步骤是确定的。(对第一句的理解)
相当于父类中有算法步骤,但是把具体的算法步骤延迟到子类中去实现。(对第二句的理解)
如此:就实现了算法步骤和算法步骤的具体实现分离。
如果再有一份新的算法的实现,就再写一个子类就可以了。
- 既然是步骤,就是有顺序
-
应用场景
- 若干个类中有步骤相同的逻辑,但是步骤的实现方法不同。
复杂的算法,可以把核心的算法设计为模板方法,具体相关的细节由子类去实现。
-
举例
- Pascal君和Java君点完餐之后,想要吃饭。
他们的吃饭步骤如下:
准备餐具,吃饭,收拾餐具。
但是pascal君和java君准备的餐具不同,吃饭的方式当然也不一样,并且pascal君不喜欢收拾餐具。而java君每次吃饭后都要自己收拾餐具。
- 活用场景
-
解决之道
- 分析异同:
同:吃饭步骤三步。
异:三个步骤的具体实现不同
将相同的步骤抽离出来,就属于模板方法。
如此:我们将其封装到父类中,这个父类可以是一个抽象类,然后让子类去继承父类,然后子类去具体实现具体的被模板调用的方法,这个就把变化的部分和不变的部分分离开了。
- 代码实现
- 注意点
- 一般将父类的模板方法加上final关键字,防止被恶意覆盖。
- 父类的抽象方法一般用protected修饰符修饰,让其对子类和同包下的类可见
-
Servlet
- servlet中对一个请求进行具体的业务逻辑处理。
是调用了HttpServlet中的service方法,然后service方法根据不同的请求分发到具体的方法中,而具体的业务逻辑则由用户去进程HttpServlet类去实现
-
总结
-
模板方法模式一般分为两类
- 模板类
- 定义了算法的实现步骤
- 抽象的具体步骤的实现
- 具体实现类
- 子类去继承父类,然后去具体实现步骤
-
模板方法模式封装了不变的部分,扩展了可变得部分。如果scala君也想要吃饭,只需要去继承父类,然后去实现具体的步骤即可,不需要修改已经存在的代码。
- 符合开闭原则
-
当父类有N多步骤,而子类需要实现其中一两步时。
- 问题
- 模板方法无法对子类进行约束
- 解决方案
- 钩子方法
- 在父类中增加is***的方法,然后自己具体实现,返回true或者false表明是否需要调用这个方法。
- 在父类的模板方法中先判断是否可以执行,然后在执行步骤,这些is***方法就是钩子方法
- 顾名思义:钩子可以钩取这些基本方法,控制其是否执行。
- 活用场景
- 但是还有一个问题 就是小黄姐姐孩子上学快迟到了 刚洗完的头发还没有来的及吹就出门了 送完孩子之后 她想再去和帅哥约会 就想着去理发店把头发吹干 这个时候 剪和洗都不需要了 那么怎么解决这个问题呢
- 模板方法模式给出了钩子方法 就是在理发的抽象方法中加上一个is****()的抽象方法 具体再子类中实现 进而判断哪些步骤是否需要执行 就解决了小黄姐姐只需要吹干头发的问题了
-
3.命令模式
-
概念
-
内容
- 命令模式将请求封装成一个对象,从而使用不同的请求对客户端进行参数化,使用求情队列,或者记录请求日志,并且支持撤销操作。
-
解析
- 从定义上来看,他是把命令封装成了对象,命令就是指令,是上级发给下级的,既然有命令,就会有上下级的层次关系,命令是下发给下层人员去执行的。
所以命令模式中有决策者和执行者。
- 所谓的使用请求将客户端参数化,就是将执行者的命令参数化,也就是将命令封装成了对象。
- 至于队列和记录日志,就是在基础的命令模式上的扩展
-
应用场景
-
我们经常要向某些对象发请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
- 例如:使用遥控控制电视机
- 我们使用遥控器的时候,做的只是按下某个按钮,然而这个按钮按下之后,遥控器发送的是什么样的红外线信号,我们不得而知,信号传到电视中之后被谁获取了我们也不知道。获取之后有什么样的操作,我们更不得而知。
- 我们只知道,我们按下这个按钮,电视做出了一些变化,也就是说,我们发出某个指令,获得了一些反馈信息
-
举例
- java君比较勤奋,而且有收拾盘子的好习惯。所以他被餐厅老板奖励,可以让java君去定做一些菜品,这下java君高兴坏了。但不久之后他就发现问题了。
比如:java君想吃焗盐鸡。然后他走到餐厅后,找到采购员,然后命令她去采购新鲜的鸡和粗盐。采购回来之后,他又找到负责宰鸡的师傅,帮忙杀掉黄毛鸡,然后找到厨师师傅帮忙给抹上细盐,包上吸油纸,锡纸,埋到粗盐中。最后找到烘烤师傅,帮忙烤熟。这样他才得到一只香喷喷的盐焗鸡。
不过这样太麻烦了呀。java君只是一个食客,结果他想吃一只鸡就需要前前后后联系那么多的人。并且java君有时候还想去吃黄焖鸡,这样就要去联系其他的人了。所以,他很厌烦。
- 解决之道
- java君找到老板说,你这样让我定做虽然好,但整个制作过程都要我去联系,我也受不了啊。你就告诉我一个接头的负责人,我告诉他怎么做。
- 你们内部怎么处理的我就不管了。最后给我一个完成的菜品就好了。增加一个接头人。java君每次想吃东西的时候就告诉这个接头人。然后等他的实物就可以了。
- 代码实现
- 其 实现 就是将参数命令化,封装成了一个对象。然后传给调用者,让调用者去执行这个命令。这样一来,java君想要什么菜品,只需要实例化一个命令,把他传给调用者就行了。
- 在该例子中,java君并不知道接受者是怎么去做的,也就是Receiver角色并没有暴露给Client,但是Client却依赖了Recevier。
- 在实际应用命令模式的时候,Receiver层一般都会被封装
- 在项目中,我们一般秉承“约定优于配置”这条原则。
因为每一个命令都是对一个或者多个Receiver的封装。
所以我们可以在项目中通过有意义的类名或者命令处理命令角色和接受角色之间的耦合关系,从而减少高层模块Client类,和底层模块Receiver类的依赖关系,从而提高系统的稳定性
-
概要
- 使得请求的发送者和接收者彼此间消除耦合,让对象之间的调用更加灵活
-
总结
-
角色
- Invokeer
- 调用者接收客户(客户端)的命令,并且执行该命令
- Invoker类
- 很简单,就是接受命令,执行命令。
- Command
- 声明需要执行的命令
- 抽象命令类
- 执行做饭命令
- 具体命令类
- 执行做盐焗鸡
- 执行做黄焖鸡
- 命令模式的核心。根据具体的需求,Command也可以有多个。
所以在类图中,用了抽象Command来表示命令。
- Receiver
- 就是具体进行工作的角色
- 命令传到接收者处,接受者执行命令
- 员工类
- 一般作为抽象类
- 因为接收者可能有多个,有多个就要定义为抽象接收者。
- 类图
-
本质
- 对命令的封装,将发出命令和执行命令两个责任解耦。
每个命令都是一个操作,请求的一方发出命令,然后执行者收到命令,执行命令。
-
优点
- 使用命令模式,调用者和接受者之间没有任何依赖关系。
调用者进行调用的时候,只需要调用execute()方法即可,而不用关心具体是哪一个接受者
- Command子类容易扩展
- 在上面的栗子中,java君如果想要预定其他的菜品,只需要扩展一个子类就可以了。
- 命名模式支持撤销操作。
- 一是结合备忘录模式使其恢复到最后状态
- 只适合接受者是状态的变化,而不适合事件处理。
- 可以增加一个新的命令rollback,实现事务的回滚。
- 在每次执行命令行的时候,把执行的过程写到日志中,撤销的时候通过读取日志,执行相反的命令。
- 子主题 2
- 命令模式可和其他模式进行联合
- 和组合模式进行联合
- 可以使用宏命令,我们在现在的命令模式中执行命令的时候,只是对命令的直接执行。
- 如果使用宏命令,我们可以在执行命令的时候,传入一个具体的对象,在调用execute方法的时候,会递归调用他所包含的每个成员命令的execute方法。
这个对象可以是一个简单的命令,也可以是一个宏命令,这样就会实现对命令的批量处理。
- 和模板方法进行联合’
- 可以减少Command子类膨胀问题
-
缺点
- 在Command命令复杂的时候,会造成Command子类膨胀
-
命令模式的扩展
- 请求队列
- 就是把若干个请求封装起来,放到一个队列中。
- 将请求封装起来后,就可以让请求进行传递,这样不会影响请求的完整性
- 并且可以让命令在不同的线程中被调用
- 想象一个场景
- 有一个工作队列,我们在一段添加命令,线程可以在另一端进行不断取出命令来执行。
线程和命令之间是完全解耦的。线程不用关心每个命令是干什么用的。
他们只知道取出该命令对象,然后调用其execute()方法。
- 请求日志
- 有时候我们需要将某些应用的动作记录到日志中。在宕机后,我们可以通过日志,重新调用这些动作,恢复到之前的状态。然后继续执行接下来的动作。
- 通常我们会增加两个方法,load()和store(),在我们执行某个请求的时候,会调用store()方法记相关日志,我们可以用对象的序列化将这个命令持久化到硬盘上,死机之后,我们可以在下一次启动的时候调用load()方法载入对象,然后执行该命令对象,恢复到宕机之前的状态。
-
4.责任链模式
-
概念
-
内容
- 是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。
每一个处理对象决定它能出处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。
该模式还描述了往该处理链的末尾添加新的处理对象的方法。
-
解析
- 责任链模式包含了命令对象和处理对象,是该模式中的两个角色。
既然是责任链,那么责任链是怎么表现出来的呢?概念中提到,每一个处理对象决定它能处理的那些对象,这就是责任的体现。
我如果能处理这个对象,那么我就对这个对象负责,如果我不能处理,我就把它叫给其他的对象。如此,就形成了一条处理链。
-
应用场景
- 当一个请求可能被多个处理者进行处理时,或者我们不知道会被哪一个处理者进行处理时,就可以使用该模式。
-
想象一个场景
- java君太厉害了,以至于java君所在的公司想给他报销餐费,这个公司有个奇怪的规定,项目经理只能报销<=500的餐费,部门经理只能报销<=1000且>=500的餐费,总经理只能报销1000以上的餐费,这一次,java君在享用了自己制定的菜品之后,希望各位领导大大们给他报销,那么大家可能会这样写
- 这样大量的if-else语句嵌套,使代码的可读性大大降低了,不美观,体现不出来代码的艺术感,并且如果公司规定变化了。这个if-else结构就得跟着变化。不符合开闭原则。
- 试想
- 如果java君的公司又来了一个新的大爷,也会报销某一个区间内的餐费,这个结构就被破坏了。还需要重新写。
- 设计模式就是为了应对需求变更的。并且力求代码的可读性
-
解决之道
- 需求分析
- java君提出一个请求,然后希望得到一个答复。
并且是唯一的一个答复,不能是项目经理报销一次后,部门经理再给他报销一次。
无论是可不可以被报销,或者被谁报销,java君不会去理会,java君只在意最后的结果。
- 设计方案
- 先把java君的请求传给项目经理,能处理则处理,不能处理则交给下一个经理大爷。最后反馈给java君,这样即使又来一个经理大爷,我们直接把他安排到这个处理链的末端就可以了。
- 代码实现
- 我们定义了一个抽象的经理类,里面定义了抽象的 该经理是否可以处理该请求,并且定义了具体的处理请求的方法,先判断是否可以被当前经理处理,如果可以,就调用当前经理的处理结果,如果不能处理,就把请求交给他的下一个继任者进行处理。
- 每个具体的经理类实现自己的判断方法和处理结果
- 我们的请求类java君有一个获取当前请求的金额方法,具体设置请求 请求金额在构造方法中。
- 此处有点类似模板方法
-
总结
-
角色
- Client
- 请求的发出者,希望得到回复
- 例子中的java君
- 有一个获取请求的方法
- 有一个设定请求的方法
- 设定请求一般在构造方法中进行
- Handler
- 抽象处理者,负责核心逻辑
- 例子中的抽象经理
- 职责
- 定义一个请求的处理方法
- 定义链的编排
- 即设置下一个处理者
- 定义了抽象的由子类实现的两个方法
- 包括具体处理者的更换
- Concretehandler
- 具体实现者,实现具体的处理方法,判断是否可以处理该请求
- 例子中的具体经理
- 观察模式
- 解释器模式
- 迭代模式
- 中介模式
- 备忘录模式
- 状态模式
- 访问者模式