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

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


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

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

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

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

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

剑谱真容

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


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

招式入门必备

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

  1. 依赖


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


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


 public class A extends B{

}
  1. 聚合
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();
       }
   }
}
  1. ConcreteSubject:被观察者的实体对象,包含改变状态的方法,状态一旦改变,要通知所有观察者。
public class ConcreteSubject extends Subject{

   private String subjectState;
   
   public String GetState(){
       return subjectState;
   }
   
   public void SetState(String subjectState){
       this.subjectState = subjectState;
       //此处是关键,但图谱中没有记载
       //当状态改变时通知所有观察者进行更新
       this.Notify();
   }
}
  1. Observer:所有观察者的抽象父类。
public abstract class Observer {
   public abstract void Update();
}
  1. 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("被观察者的状态发生改变,通知并自动更新跟其有关的其他对象");
       }
   }
}
  1. 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. 侠之大者,应勤学苦练内功心法,永远不要安于现状。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,000评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,825评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,055评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,766评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,639评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,453评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,860评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,492评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,783评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,817评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,624评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,439评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,872评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,075评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,372评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,868评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,075评论 2 338

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 商品类别树## 考虑这样一个实际的应用:管理商品类别树。 在实现跟商品有关的应用系统的时候...
    七寸知架构阅读 5,945评论 10 59
  • 1 场景问题# 1.1 订阅报纸的过程## 来考虑实际生活中订阅报纸的过程,这里简单总结了一下,订阅报纸的基本流程...
    七寸知架构阅读 4,523评论 5 57
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,518评论 18 139
  • 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于...
    JxMY阅读 922评论 1 3
  • 离开Bali岛,现在在登巴萨机场,飞机准备起飞,发现流量还没用完,有一种淡淡的忧桑...
    melos2016阅读 140评论 0 0