武林秘籍之设计模式迷你手册

96
geniusmart
2016.05.10 10:38* 字数 3211

偶然发现几年前写的一篇文章,现在看来觉得也挺有意思的,特录入于此,希望对初学设计模式的同学有所帮助。



编程是一个江湖,江湖之大,鱼龙混杂,一部分江湖人士乃虾兵蟹将,一不小心就被一箭射死,我们称之为“码农”,这些人事江湖的重要组成部分,他们承担着堆砌代码,实现功能设计的使命,他们在江湖中虽为龙套,但不可或缺。另一部分人,华山论剑,刀光剑影,矗立江湖之巅,他们是系统分析师、架构师等,他们内功深厚,视野开阔,一招一式,举手投足间蕴藏着对可维护性、可扩展性等的深思熟虑。当然,更多的一部分人,他们不甘于现状,天资聪慧,正由“码农”向高手的身份努力中。

对于初出茅庐的江湖新人何时才可以扬眉吐气,一睹华山之貌,这漫漫长路着实坎坷,摆在眼前的有诸多武侠剑谱:Java,Java EE,Android,UML建模,用户体验,项目管理,算法,行业领域,商业知识等等。有些人聪明勤奋,他们用较短的时间升华了自己,有些人一辈子也无法走完这条长路。

在这个编程的江湖中,我们使用着共同的武器,夜以继日的用一招一式,一键一盘修炼着基本功,但终有一日,我们发现这些招式逐渐重复,我们的这一剑下去,可能伤到自己,伤到自己精心编写的这个作品,然后我们贴上狗皮膏药,继续挥洒我们的刀剑,久而久之,眼前的这个作品已经面目全非,甚至连我们自己都极其厌恶,恨不得推翻重来。

因此,是时候考虑一下提高自己的内功修为了,把招式化繁为简,回顾一下我们以前的江湖战绩吧:难以复用、难以维护、难以扩展……缺点之多不再赘述,于是我们拜师学艺,如获至宝:"开-闭"原则、里氏代换原则、单一职责原则、依赖倒转原则、接口隔离原则、迪米特法则……是的,这些已经成文的内功心法早已是公开的秘密,但是这些咒语一般的口诀如此飘渺无边,领悟起来实在太难太难,一不小心,便如欧阳锋一般倒练经文,走火入魔,那么是否有些实实在在的招式让我们更好的领悟这些内功心法?

幸运的是,我们的前辈们早已悟透这些内功心法,编写了23种招式,并记录成图谱和文字,今日,小侠便与各位江湖人士分享一下这些招式的入门。记载这些招式的剑谱朴素至极,精简至极,它的名字便是《设计模式迷你手册》。小侠这篇文章仅仅是在这本剑谱的基础上,抛砖引玉,让各位初入江湖的侠客,即使无深厚的技术功底,也可如看图识字一般,读懂看似深奥的设计模式,并且可以找到切入点,挥洒刀剑,留下实现设计模式的代码,进而培养兴趣,并小有所成,为以后的侠客之路做好铺垫。

剑谱真容

如何取得这本剑谱,各位侠客无需为了得到藏匿于倚天剑和屠龙刀中的武林秘籍而争得头破血流,只需向百大侠或谷大侠免费索取即可。这里提供一个线上的版本:点击这里查看,接下来,我们来看一下这本剑谱的大纲:



这本剑谱记载着23种招式,并分成三部分,分别为创建型、结构型和行为型,每个招式分别有图谱(UML类图)以及文字说明(设计模式的意图和适用性)。

招式入门必备

行走江湖,需要学几招防身的基本招式,以下介绍几招简单得不能再简单的基本功,分别是依赖、关联、继承和聚合,其图谱和招式如下所示,各位看官花几秒钟时间阅读以下类图和对应的代码实现。

  1. 依赖


    public class A{
     public void method(){
         //B为A的局部变量
         B b = new B()
     }
    }
  2. 关联


    public class A{
     //B为A的全局变量
     B b = new B()
    }
  3. 继承


    public class A extends B{
    
    }
  4. 聚合

public class A{

    List<B> list = new ArrayList<B>();

    public void add(B b){
        //A由1个或多个B组成
        list.add(b);
    }
}

