设计模式 - Design Patterns

一、 什么是设计模式?

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

设计模式对于面向对象编程语言的开发者来说是必备知识,但对于开发者来说设计模式又是模糊的一个技术,因为它没有严格的数学分析与证明,它只是实践检验下的经验之谈。

历史

  1. 1977/79年,建筑师 - 克里斯托佛·亚历山大(Christopher Alexander) 编制了一本汇集建筑领域设计模式 的书。当时的他一定不曾想到,这种设计模式的思想在若干年后竟对软件开发领域里产生了极其深刻的影响。

  2. 1987年,肯特·贝克沃德·坎宁安 利用了亚历山大 的设计模式思想,把它们应用在了Smalltalk 中的图形用户接口(GUI )的生成代码中。

  3. 1988年,埃里希·伽玛(Erich Gamma) 在他的苏黎世大学 博士毕业论文中开始尝试把这种思想改写为适用于软件开发。与此同时James Coplien 在1989年至1991年也在利用相同的思想致力于C++ 的开发,而后于1991年发表了他的著作Advanced C++ Programming Styles and Idioms

  4. 还是在1988年,Erich Gamma 获得博士学位后去往了美国。在那里,他结识了Richard Helm , Ralph Johnson ,John Vlissides

    设计模式 - Design Patterns_第1张图片

    他们合作出版了:《设计模式:可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software. 1995年出版,出版社:Addison Wesly Longman.Inc) 一书

    设计模式 - Design Patterns_第2张图片

    在这之后,设计模式的思想很快传播开来并深刻影响了之后的软件开发领域。这四位作者也在软件开发领域里以GoF(“四人帮”,即Gang of Four : Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)而闻名于世。有时,GoF 也会用于代指设计模式 这本书。

补充

  • 建筑师Alexander 亚历山大的原话

    埃里希·伽玛(Erich Gamma )等人在Design Patterns 书中引用的建筑师Christopher Alexander 的原话是:

    Christopher Alexander says, “Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice”.

二、为什么需要理解和使用设计模式?

可以从两个维度知道”Why ?”。

  1. 代码维度:增强代码的可重用性

    使用设计模式的目的是:增强代码的可重用性(reusable)

    这就意味着:如果你的代码只是Hello World!,即只做了一个很简单的事,你不需要也不应该引入设计模式。

    否则,当你要解决问题的规模上升到一定程度的时候,如果你的代码没有提前规划过结构,那么面对一定会出现的需求(代码)的变更 时,代码的修改、扩展和维护等问题会使你崩溃。

  2. 交流维度:与同事之间交流的“术语”

    当你使用了某种设计模式来编写代码时,你只需要说出模式的名字(如:观察者模式),大家就能明白你代码的结构,而不是还需要描述一大堆细节才能说的清楚。

    查看和使用别人的代码时,如果有“设计模式”这个共同语言,我们能很快明白它人的代码意图。

警惕妖魔化

  • 有时设计模式被妖魔化了,大家都说不要用,会让程序臃肿不堪…… 事实上我觉得最初说这种话的人,一定是已经掌握了设计模式的那些人,这只是他们在遇到某些只需要简单代码就能解决的问题时,所发出的切合实际的片段言论罢了。

    所以你一定要学习设计模式,真要说“不需要用”,记得要先吃过葡萄再说酸吧。

三、具体目标以及6个基本原则

上文说到从代码上来说设计模式的目标是代码的复用。在这里代码复用还可拆解为2个基本的目标:

  • 开闭原则:类应该能在拒绝修改原代码的基础上可以被扩展。
  • 隔离复杂和变化的部分:应该将复杂的代码、总是被修改的代码隔离到一个独立的类。

怎么才能达到我们的目标呢?这里有6个基本的手段,称为面向对象的6个基本原则:

  1. 开闭原则(Open Closed Principle,OCP)

  2. 里氏代换原则(Liskov Substitution Principle,LSP)

  3. 依赖倒转原则(Dependency Inversion Principle,DIP)

  4. 接口隔离原则(Interface Segregation Principle,ISP)

  5. 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

  6. 最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)

这其中,开闭原则有理想主义色彩因为不可能完全不修改,它是面向对象设计的终极目标,而其它几条,则可以看做是开闭原则的实现方法。

