FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

apm70plus/design-patterns: 白话设计模式 · GitHub

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

白话设计模式


设计模式都说重要,可是多少人跟我一样,翻开四人帮的经典之作,却总是觉得晦涩难懂,勉强看几篇就难以为继了。多年以后重读设计模式,尝试结合自己的经验,以通俗的白话唠唠设计模式。废话到此,言归正传。

设计模式的六大指导原则

设计模式前人总结了6大指导原则,什么里氏代换、迪米特大法。。。虽然是对前人贡献的一种纪念,但是对我这种记忆力不好的人,要难记住这些名字真是好难,就算勉强记住了,过段时间还是会自问:“这些人名背后到底是啥子吗?” 所以还是换种方式来聊6大原则吧。

  1. 开闭原则 —— 对扩展开放、对修改关闭
    最核心,也是最抽象的指导原则。就像太极的两仪,所有其它原则都在开闭原则之内。 软件开发,除了BUG,还有什么事情是一定会发生的?追加需求需求变更!所以,好的设计,要容易扩展新功能,同时尽可能的少修改已有的代码。
    可是有多少码场小白有过这样的经历,费好大劲各种修修补补才把新功能加上了,惴惴不安的发布到测试环境,还没喘口气,就听有人气冲冲大吼:“谁改的代码!之前测试好好的XX功能,怎么不好用啦?” 这就是不符合开闭原则的设计,新加个功能,发现影响一大片,费半天劲,新功能没做好,以前的功能还改坏了。 把原来的功能改坏了!怎么破?不修改!好的设计,还要尽可能少的去修改原来的代码,没有改自然没有错。
    而符合开闭原则的设计,对扩展新功能很友好,往往无需修改旧的代码,没有改自然没有错;而对于功能变更,不得不改时,应该尽早识别出容易变更的点,把它关进小黑屋(封装起来),让其独立发展,降低修改波及的范围。 开闭原则是软件设计的大方向,后面将要讲到的各种设计技巧或模式,都是为了指导你向这个大方向迈进。所有符合开闭原则的设计模式,都是好模式,不局限于23种模式。

  2. 面向基类编程 —— 在程序中尽量使用基类类型进行定义,而在运行时再确定其子类
    在被调用的地方,用父类代换子类进行定义,可以降低耦合度。修改子类的功能不影响其它兄弟类,可以说是做到了对修改关闭,未来可以通过实现新的子类来扩展新功能,而这些扩展在代码中对调用方来说是透明无感知的,做到了对扩展开放。

  3. 面向接口编程 —— 针对接口编程,依赖于抽象而不依赖于具体
    和面向基类或抽象类编程同样的道理,只是更彻底了。抽象类内部还允许有数据和方法,这与所有子类是高度耦合的。当某一天父类定义的行为与需求冲突了,其变更将直接影响所有子类,其后果不可预测,这就不利于功能扩展。而接口是绝对的抽象,只声明其功用性,具体行为由实现类决定,这对扩展更友好,不用瞻前顾后。

  4. 接口虽好,也要做好隔离 —— 使用多个隔离的接口,比使用单个接口要好
    面向接口编程既然那么好,是不是已经完美了。NO!一个接口里面声明的功能越多,越不利于扩展。接口定义应该遵循单一职责的原则,声明的功能要尽可能的内聚,单一,不太相干的功能,应该定义新的接口来进行隔离。

  5. 接口虽好,最好还是不调用或少调用 —— 实体应当尽量少地与其他实体间发生相互作用,使得模块相对独立
    面向接口编程,有那么多好处,又容易扩展,又容易更改,但是如果能不调用接口,或者少调用接口,那就更好了。我跟你没关系,你爱咋滴咋滴!
    足够的内聚,必然导致极低的耦合。越少的依赖别人,抗风险能力就越强。别人是在不断变化的,过多的依赖别人,它们的变化就会不停影响你。如果你依赖的某个功能不好用了,你还能正常提供服务吗?

  6. 尽量使用组合/聚合的方式,而不是使用继承
    前面已经谈过,继承是高度耦合的关系。父子关系,你想想吧,那得多亲的关系啊!耦合度能低的了吗。父类的功能,直接体现在子类身上;父类的变化直接体现在子类上面,如果一个大家族好几代的子孙类扩散到了程序世界的各个角落,各具特色的发挥着自己的作用,突然最顶层的类修改了一个功能,这个变化瞬间传播到所有的子孙后代,天知道接下来会发生什么呢?
    所以,继承虽然很强大,却被有经验的码农们很谨慎的使用着。继承是把双刃剑,即便要用,也要继承链尽可能的短,限制在可控的范围内。