有了这几招基本功之后,便可进入具体招式的修炼,任何招式都是由这四种关系组合而成。接下来我们进入23种招式的研习。研习的路线如下:
(1)打开《设计模式迷你手册》-->(2)查看UML类图-->(3)分析类图中依赖、关联、继承、聚合的关系,着手开始写代码-->(4)写完各个类图,通过Client类进行验证-->(5)揣摩该模式的意图、适用性、优缺点-->(6)研究该模式的实际应用。

本文目的在于让各位侠客根据最原始的剑谱《设计模式迷你手册》,自行领悟代码,因此只介绍了如何把UML图实现为代码,第(5)、(6)点本文并没有详细介绍,各位侠客日后需多加钻研这两点,毕竟这才是设计模式的精华。

招式之组合模式(Composite)



在这个招式里面,我们可以找到关联、继承、聚合这三招入门招式,因此写起来并不复杂,如下:

  • Component:节点的抽象类
    public abstract class Component {
      public abstract void Operation();
      public abstract void Add(Component c);
      public abstract void Remove(Component c);
      public abstract Component GetChild(int i);
    }
  • Composite:组合体,如树一般,可包含叶子节点或子树

    public class Composite extends Component{
    
      //Composite由多个Component组成(聚合关系)
      private List<Component> list = new ArrayList<Component>();
      public void Operation() {
               for(Component c:list){
                   //遍历所有子节点,并执行子节点的方法
                   c.Operation();
               }
      }
    
      public void Add(Component c) {
          list.add(c);
      }
    }
  • Leaf:叶子节点
    public class Leaf extends Component{
      public void Operation() {
          System.out.println("this is leaf");
      }
    }
  • Client:客户端,构造一个组合体,并遍历该组合体的所有叶子节点,如下图所示:



    这个类很重要,这个类是否写正确,以及运行结果是否正确意味着对设计模式是否真正理解

    public class Client {
      public static void main(String[]args){
          Leaf leaf1 = new Leaf();//叶子节点
          Leaf leaf2 = new Leaf();
          Leaf leaf3 = new Leaf();
    
          Composite c = new Composite();//子树节点,包含两个叶子节点
          c.Add(leaf2);
          c.Add(leaf3);
    
          Composite composite = new Composite();//树
    
          composite.Add(c);
          composite.Add(leaf1);
    
          composite.Operation();//遍历所有节点
      }
    }

招式之责任链模式(Chain of Responsibility)



这个招式包含了继承和依赖的关系,但是有一个类图比较难以理解,如下图所示,



如果把改图切换成如下所示,则比较好理解,



Handler依赖自身,即Handler类中有个全局变量,变量名为successor,代码如下所示:

public abstract class Handler{
    private Handler sucessor;
}

解决完这个问题后,剩下的难点就是如何建立起一条责任链:

  • Handler:所有责任处理者的抽象父类
public abstract class Handler {
    //自身关联对象
    private Handler successor;

    public abstract void  HandleRequest();

    //以下两个方法UML类图没有展示出来,应根据对该模式的理解自行添加
    public void setSuccessor(Handler s){
        this.successor = s;
    }

    public Handler getSuccessor(){
        return successor;
    }

}
  • ConcreteHandler1:责任处理者1,本处理者关联其他处理者

    public class ConcreteHandler1 extends Handler{
    
      public void HandleRequest() {
          getSuccessor().HandleRequest();//关联其他处理者
      }
    }
  • ConcreteHandler2:责任处理者2,本处理者关联其他处理者

    public class ConcreteHandler2 extends Handler{
    
      public void HandleRequest() {
          getSuccessor().HandleRequest();//关联其他处理者
      }
    }
  • ConcreteHandler3:责任处理者3,对逻辑进行处理

    public class ConcreteHandler3 extends Handler{
    
      public void HandleRequest() {
          System.out.println("责任处理者:ConcreteHandler3对逻辑进行处理");
      }
    }
  • 客户端,构造责任链:处理者1-->处理者2-->处理者3,并执行处理的逻辑,真正处理逻辑的责任者为处理者3。

    public class Client {
    
      public static void main(String[]args){
          Handler handler1 = new ConcreteHandler1();
          Handler handler2 = new ConcreteHandler2();
          Handler handler3 = new ConcreteHandler3();
    
          handler2.setSuccessor(handler3);
          handler1.setSuccessor(handler2);
          //以上代码建立这样的责任链:处理者1->处理者2->处理者3
          //因此打印结果为“责任处理者:ConcreteHandler3对逻辑进行处理”
          handler1.HandleRequest();
      }
    }