1、开闭原则(Open Closed Principle)

  1. 内容

    此原则是由Bertrand Meyer 提出的,原文是:

    Software entities should be open for extension, but closed for modification。
    软件实体们应该对括展开放,但是拒绝修改

    本原则是面向对象设计中“可复用设计”的基石,是最重要的原则,其它原则是实现开闭原则的手段。

    具体到项目中来说:就是对组件功能的扩展开放,而对修改原有代码的关闭。模块应尽量在不修改原代码的情况下进行扩展。

  2. 表现

    举一个工厂模式(即工厂方法模式 factory method pattern)的例子。假设中关村有一个卖毛片盘和盗版盘的小贩,利用开闭原则我们改如何给他设计一个“光盘销售管理软件”呢?

    首先,我们应该先设计一个光盘(Disc)接口。而毛片盘和盗版盘是其子类。利用工厂方法,小贩通过工厂类DiscFactory来管理这些光盘。代码为:

    public interface Disc {}
    
    public class MaoPianDisc implements Disc {}
    
    public class PirateDisc implements Disc {}
    
    public class DiscFactory {
        public static Disc getDisc(String name){
            if (name.equals("MaoPianDisc")) {
                return new MaoPianDisc();
            } else if (name.equals("PirateDisc")) {
                return new PirateDisc();
            }
            return null;
        }
    }

    小贩的售卖逻辑如下:

    public class Vendor {
        public static void main(String[] args){
            // 客户告诉我需要的光盘类型名,比如说是"PirateDisc"
            String type = "PirateDisc";
            Disc d = DiscFactory.getDisc(type);
            d.sell();
        }
    }

    问题来了:假如有一天,小贩开始卖正版盘(GenuineDisc)了,我们需要做哪些改动?

    1. 创建一个Disc的子类正版盘GenuineDisc

    2. 修改创建毛片的工厂方法,增加一个if语句。

    可以看到,我们并没有修改小贩的售卖逻辑,而只是扩展增加了一个类,增加了一个工厂方法的分支。大体上我们事先了开闭原则,所以可以说我们的程序是“可复用”的。

2、里氏代换原则(Liskov Substitution Principle)

  1. 内容

    里氏代换原则是由Barbara Liskov(美国第一位计算机女博士、第二位女图灵奖得主)在1987年的一次演讲中首先提出,并在1994年发表的:

    如果一个面向对象设计是遵守本原则的,那么有:任何基类可以出现的地方,子类一定可以出现

    子类必须能够替换基类,否则不应当设计其为子类”,原句可以在Wikipedia中见到

  2. 表现

    表现在代码里,如果调用的是父类的话,那么换成子类也完全可以运行。比如:

    Disc d = new PirateDisc();
    d.sell();

    此时,可以将PirateDisc类改为MaoPian类吗?没问题完全可以运行。Java编译器会检查程序是否符合里氏代换原则

    还记得java继承的一个原则吗?

    子类override的方法的访问权限不能小于父类对应方法的访问权限。

    现在知道里氏代换原则就明白了:如果子类权限小的话就不能保证代换原则了。

3、依赖倒置原则(Dependence Inversion Principle)

  1. 内容

    高层不应该依赖低层,应该依赖抽象

    在传统结构化编程中,高层组件通常依赖其它低层组件来实现自身(“高层”即依赖低层组件才能定义自身的那些类)。这表现在UML 类图中就是自上而下的燕尾箭头依赖关系。

    那么依赖倒置就是说:逆转这种依赖,高层不应该依赖于低层。

    高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体,具体应当依赖于抽象。

    一句话:设计要依赖于抽象而不是具体。不要一上来就设计具体的类,要抽象的想。

  2. 表现

    如果没有任何继承关系存在,就会导致具体业务代码(高层)依赖于底层具体模块。此时,只要具体模块要增删改变,高层也要改变。

    应该:

    1. 传递参数,或者在组合聚合关系中,尽量引用层次高(祖先)的类。

    2. 主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉。

四、接口隔离原则(Interface Segregation Principle)

  1. 内容

    要使用多个专门的接口。每一个接口对应且仅对应一种角色。

  2. 表现

    如果不是接口隔离,那么就会出现这种情况:

    使用此接口的客户会面临由于这些不使用的方法的改变所带来的改变。所以Java要能够实现多个接口

