架构模式 | 模式描述 | 适用场景 |
---|---|---|
分层模式(Layered pattern) | 用于可分解为子任务的结构化程序,每个子任务都位于特定的抽象层级,每一层都为上一层提供服务。 | 桌面应用程序;电子商务web应用程序;移动App; |
客户端-服务器模式(Client-server pattern) | 服务器将向多个客户端提供服务。客户端从服务器请求服务,服务器向这些客户端提供相关服务。 | 电子邮件、文档共享和银行等在线应用程序;基于IPC的应用程序; |
主从模式(Master-slave pattern) | 主节点将工作分配给相同的从节点,并根据从节点返回的结果计算最终结果。 | 数据库主从复制;进程内多线程调度; |
管道-过滤器模式(Pipe-filter pattern) | 用于构造生成和处理数据流的系统。每个处理步骤都包含一个过滤器组件。要处理的数据通过管道传递。这些管道可用于缓冲或同步目的。 | 编译器; |
代理模式(Broker pattern) | 通过解耦组件来构造分布式系统。 | 消息中间件;网络传输中的代理软件; |
点对点模式(Peer-to-peer pattern) | 每个组件都称为对等节点。对等节点既可以作为客户机(从其他对等节点请求服务),也可以作为服务器(向其他对等节点提供服务)。 | 文件共享网络;多媒体协议; |
事件-总线模式(Event-bus pattern) | 订阅发布模式,事件源将消息发布到事件总线上的特定通道,监听者订阅特定的通道。 | 通知服务;注册中心; |
模型-视图-控制器模式(Model-view-controller pattern) | MVC模式,解耦组件并允许有效的代码重用 | web应用程序架构;GUI 应用程序; |
黑板模式(Blackboard pattern) | 对于没有确定解决方案策略的问题非常有用,所有的组件都可以到达黑板。组件可以生成添加到黑板上的新数据对象。组件在黑板上查找特定类型的数据,并通过与现有的知识源进行模式匹配找到这些数据。 | 语音识别;车辆识别及追踪; |
解释器模式(Interpreter pattern) | 用于设计一个解释专用语言编写的程序组件。 | 数据库查询语言,如SQL用于描述通信协议的语言 |
设计模式按照目的来分类有:创建、结构、行为三种,按照作用范围来分类有:类模式和对象模式两种。
- 创建型模式:用于创建对象,就是将对象的创建与使用分离。从而降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
- 描述如何将类,对象,接口之间按某种布局组成更大的结构。
- 行为型模式:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
23种设计模式如下:
类型 | 模式名称 | 模式描述 |
---|---|---|
创建型 | 单例模式(Singleton) | 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。 |
工厂方法模式(Factory Method) | 定义一个用于创建产品的接口,由子类决定生产什么产品。 | |
抽象工厂模式(AbstractFactory) | 提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。 | |
建造者模式(Builder) | 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。 | |
原型模式(Prototype) | 将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。 | |
结构型 | 适配器模式(Adapter) | 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 |
桥接模式(Bridge) | 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。 | |
组合模式(Composite) | 将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。 | |
装饰模式(Decorator) | 动态的给对象增加一些职责,即增加其额外的功能。 | |
外观模式(Facade) | 为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。 | |
亨元模式(Flyweight) | 运用共享技术来有效地支持大量细粒度对象的复用。 | |
代理模式(Proxy) | 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。 | |
行为型 | 模板方法模式(TemplateMethod) | 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 |
策略模式(Strategy) | 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。 | |
命令模式(Command) | 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。 | |
职责链模式(Chain of Responsibility) | 把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。 | |
状态模式(State) | 允许一个对象在其内部状态发生改变时改变其行为能力。 | |
观察者模式(Observer) | 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。 | |
中介者模式(Mediator) | 定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。 | |
迭代器模式(Iterator) | 提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 | |
访问者模式(Visitor) | 在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。 | |
备忘录模式(Memento) | 在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。 | |
解释器模式(Interpreter) | 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。 |
应用实践指南
以下总结了各种设计模式对应的适用场景:
模式名称 | 适用场景 |
---|---|
单例模式(Singleton) | 无状态类使用单例模式可以节省内存资源 |
工厂方法模式(Factory Method) | 在不知道具体实现细节的情况下创建对象的场景 |
抽象工厂模式(AbstractFactory) | 客户端与对象创建解耦,需要创建多个不同类型的对象的场景 |
建造者模式(Builder) | 生成复杂对象的场景 |
原型模式(Prototype) | 快速创建大量同类对象的场景 |
适配器模式(Adapter) | 让两个不兼容的类一起工作的场景 |
桥接模式(Bridge) | 将一个类的抽象部分和实现部分独立改变的场景 |
组合模式(Composite) | 表示树形结构的场景 |
装饰模式(Decorator) | 动态地为对象添加新职责的场景 |
外观模式(Facade) | 为一个复杂的子系统提供一个简单的接口的场景 |
亨元模式(Flyweight) | 在多个地方共享大量细粒度对象的场景 |
代理模式(Proxy) | 在访问某个对象时增加额外控制的场景 |
模板方法模(TemplateMethod) | 在不改变算法结构的情况下重定义算法中的某些步骤的场景 |
策略模式(Strategy) | 在不同情况下使用不同算法的场景 |
命令模式(Command) | 支持命令的撤销和恢复、延迟调用或日志操作的场景 |
职责链模式(Chain of Responsibility) | 在不明确指定接收者的情况下,向多个对象中提交一个请求的场景 |
状态模式(State) | 根据对象的状态来改变它的行为的场景。 |
观察者模式(Observer) | 在对象之间松散耦合的场景 |
中介者模式(Mediator) | 在多个对象之间松散耦合的场景 |
迭代器模式(Iterator) | 为容器对象提供多种遍历方式的场景 |
访问者模式(Visitor) | 在不改变各元素的类的前提下定义对这些元素的新操作的场景 |
备忘录模式(Memento) | 历史回放或者回滚等场景 |
解释器模式(Interpreter) | 定义一个语言并为该语言实现一个解释器的场景 |
接下来以实际案例为大家展现一下设计模式在实际场景的应用。案例包含了创建型,结构型,行为型各种模式类型里常用的设计模式,比如:
- 用工厂模式隔离业务实现;
- 用策略模式消解业务流程分支;
- 用模板方法模式提取业务分支公共流程;
- 用建造者模式简化入参对象的构建难度;
- 用代理模式横向扩展通用能力(日志,异常处理);
- 用职责链模式对请求进行敏感词,防刷校验;
- 用命令模式让指令拥有了记忆;
中国有个古谚语:“一个和尚挑水吃,两个和尚抬水吃,三个和尚等水吃。” 我们就通过程序来模拟出家人的寺庙生活。
工厂模式
首先,这三个人是如何成为和尚的呢?
一号和尚(贫困潦倒型),出生在一个大山里头,父母怕他孤单,给他生了5个弟弟,在他9岁那年,恰巧家里闹了饥荒,为了吃上饭,进了寺庙,出了家;
二号和尚(走投无路型),出生在一个湖泊旁边,因为生性耿直,18岁那年,走在街头,路见不平,三拳打死街上恶霸,为了赎罪,受了戒,坠入空门;
三号和尚(天选之子型),从小敏而好学,性情温厚,对佛学产生浓厚兴趣,13岁那年,为了继承和光大佛法,断了尘缘,皈依佛门。
N号和尚,......
每一个和尚的来历都不尽相同,但在当下喝不上水,这件事情上,都显得不重要。重要的是,只要凑足三个和尚,就会没水喝。那么寺庙如招收和尚?这里就可以用到工厂模式的思想。
// 贫困潦倒产生的和尚过程:1.大山里;2.闹饥荒;3.要吃饭;
一号和尚 = HeShangFactoty.getOneHeshang("贫困潦倒型");
// 走投无路产生的和尚过程:1.生性耿直;2.打死恶霸;3.要赎罪;
二号和尚 = HeShangFactoty.getOneHeshang("走投无路型");
// 天选之子产生的和尚过程:1.敏而好学;2.佛学感兴趣;3.要广大佛法;
三号和尚 = HeShangFactoty.getOneHeshang("天选之子型");
以上示例想体现的是工厂模式能将复杂的对象创建和使用进行了分离设计。下面就以和尚吃水这件事情,用程序的方式详细展现工厂模式的实现思路。按照和尚的人数,分别有挑,抬,等三种实现方式。以下为基础代码实现:
public interface Waterable {
Water getWater();
}
public class TiaoShui implements Waterable{
public Water getWater(){
System.out.println("先到山下去!");
return "水是挑上来的!";
}
}
public class TaiShui implements Waterable{
public Water getWater(){
System.out.println("先到山下去!");
return "水是抬上来的!";
}
}
public class DengShui implements Waterable{
public Water getWater(){
System.out.println("就坐在原地!");
return "水是等不来的!";
}
}
具体使用
public class Factory {
/**
* 按照和尚数量生成取水对象
*
* @param heShangNum 和尚数量
* @return
*/
public static Waterable getWaterable(Integer heShangNum) {
switch (heShangNum) {
case 1:
return new TiaoShui();
case 2:
return new TaiShui();
case 3:
return new DengShui();
default:
throw new RuntimeException("庙小,装不下那么多和尚!");
}
}
}
策略模式
按照不同的条件(人数),分别有几种获取水的方法:挑,抬,等。可以通过策略模式来实现,前面的实现方式其实就是策略模式和工厂模式的结合。我们再看一下策略模式的具体使用方式如下:
/**
* 通过入参和尚人数,就可以动态改变Waterable.getWater()的取水模式
* @param heShangNum
* @return
*/
public void getWater(Integer heShangNum) {
Waterable waterable = Factory.getWaterable(heShangNum);
Water water = waterable.getWater();// 取水
}
1.输入参数1:挑水模式的实现(对应Tiaoshui实现类);
2.输入参数2:抬水模式的实现(对应Taishui实现类);
3.输入参数3:等不到水模式的实现(对应Dengshui实现类);
通过和尚人数,就可以动态获得对应的取水实现,即所谓的通过策略实现业务,对于使用方来说(主流程),无需关注取水的具体实现(解耦:业务流程稳定性的设计体现),新增取水方式时,只需要新增一个类实现就可以了,存量的实现和主流程都不会受到影响。
模板方法
我们细化取水过程,取水过程一般需要三步:
1.拿起工具(扁担或者木棍);
2.到寺庙南面的小河边(步行);
3.装满水带回寺庙(挑水,抬水,等水);
我们可以将取水流程步骤进行模板化。
public interface Waterable {
Water getWater();
}
public abstract class AbstractWaterable implements Waterable {
@Override
public Water getWater() {
takeTool();
toRiver();
return moveWater();
}
/** * 拿起工具 */
protected abstract String takeTool();
/** * 到河边去 */
protected String toRiver() {
System.out.println("走过去!");
return "步行";
}
/**
* 将水带回来
* * @return
*/
protected abstract Water moveWater();
}
个性化场景实现
public class TiaoShui extends AbstractWaterable {
@Override
protected String takeTool() {
return "扁担";
}
@Override
protected Water moveWater() {
return "挑水";
}
}
public class Taishui extends AbstractWaterable{
@Override
protected String takeTool() {
return "木棍";
}
@Override
protected Water moveWater() {
return "抬水";
}
}
public class DengShui extends AbstractWaterable{
@Override
protected String takeTool() {
return "意念";
}
@Override
protected String toRiver() {
return "一动不动";
}
@Override
protected Water moveWater() {
return "无水";
}
}
具体使用
/**
* 和尚取水:实现一个和尚挑水喝,两个和尚抬水喝,三个和尚等水喝
*/
public void fetchWater(){
//
for (int heShangNum = 1; heShangNum < 4; heShangNum++) {
Waterable waterable = Factory.getWaterable(heShangNum);
Water water = waterable.getWater();
}
}
模板方法讲的是流程标准定义和能力复用。示例中,定义了取水的三个阶段,选择工具,出行方式,搬运方式。单看出行方式中,【挑水】和【抬水】复用了模板方法里的通用实现,【等水】则个性化的重写了出行方式。
建造者模式
我们取水需要一些工具,按照取水方式(挑,抬,等)可以分为扁担+木桶,木棍+木桶,意念(什么也不需要)等装备的组合方式。如何定义getWater(ToolBox toolBox)的入参ToolBox,使其能够按照对应取水方式匹配正确的装备组合呢?这里就可以使用建造者模式。
public class ToolBox {
private final String bianDan;
private final String muTong;
private final String muGun;
private ToolBox(TiaoBuilder builder){
this.bianDan=builder.bianDan;
this.muTong=builder.muTong;
this.muGun = null;
}
private ToolBox(TaiBuilder builder){
this.bianDan = null;
this.muTong = null;
this.muGun=builder.muGun;
}
private ToolBox(DengBuilder builder){
this.bianDan = null;
this.muTong = null;
this.muGun=null;
}
public static class TiaoBuilder{
private String bianDan;
private String muTong;
public TiaoBuilder setBianDan(String bianDan) {
this.bianDan = bianDan;
return this;
}
public TiaoBuilder setMuTong(String muTong) {
this.muTong = muTong;
return this;
}
public ToolBox build(){
return new ToolBox(this);
}
}
public static class TaiBuilder{
private String muGun;
private String muTong;
public TaiBuilder setMuGun(String muGun) {
this.muGun = muGun;
return this;
}
public TaiBuilder setMuTong(String muTong) {
this.muTong = muTong;
return this;
}
public ToolBox build(){
return new ToolBox(this);
}
}
public static class DengBuilder{
public ToolBox build(){
return new ToolBox(this);
}
}
//省略getter方法
}
具体使用
ToolBox oneHeShangToolBox = new ToolBox.TiaoBuilder().setMuTong("小号木桶").setBianDan("小号扁担").build();
ToolBox twoHeShangToolBox = new ToolBox.TaiBuilder().setMuTong("大号木桶").setMuGun("长号木棍").build();
ToolBox threeHeShangToolBox = new ToolBox.DengBuilder().build();
建造者模式属于创建型设计模式,它可以将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
代理模式
为了鼓励多劳多得,庙里取水开始采用积分机制,每取回来一桶水就要敲一下木鱼,打一次卡。这里就可以采用代理模式。代理分为静态代理和动态代理,为了简单起见,这里就用静态代理来举例。
public class WaterableProxy implements Waterable{
/**
* 被代理的原始对象
*/
private Waterable waterable;
public WaterableProxy(Waterable waterable) {
this.waterable = waterable;
}
@Override
public Water getWater() {
Water water = waterable.getWater();
// 增强的新功能,不管是挑水,抬水,等水,只有带回来水,就可以
if(water != "无水"){
System.out.println("我敲一下木鱼,打一次卡!");
}
return water;
}
}
具体使用
public void doProxy(){
Waterable waterable = new Taishui();
WaterableProxy proxyWaterable = new WaterableProxy(waterable);
proxyWaterable.getWater();
}
代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(通过代理访问真实对象)
责任链模式
为了升级取水工具,将小木桶升级大金桶,寺庙决定对外提供烧香拜佛,诵经礼佛等增值服务。为了安全起见,寺庙引进了安检机制,流程是这样的:
- 禁止携带宠物;
- 衣着穿戴整齐;
- 其它业障,最终解释权归寺庙;
public interface SecureFilter {
void filter(Map context);
}
public class PetSecure implements SecureFilter{
@Override
public void filter(Map context) {
if(context.containsKey("宠物")){
throw new RuntimeException("请出去:禁止携带宠物进入!");
}
}
}
public class WearSecure implements SecureFilter{
@Override
public void filter(Map context) {
if(context.containsKey("光膀子")){
throw new RuntimeException("请出去:有伤风化者!");
}
}
}
public class OtherSecure implements SecureFilter{
@Override
public void filter(Map context) {
if(context.containsKey("大声喧哗")){
throw new RuntimeException("请出去:佛门乃清净之地!");
}
}
}
具体使用
/**
* 安检责任链实现
*/
class SecureChain implements SecureFilter{
// 注入PetSecure,WearSecure,OtherSecure等过滤器
private List<SecureFilter> secureFilterList;
/**
* 进入寺庙,进行安检逻辑
* @param context
*/
@Override
public void filter(Map context) {
// 进行安检流程
for (SecureFilter secureFilter : secureFilterList) {
secureFilter.filter(context);
}
System.out.println("佛祖保佑,安检通过!");
}
}
流程示意图
责任链模式一般和过滤器模式组合一起使用,即创建一个链条,经过这个链条处理的所有对象和数据分别进行依次加工,每个环节负责处理不同的业务,环节间彼此独立解耦,同时可以复用。这种设计的巧妙之处在于可以链式调用,不同的过滤方式可以灵活的排序和组合。既可以使用单个过滤器进行处理,也可以直接添加一条责任链。
命令模式
寺庙里的和尚除了打水工作之外,还有很多工作要做,所有的工作安排都是按照主持的指令来执行的,比如某日清晨的工作安排如下:
- 一号和尚做早餐;
- 二号和尚扫庭院;
- 三号和尚敲古钟;
结构定义
public class Command implements Serializable {
// 做早餐,打扫,敲钟等指令标识
private OrderTypeEnum order;
// 正向执行OR逆向回滚
private Integer direction;
// 省略get和set方法
}
// 指令动作执行器,每种指令对应一个实现
public interface OrderHandler {
/**
* 执行逻辑
*
* @param callContext
* @return
*/
PipeResult execute(CallContext callContext);
/**
* 支持的命令类型:做早餐,打扫,敲钟等命令标识
*
* @return
*/
OrderTypeEnum getOrderType();
}
// 指令类型管理器
public interface PipelineCmd {
/**
* 指令行定义
*
* @return
*/
Command getCommand();
/**
* 执行逻辑
*
* @param pipeContext
* @return
*/
PipeResult execute(PipeContext pipeContext);
/**
* 如果可以撤消指令,则此方法应返回true ,否则返回false
*
* @return
*/
default boolean isReversible() {
return true;
}
}
// 指令执行器管理器
public interface CmdHandler {
/**
* 业务执行
*
* @param callContext
* @return
*/
PipeResult execute(CallContext callContext);
/**
* 业务回滚(只回滚当前指令)
*
* @param callContext
* @return
*/
PipeResult rollback(CallContext callContext);
/**
* 全部回滚
*
* @param pipeContext
* @return
*/
PipeResult rollbackAll(PipeContext pipeContext);
}
命令实现
public class ZhuChiCmd implements PipelineCmd {
private Command command;
private transient OrderHandler orderHandler;
public StepCmd(Command command, OrderHandler orderHandler) {
this.command = command;
this.orderHandler= orderHandler;
}
@Override
public PipeResult execute(PipeContext pipeContext) {
return orderHandler.execute(new CallContext(command, pipeContext));
}
// 省略get和set方法
}
public class Breakfast implements OrderHandler {
/**
* 执行逻辑
*
* @param callContext
* @return
*/
PipeResult execute(CallContext callContext){
System.out.println("做早餐啦!");
}
/**
* 支持的指令类型:做早餐,打扫,敲钟等指令标识
*
* @return
*/
OrderTypeEnum getOrderType(){
return OrderTypeEnum.BREAKFAST;
}
}
public class Clean implements OrderHandler {
/**
* 执行逻辑
*
* @param callContext
* @return
*/
PipeResult execute(CallContext callContext){
System.out.println("打扫庭院啦!");
}
/**
* 支持的指令类型:做早餐,打扫,敲钟等命令标识
*
* @return
*/
OrderTypeEnum getOrderType(){
return OrderTypeEnum.CLEAN;
}
}
public class Ring implements OrderHandler {
/**
* 执行逻辑
*
* @param callContext
* @return
*/
PipeResult execute(CallContext callContext){
System.out.println("敲钟啦!");
}
/**
* 支持的命令类型:做早餐,打扫,敲钟等指令标识
*
* @return
*/
OrderTypeEnum getOrderType(){
return OrderTypeEnum.Ring;
}
}
public class CmdFactory {
private List<OrderHandler> orderHandlerList;
/**
* 获取指定指令条件的指令对象
*
* @param command
* @return
*/
public PipelineCmd getPipelineCmd(Command command) {
for (OrderHandler orderHandler : orderHandlerList) {
OrderTypeEnum orderTypeEnum = orderHandler.getOrderType();
if (orderTypeEnum.equals(command.getOrder())) {
return new ZhuChiCmd(command, orderHandler);
}
}
throw new RuntimeException("对不起主持:没有多余的和尚来执行新命令了!");
}
/**
* 获取给定指令的回滚操作指令对象
*
* @param command
* @return
*/
public PipelineCmd getRollbackPipelineCmd(Command command) {
Command rollbackCommand = getRollbackCommand(command);
return getPipelineCmd(rollbackCommand);
}
}
具体使用
public class CmdHandlerImpl implements CmdHandler {
private CmdFactory cmdFactory;
@Override
public PipeResult execute(CallContext callContext) {
PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(callContext.getCommand());
PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
return pipeResult;
}
@Override
public PipeResult rollback(CallContext callContext) {
Command rollbackCommand = cmdFactory.getRollbackCommand(callContext.getCommand());
if (rollbackCommand == null) {
return new PipeResult("不需要回滚");
}
PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(rollbackCommand);
if (!pipelineCmd.isReversible()) {
return new PipeResult("不支持回滚");
}
PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
return pipeResult;
}
@Override
public PipeResult rollbackAll(PipeContext pipeContext) {
// 命令执行备忘录模式对象,这里不再展开
Caretaker<Command> caretaker = pipeContext.getCaretaker();
// 拿到上一步执行命令,依次循环回滚
Command command = caretaker.pop();
while (command != null) {
PipelineCmd pipelineCmd = cmdFactory.getRollbackPipelineCmd(command);
if (pipelineCmd != null) {
pipelineCmd.execute(pipeContext);
}
command = caretaker.pop();
}
return new PipeResult();
}
}
命令模式将一个请求封装为一个对象,使发出的请求的对象和执行请求的对象分割开。这两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。命令模式可以与备忘录模式组合使用,方便实现Undo和Redo操作。
网友评论