招式之观察者模式(observer)



这里特意将这个图谱拿出来给各位一起研究,因为这个招式略显复杂,应慢慢研习。图谱中包括依赖、继承、聚合这三招基本招式,除此之外,不能错过图谱中的注释部分,里面的描述很有价值。

  1. Subject:所有被观察者的抽象父类,包含注册观察者、注销观察者和通知观察者的方法。

    public abstract class Subject {
     //聚合关系,Subject包含多个Observer对象
     private List<Observer> observers = new ArrayList<Observer>();
     //注册观察者的方法
     public void Attach(Observer o){
         observers.add(o);
     }
    
     //注销观察者的方法
     public void detach(Observer o){
         observers.remove(o);
     }
    
     //通知所有观察者执行更新的方法
     public void Notify(){
         for(Observer o:observers){
             o.Update();
         }
     }
    }
  2. ConcreteSubject:被观察者的实体对象,包含改变状态的方法,状态一旦改变,要通知所有观察者。

    public class ConcreteSubject extends Subject{
    
     private String subjectState;
    
     public String GetState(){
         return subjectState;
     }
    
     public void SetState(String subjectState){
         this.subjectState = subjectState;
         //此处是关键,但图谱中没有记载
         //当状态改变时通知所有观察者进行更新
         this.Notify();
     }
    }
  3. Observer:所有观察者的抽象父类。
    public abstract class Observer {
     public abstract void Update();
    }
  4. ConcreteObserver:观察者的实体对象

    public class ConcreteObserver extends Observer{
     //依赖关系
     private ConcreteSubject subject ;
     private String observerState;
    
     public ConcreteObserver(ConcreteSubject subject){
         this.subject = subject;
     }
    
     public void Update(){
         observerState = subject.GetState();
         if("change".equals(observerState)){
             System.out.println("被观察者的状态发生改变,通知并自动更新跟其有关的其他对象");
         }
     }
    }
  5. Client:客户端对象,当被观察者的状态发生改变时,立即通知所有的观察者,Client执行后,后台打印“被观察者的状态发生改变,通知并自动更新跟其有关的其他对象”。
    public class Client {
     public static void main(String[] args) {
         //被观察者对象
         ConcreteSubject subject = new ConcreteSubject();
         //观察者对象
         Observer observer = new ConcreteObserver(subject);
         //注册观察者
         subject.Attach(observer);
         //被观察者的状态发生改变,观察者通知其他相关的对象
         subject.SetState("change");
     }
    }

几个秘诀

  1. 这本剑谱中的图谱价值非常高,即23个UML类图,几个方框,几条直线勾勒出了变幻无穷的招式,因此要反复研究。

  2. 图谱看似简单,但是下手修炼并不容易,抛开一切杂念,从最基础的地方(依赖、关联、继承、聚合)开始入手,解析每个类图和类与类之间的关系,切忌光看不练。

  3. 图谱中的Client类非常重要,该类有些图谱中有记载有些则没有,各位修炼时,务必写好Client类,并执行,这里的代码和运行结果意味着这个设计模式你是否钻研正确。

  4. 当攻克了图谱之后,你已经小有所成,但不可沾沾自喜,不要错过图谱下面的文字说明,这里描述着该招式的“意图”、“适用性”,你不妨再思考一下它有何优缺点,以及实际开发过程中,开源的框架中,JDK的API中哪些场景用到了该设计模式,如装饰者模式在Java IO使用到,观察者模式在AWT的事件模型中使用到。当以上种种你都领悟之后,相信侠客你的内功修为已经上升一个层次。

后记

  1. 本文介绍了入门23种招式的一种研习技巧,代码不参杂任何的业务场景,也暂不研究具体应用,目的在于解决不少新手在学习设计模式时无从入手的困惑。
  2. 学习设计模式没有捷径,需要编写大量的代码之后,才可能有顿悟的一天。
  3. 学会了招式不等于成为高手。
  4. 不要迷信内功心法,为了招式而使用招式;即不要为了使用设计模式而使用。
  5. 侠之大者,应勤学苦练内功心法,永远不要安于现状。
文艺的安卓君