把大话设计模式读薄

作者:bibodeng 发布于:2015-4-28 19:55 Tuesday 分类:编程技术

前言

最近用了近两三天来翻完了《大话设计模式》这本书,里面一一展示了29种设计模式,这些设计模式都是基于面向对象的,根据各种法则推演出来,是面向对象的“类的结构”。里面很多结构都是借鉴了生活中的一些解耦的思想,我们开发的过程中应当有意识地在编写代码的时候怎么样让程序结构更好。这其实是用一种面向对象的思想来思考程序的各个模块,而我们的世界本来就是各种对象的协作,我们是要让程序模拟我们的现实世界。全书都是用大鸟和小菜的有趣故事来作引喻,读来较为轻松。

通常在设计软件时想明白两个问题很重要: 该对象拥有的属性是什么? 该对象应该怎么与外界交互? 如果想明白了这两件事情,那么我们的设计模式大概也就此出来了。

大多数时候我们通常都是要具体情况具体分析,比如创建对象,当遇到对象种类太多的时候,我们用简单工厂来工作。而当一件事情遇到很强的耦合的时候,我们借助面向对象的三个特征(封装,继承,多态),并引入一些抽象类或者结构,就能够解决我们的难题。其中有几个让我印象特别深刻,如命令模式(专门用一个人来记录命令,命令本身作为一个对象),组合模式(树结构),职责链模式(链结构),这些结构是在特定情境下才该用的,用得不当强套反而会不好。

对象之间的交互,是设计模式的重点所在。设计模式主要分为三类:

  • 创建型模式 创建模式主要谈论该如何创建对象,适用于各种情况
  • 结构型模式 在内部或者外部引入某种结构,然后用这种结构解耦
  • 行为型模式 为了完成特定的任务,而引入结构来模拟某种行为

常见法则

  • 单一职责原则

单一职责原则

一个类应当只做一件事情,应该将事物进行抽象,将各种逻辑抽象到各类中,由类的协作交互来完成操作。这也是面向对象所要解决的问题,代码不应该到处复制,而只应当集中在一处,然后提供外部接口,这样修改起来也方便。

  • 封闭-开放原则

封闭-开放原则

一个软件实体应该可以扩展,但不应该被修改。在设计之初,应该让它抽象得足够好,并且容易扩展。而不是当需求有变动时,需要大量修改代码。书中一个很好的考验程序的方法时,加一些变化的需求,看看原本的程序是不是会很容易被改动,就比方说超市收银不仅是算算总数,而考虑上打折,返现,程序需不需要大改?

  • 依赖倒转原则

依赖倒转原则

上层不应当依赖于下层,上层和下层都应该依赖于抽象的接口,两者之间的关系,应当是一种抽象上的关系。而实例,只是延续了这种关系。就好像爸爸和儿子是一种抽象关系,再生一个儿子(实例),这种父子关系也能复用,不会改变。

依赖倒转

  • 里氏代换原则

里氏代换原则

一个类的子类实例,替代程序中的所有该类实例,程序不会出问题。就是说子类完全能够做父类能做的事情。既然子类和父类是 is a的关系,那么就应该维护这种抽象关系的纯净。就像无论老人小孩都是人,他们都是人,都具备人的根本属性。

  • 聚合复用

聚合复用原则

尽量使用聚合来替代继承,继承会让类型很多,维护很难。而现实世界中,大多数事物都是一种组合关系,比如汽车和和各个部件,尽量不要因为各种外在的东西而去新增类,比如多个烟囱的车还是一样的车,没必要去新增类,如果的确是继承关系,如各种种类的车(奔驰,宝马,特斯拉。。),也不要怕继承。

  • 迪米特法则(最少知识)

迪米特法则

一个类应当了解尽量少的知识便能工作起来,故而应当提供相应接口,不该别人知道的不让别人知道,自己不该知道的也不去知道。

创建型模式

顾名思义,创建型模式就是创建一个对象时使用的模式。

  • 单例

每个类仅能有一个这种类型的实例,这个实例作为类的Static属性,当存在的时候直接返回,如果不存在就创建一个存在这个属性中。我们常常在WCF的client端信道上使用单例模式,让它能够得到充分的复用。

  • 工厂

通过一个过程来组装一个对象,也可以把构造的过程隐藏起来,不用关心具体的实例化过程。简单工厂针对不同的子类型,根据传入参数进行判断从而选择构造函数。工厂方法则将工厂抽象出来,对应的工厂可以组装对应的产品,而决定使用哪个工厂的决定还是要的,只不过是延后到了客户端,可以使用反射技术就能够灵活地在构建工厂的时候指定类型。

  • 抽象工厂

在工厂方法之上的一种升级,工厂可以构造多种产品。创建一系列相关或相互依赖的接口,而无需指定具体的类。对被构造的产品,又提供了一层抽象产品类,从而解除具体类与工厂的依赖。

  • 建造者

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以构建不同的表示。引入一个指挥类,让建造者来负责复杂对象的构建,而不用在内部去按顺序组建。说白了,就是把组装过程分离出来,在需要的时候注入到Builder里面。

  • 原型

