架构师日记-深入理解软件设计模式



一、设计模式与编程语言


1.1 什么是设计模式
设计模式(Design pattern):由软件开发人员在软件开发中面临常见问题的解决方案,是经过长时间的试验积累总结出来的,它使设计更加灵活和优雅,复用性更好。从实用的角度来看,它代表了某一类问题的最佳实践。
设计模式到底解决了开发过程中的哪些难题呢,它又是如何来解决的呢?
其核心是 复用和解耦。 使不稳定依赖于稳定、具体依赖于抽象,以此增强软件设计适应变化的能力。

1.2 什么是编程范式

要探讨设计模式和编程语言的关系,还得从编程范式谈起。编程范式一词最早来自 Robert Floyd 在 1979 年图灵奖的颁奖演说,是程序员看待程序的观点,代表了程序设计者认为程序应该如何被构建和执行的看法,与软件建模方式和架构风格有紧密关系。
当前主流的编程范式有三种:
  1. 结构化编程(structured programming)
  2. 面向对象编程(object-oriented programming)
  3. 函数式编程(functional programming)
这几种编程范式之间的关系如下:
  1. 起初是非结构化编程,指令(goto指令)可以随便跳转,数据可以随便引用。后来有了结构化编程,人们把 goto 语句去掉了,约束了指令的方向性,过程之间是单向的,但数据却是可以全局访问的;
  2. 后来面向对象编程的时候,人们干脆将数据与其紧密耦合的方法放在一个逻辑边界内,约束了数据的作用域,靠关系来查找;
  3. 到函数式编程的时候,人们约束了数据的可变性,通过一系列函数的组合来描述数据,从源到目标映射规则的编排,中间它是无状态的;
编程范式是抽象的,编程语言是具体的。编程范式是编程语言背后的思想,要通过编程语言来体现。C 语言的主流编程范式是结构化编程,而 Java 语言的主流编程范式是面向对象编程,后来 Java8 开始支持 Lambda 表达式,将函数式编程范式的内容融合进来,同时新诞生的语言一开始就支持多范式,比如 Scala,Go 和 Rust 等。
从结构化编程到面向对象编程,再到函数式编程,抽象程度越来越高(离图灵机模型越来越远),与领域问题的距离越来越近。直观地来讲,就是解决现实问题的效率提升了,灵活性和执行效率随之有所下降。
设计模式无论用什么语言实现都是可以的,然而由于语言的各自差异化特点,不是每种语言都完美或统一实现各种设计模式。 比如Java里面有策略模式,那是因为Java8之前不支持方法传递,不能把一个方法当作参数传给别人,所以有了策略模式。 而JavaScript等语言可以直接传函数,就根本没必要造一个策略模式出来。

1.3 什么是多态特性

面向对象编程语言有三大特性:封装、继承和多态。
  1. 封装即信息隐藏或数据保护,“数据结构"通过暴露有限的访问接口,授权外部仅能通过"数据结构"提供的方法(函数)来访问其内部的数据;
  2. 继承的好处是可以实现代码复用,但不应过度使用,如果继承的层次过深就会导致代码可读性和可维护性变差。因此建议少用继承而多用组合模式;
  3. 多态可以分为变量的多态,方法的多态,类的多态。通常强调的是类的多态,多态的实现是指子类可以替换父类,在实际代码运行过程中调用子类的方法实现;
多态可以说是面向对象中最重要的一个特性,是解决项目中紧耦合的问题,提高代码的可扩展性和可复用性的核心,是很多设计模式、设计原则、编程技巧的代码实现基础。
多态比较直观的理解就是去完成某个动作,当不同的对象去完成时会产生出不同的状态,其作用范围可以是方法的参数和方法的返回类型。
多态这种特性也需要编程语言提供特殊的语法机制来实现,Java 中多态可以通过"子类继承父类+子类重写父类方法+父类引用指向子类对象"的方式实现,还可以通过"接口语法"的方式实现。C++中则使用virtual(虚函数)关键字来实现。像一些动态语言如 Python 也可以通过 duck-typing 的语法实现,另外 Go 语言中的"隐藏式接口"也算是 duck-typing。
Python 语言,实现多态示例如下:
class MyFile:    def write(self):        print('I write a message into file.')        class MyDB:    def write(self):        print('I write data into db. ')        def doIt(writer):    writer.write()
def demo(): myFile= MyFile() myDB = MyDB() doIt(myFile) doIt(myDB )


二、设计模式与架构模式

2.1 了解架构模式

对给定上下文的软件架构中常见问题的一种通用的可复用的解决方案,可以为设计大型软件系统的各个方面提供相应的指导。它不仅显示了软件需求和软件结构之间的对应关系,而且指定了整个软件系统的组织和拓扑结构,提供了一些设计决策的基本原理,常见的架构设计模式如下:

2.2 了解设计模式

在1995年,有四位编程界的前辈合著了一本书,书名叫做《Design Patterns: Elements of Reusable Object-Oriented Software》,翻译过来就是《设计模式:可复用面向对象软件的基础》,书里面总共收录了23种设计模式。这本书是软件研发领域重要的里程碑,合著此书的四位作者,被业内称为GoF(Gang of Four),因此这本书也被人称为GoF设计模式。
设计模式按照目的来分类有: 创建、结构、行为三种,按照作用范围来分类有: 类模式和对象模式两种。
  1. 创建型模式:用于创建对象,就是将对象的创建与使用分离。从而降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
  2. 结构型模式 :描述如何将类,对象,接口之间按某种布局组成更大的结构。
  3. 行为型模式 :用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