五、合成/聚合复用(Composite/Aggregate Reuse Principle)

  1. 内容

    合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)经常又叫做合成复用原则

    合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。就是说要少用继承,多用合成关系来实现

    一句话:设计者首先应当考虑合成/聚合,而不是继承。实践中合成/聚合会带来比继承更大的利益。

  2. 表现

    有几个类要与数据库打交道,就写了一个数据库操作的类,然后别的跟数据库打交道的类都继承这个。结果后来,我修改了数据库操作类的一个方法,各个类都需要改动。“牵一发而动全身”!面向对象是要把波动限制在尽量小的范围。

    在Java中,应尽量针对Interface编程,而非实现类。这样,更换子类不会影响调用它方法的代码。要让各个类尽可能少的跟别人联系,“不要与陌生人说话”。扩展性和维护性才能提高。

六、最少知识原则

  • 内容

    也叫迪米特法则。不要和陌生人说话,即一个对象应对其他对象有尽可能少的了解。这是一个如何低耦合(Loosely-Coupled)的法则。

    一句话:一个对象应当尽可能少的去了解其他对象。

四、分类概述23种模式

概述

如上所述,GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》第一次将设计模式提升到理论高度,并将之规范化。

该书共提出了23种基本设计模式,这些解决方案已经能解决绝大多数问题了,所以开发者最基本的需要掌握这些设计模式。虽然在可复用面向对象软件的发展过程中,新的设计模式仍然会不断的出现,但我们掌握了基本原理定能融会贯通。

分类

Gof在《设计模式》中把设计模式分为3种类型23种:创建型模式、结构型模式、行为型模式,并把它们通过授权、聚合、诊断的概念来描述。

1. 创建型模式(Creational Patterns)5个

避免让使用者直接new 对象,而是引入一个第三方来解耦创建对象过程。

  1. Factory Method(工厂方法模式)

    作用:将客户(超类中的业务代码)和具体创建对象的代码解耦。

    行为:工厂方法利用继承,把创建对象的工作推迟到子类(在子类覆盖工厂方法创建对象)。

    《我的博文-工厂方法 Factory Method》

  2. Abstract Factory(抽象工厂模式)

    Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

    归类有相同主题的对象工厂。

    《我的博文-抽象工厂 AbstractFactory》

  3. Singleton(单例模式)

    限制一个类只能有一个实例。

    《单例模式 - Singleton Patterns》

  4. Builder(建造者模式)

    引入一个第三方(Builder)用于构建一个复杂对象,如此便将构造和展现分离,使得复杂构造流程在Builder中,而对象单纯用于展现。

  5. Prototype(原型模式)

    用clone 一个已存在对象的方式来创建对象。

2. 结构型模(Structural Patterns)7个

  1. 适配器模式(Adapter)
    Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise bacasuse of incompatible interfaces.
    将某个类的接口转换成客户端期望的另一个接口表示。适配器模式可以消除由于接口不匹配所造成的类兼容性问题

  2. 桥接模式(Bridge)
    Decouple an abstraction from its implementation so that the two can vary independently.
    将一个抽象与实现解耦,以便两者可以独立的变化。

  3. 组合模式(Composite)
    把多个对象组成树状结构来表示局部与整体,这样用户可以一样的对待单个对象和对象的组合。

  4. 装饰模式(Decorator)
    向某个对象动态地添加更多的功能。修饰模式是除类继承外另一种扩展功能的方法。

  5. 外观模式(Facade)
    为子系统中的一组接口提供一个一致的界面, 外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

  6. 享元(Flyweight)
    通过共享以便有效的支持大量小颗粒对象。

  7. 代理(Proxy)
    Provide a surrogate or placeholder for another object to control access to it.
    为其他对象提供一个代理以控制对这个对象的访问。
    我的博客:《代理模式 - Proxy Patterns》

3. 行为型模式(Behavioral Patterns)11个

  1. Chain of Responsibility(职责链模式)
  2. Command(命令模式)
  3. Interpreter(解释器模式)
  4. Iterator(迭代器模式)
  5. Mediator(中介者模式)
  6. Memento(备忘录模式)
  7. Observer(观察者模式)
  8. State(状态模式)
  9. Strategy(策略模式)
  10. Template Method(模版方法模式)
  11. Visitor(访问者模式)

参考:

wikipedia维基百科 - Design_Patterns设计模式

wikipedia维基百科 - 设计模式_(计算机)


接下来我会将基本的23种设计模式都介绍一遍,敬请期待。

你可能感兴趣的:(Design,Pattern,10/12,Object-Oriented)