说到原型就应该想到实例,拷贝一个实例,然后加以改造返回——克隆。使用这个模式是为了在构建对象时,只要小小改动,却要费力重建一遍的情况下节省步骤。就像抄别人试卷一样,答案全抄过来,改掉名字就可以了(当然千万不要这么做)。

结构型模式

通过将对象间的形成某种存储上的结构,从而使用其独特的结构特性来完成工作。

  • 适配器

将原有的接口适配到新的接口,在封装时做一些额外的工作。

  • 装饰器

对对象进行一层封装,而且封装之后还可以再次进行封装,就像穿衣服一样,一件套一件。这个外套的内部维护着一个人的实体,而外套的装饰行为,则会影响到里面的人。技术上有个要点就是这个装饰类也是人的子类——装饰了xx的人。

  • 桥接

将抽象部分和实现部分分离,使得它们可以独立变化。要表达两个类之间的关系,可以这样做:两个类之上还有一个抽象类,而两个类之间的关联,可以在抽象的类之间定义。

  • 组合

类似于数据结构中的树结构,呈一种组合递归形态。一个节点可以由子节点或者叶子节点来组成,而这个节点也可以作为其它节点的子节点。在技术上就是存储对孩子的引用,从而可以从父节点下钻到孩子节点。这个模式还有一个特色,那就是由于父子实现的接口一致,顶部的操作可以一直递降到每个子节点直至叶子节点,在UI上,这种设计可能比较有用(如折叠一个树结构目录)。

  • 享元

维护一个(组)资源,这个资源大家一起使用,而不是每个实例都维护一个(组)。共享才是王道,技术上可以使用一些判断来减少大对象的维护,当需要的时候返回现成的。

  • 代理

帮别人送情书,但是收信人不知道真正写信的人的存在,只知道有人送给ta。代理方和被代理方应该实现一样的接口,而代理方维护一个被代理方,被代理方才是真正干活的人。这种模式往往用来对操作进行封装,进行额外的权限控制等。

  • 外观

为复杂的子系统提供一个漂亮的门面,这样可以适当地隐藏子系统后面背后的复杂逻辑,从而提供简洁的接口。用享元模式,支持大量细粒度的对象。

行为型模式

  • 观察者

大家订阅观察者的信息变化,当事件发生时,观察者负责通知各方执行操作。

  • 模板

将核心的逻辑骨架(执行A,执行B)放到父类中,而子类存储那些会变化的东西(具体重写的A,B过程)。这样当执行父类的模板方法的时候,就执行了具体的A,B方法。

  • 命令

将命令抽象出来成为一个实体,命令的执行就是让接受者做出相应动作,另外有个通知者,负责将命令触发。命令还可以方便记录日志,放入队列中等操作。

  • 状态

让状态之间去相互变化,无需集中控制,就像灯的开关一样,打上是开,打下是关,将它们分开来抽象成两种状态,这样变化起来就简单了——互相转化,而不是耦合在一起去控制其状态。

  • 职责链

通过在内部维护一个下步处理人,类似于数据结构中的链表,当前处理完后可以传递到下一步处理人处理。在审批流程中常常用到职责链模式。

  • 解释器

和编译器相关,将一些频繁使用的代号用一些抽象符号代替,需要使用的时候再进行解释,或者是在不同系统中进行转译。这个模式是借用了编译器中的概念。

  • 中介者

大家都通过一个中介器来访问其它对象,解除类与类之间的依赖。中介者模式应用在一组对象的复杂通信中。

  • 访问者

将处理过程的变化从数据结构中分离出来,在算法容易变化,数据结构比较稳定的情况下适用。如果数据结构不稳定,那需要经常修改模式中的代码,反而带来麻烦。一个事物接受观察者的观察,而观察者则执行具体的观察动作,外层还可以做一个集中控制一组这样的事物的观察动作。(这是一个比较复杂的模式)

  • 策略

将分支封装成方案策略,这样就不会导致一大堆的分支判断。将策略注入到上下文中,将具体算法和客户端调用进行隔离。

  • 备忘录

用一个简单点的结构记住实体的状态,而不是将实体整个存储,如打游戏要存档生命值,进度等,回来的时候加载档案即可。

  • 迭代器

实现一个外部的迭代器,可以迭代一个可遍历的容器中的成员,这个迭代器不会漏掉其中一个元素。

项目中的应用

学习了设计模式,就应当在适当地时候加以使用,而各模式都有其使用的特定情境,它就像是对象关系的一种数据结构,用得恰到好处就能解决软件开发中的一些问题。我在日常的开发中,也留意到了前人写的代码中使用了一些设计模式,整个系统能够承受更加复杂的架构,而代码也显得优雅,易于拓展。

by bibodeng 2015-4-28

标签: 设计模式 软件法则

发表评论:

Powered by emlog 京ICP备16017775