23种设计模式如下:

2.3 小结
  • 架构模式更像是宏观战略层面的设计,设计模式则更像是战略目标拆解出来的具体任务的实现方案;
  • 软件架构是软件的一种搭建形式,往往规定了软件的模块组成,通信接口(含通信数据结构),组件模型,集成框架等,往往规定了具体的细节;
  • 设计模式是一种软件的实现方法,是一种抽象的方法论,是为了更好地实现软件而归纳出来的有效方法;
  • 实现一种软件架构,不同组成部分可能用到不同的设计模式,某个部分也可能可以采用不同的设计模式来实现;


三、应用实践指南


3.1 适用场景

不使用设计模式也能实现业务诉求,系统也能够正常运行,为什么要使用设计模式呢?
是的,相当一部分场景是不需要进行设计模式的引入的,比如:业务逻辑简单,业务演进方向不明朗,或者就是一个不需要经常迭代的功能点。但当我们遇到了复杂问题设计的时候,就需要借助前人的经验了,而设计模式就是前人为我们沉淀总结的各种常见问题的解决方案。
那么多种设计模式,难道我需要全部系统地学习实现一遍,都要闭着眼睛就能写出来吗?其实不用,这就跟排序算法一样,我们只需要记住每种算法的适用范围和场景就可以了,在有需要的时候,再去深入研究就可以了。以下总结了各种设计模式对应的适用场景:

3.2 场景案例

为了让读者对设计模式有个更加直观立体的感知,接下来以实际案例为大家展现一下设计模式在实际场景的应用。案例包含了创建型,结构型,行为型各种模式类型里常用的设计模式,比如:
  • 用工厂模式隔离业务实现;
  • 用策略模式消解业务流程分支;
  • 用模板方法模式提取业务分支公共流程;
  • 用建造者模式简化入参对象的构建难度;
  • 用代理模式横向扩展通用能力(日志,异常处理);
  • 用职责链模式对请求进行敏感词,防刷校验;
  • 用命令模式让指令拥有了记忆;
中国有个古谚语:“一个和尚挑水吃,两个和尚擡水吃,三个和尚等水吃。” 我们就通过程序来模拟出家人的寺庙生活。

工厂模式

首先,这三个人是如何成为和尚的呢?
一号和尚(贫困潦倒型),出生在一个大山里头,父母怕他孤单,给他生了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("佛祖保佑,安检通过!");    }}
流程示意图
责任链模式一般和过滤器模式组合一起使用,即创建一个链条,经过这个链条处理的所有对象和数据分别进行依次加工,每个环节负责处理不同的业务,环节间彼此独立解耦,同时可以复用。 这种设计的巧妙之处在于可以链式调用,不同的过滤方式可以灵活的排序和组合。 既可以使用单个过滤器进行处理,也可以直接添加一条责任链。

命令模式

寺庙里的和尚除了打水工作之外,还有很多工作要做,所有的工作安排都是按照主持的指令来执行的,比如某日清晨的工作安排如下:
  1. 一号和尚做早餐;
  2. 二号和尚扫庭院;
  3. 三号和尚敲古钟;
结构定义
  
  
  
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操作。

3.3 实践心得

设计原则

具体包含单一职责原则SRP、开闭原则OCP、里氏替换原则LSP、依赖倒置原则DIP、接口隔离原则ISP、最少知识原则LKP等很多种,其核心还是围绕着低耦合,高复用,高内聚,易扩展,易维护展开的。

模式与原则

  1. 设计原则是指导思想,设计模式是实现手段之一;
  2. 设计原则在实际开发中并不能做到完全遵守,往往是打破一些原则,遵守一些原则,来实现设计的合理性;(成本,性能)
  3. 设计模式往往是问题解决方案的骨架,有时候可以当做开发规范和任务拆分执行落地的技术手段;
  4. 个设计模式,往往不仅仅采用一种设计原则,而是一些设计原则的整合;
  5. 设计模式不是一成不变的,可以根据问题场景,输出新的模式;
  6. 一个复杂场景问题,有时候需要多种设计模式的组合;
  7. 学设计模式,死记硬背是没用的,要从实践中习得;
  8. 避免设计过度,使简单的问题复杂化。一定要牢记简洁原则,设计模式是为了使设计简单,而不是更复杂;


四、总结


本文从设计模式与编程语言的关系,设计模式与架构模式的区别,设计原则和设计模式的关系等几个维度进行了分析和解答。 关于设计模式应该如何学习和应用的问题,给出了学习意见和实践心得。 当然,为了让设计模式更加的直观和立体,也花了大量篇幅在应用实践案例上面,主要是通过场景化的案例,以设计模式的方式给出解决方案,其中部分场景为了方便理解,将问题做了简化处理,但这不影响我们去理解设计模式要解决的问题类型。 冰冻三尺非一日之寒,滴水石穿非一日之功,希望本文能够为你带来帮助。

-end-


本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章