分门别类

总体来说,设计模式分为3大类总共23种

  1. 创建型模式,共5种 单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式

  2. 结构型模式,共7种 适配器模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式

  3. 行为型模式,共11种 策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

后面会逐步针对每种模式一一讲解。其实设计模式单看类图,发现很多模式有非常大的相似性,因为类图的表达能力非常有限,想要真正理解各个模式之间的区别,还是要从应用的场景入手,每个模式都是针对一种典型的场景整理出来的解决方案,从场景带入加深理解,遇到典型场景套用模式才是正确的打开方式。

单例模式

适用场景

  • 类只能有一个实例,且提供一个全局访问点
  • 这个唯一实例可以通过子类扩展,并且这个扩展是用户无感知的

工厂方法模式

适用场景

  • 工厂接口声明了创建一类对象的能力,具体创建什么样的对象,可以推迟到运行时由子类实现。对客户隐藏了动态构建不同对象的行为(封装可变性)

举例

  • Java中的线程池ThreadPoolExecutor,实例化时传入ThreadFactory的实现,从而允许用户创建自定义行为的线程。给线程池提供了一个生产不同类型线程的扩展点,应对个性化的线程定制需求非常方便,不是吗?

抽象工厂模式

适用场景

  • 相对于工厂方法声明创建一类对象的能力,抽象工厂通过接口声明了创建一系列相关或相互依赖对象的能力。由于产品直接关联紧密,抽象工厂模式可以显著减少工厂类的数量,是工厂方法在多产品线场景下的一个变种

建造者模式

适用场景

  • 一个复杂对象,通过提供一种引导性方式完成构建过程,定制对象的不同表示

举例

  • StringBuilder方便你构建复杂的字符串
  • 在各种框架中到处可见的XxxBuilder类,都是建造者模式的应用

原型模式

适用场景

  • 原型模式是支持运行时动态定制化对象模版的特殊工厂。客户可以运行时定制一系列个性化的对象模型,然后通过不同模型克隆出不同的对象

举例

  • 定制个性化监控配置,并应用到不同的服务集群,简化配置过程这样的场景

适配器模式

适用场景

  • 复用已有代码时,某个接口与现有接口不兼容,此时可以采用适配器模式,在不修改旧代码的基础上,让复用的接口适配现在的接口

桥接模式

适用场景

  • 有的时候,引起一个对象变化的因素有多个,这个时候就要从依赖具体实现提升为依赖抽象。来完成对象和变化因素之间的低耦合。变化因素的抽象接口成了对象本身和变化因素之间的桥梁,两边都可以独立的去演进

举例

  • jdbc就是典型的桥接模式。一边是很多数据库厂商各自维护自己的jdbc实现,另一边DataSource在框架层也衍生出了很多不同的实现,两边都专注于自己的领域进化

装饰器模式

适用场景

  • 如果你想在运行时动态扩展一个对象的功能,这时可以采用装饰器模式。更多体现的是一种运行时动态扩展的场景

举例

  • SpringMVC对Servlet HttpRequest的各种包装,就是装饰器的典型场景

代理模式

适用场景

  • 如果你想控制对某个对象的访问,可以采用代理模式。虽然和装饰器的实现方式很类似,但是其应用场景却有很大区别。代理模式侧重的是控制对象访问,这往往在服务启动后就已经完成对具体对象访问代理,限定并控制在服务运行期间只允许通过代理访问该对象。

举例

  • Spring AOP就是代理模式的典型应用。spring容器启动之后,就已经完成了对象的代理。

门面模式

适用场景

  • 存在多个子系统交互的时候,需要降低系统耦合性,降低系统间交互复杂度,严格控制子系统对外暴露的行为,或者区别不同用户访问系统的限制。如果有这样的诉求,可以采用门面模式

举例

  • 阿里的Duboo微服务框架,就是采用门面模式来完成子系统之间的交互

组合模式

适用场景

  • 存在“整体-部分”的层次结构,并且用户对单个对象和组合对象的使用具有一致性的场景,采用组合模式

享元模式

适用场景

。。。未完待续

About

白话设计模式

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors


Back | FazBrowse Home | New Git URL