java与23种设计模式

1 23种设计模式分类

经典的《设计模式》一书归纳出23种设计模式,本文按《易学设计模式》一书归纳分类如下:

1.创建型模式

 

前面讲过,社会化的分工越来越细,自然在软件设计方面也是如此,因此对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:

 

工厂方法模式(Factory Method);

 

抽象工厂模式(Abstract Factory);

 

创建者模式(Builder);

 

原型模式(Prototype);

 

单例模式(Singleton)。

 

2.结构型模式

 

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这里有7个具体的结构型模式可供研究,它们分别是:

 

适配器模式(Adapter);

 

装饰器模式(Decorator);

 

 

代理模式(Proxy);

 

外观模式(Facade);

 

 

桥模式(Bridge);

 

组合模式(Composite);

 

享元模式(Flyweight)。

 

3.行为型模式

 

在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,这里有11个具体的行为型模式可供研究,它们分别是:

 

模板方法模式(Template Method);

 

观察者模式(Observer);

 

状态模式(State);

 

策略模式(Strategy);

 

职责链模式(Chain of Responsibility);

 

命令模式(Command);

 

访问者模式(Visitor);

 

调停者模式(Mediator);

 

备忘录模式(Memento);

 

迭代器模式(Iterator);

 

解释器模式(Interpreter)。

 

1.1 结构型模型

1 适配器模式 用于将一个类的接口转换成另一个类的接口。

实例:构造一个类包含控件绑定数据的实现

2 桥接模式

 可以将一个类的接口与它的实现分离,这样可以不用修改客户端代码就能改变或替换实现过程

3 组合模式

 是一个对象的集合,其中任何一个对象既可以是一个组合,也可以是一个叶子对象

实例:树型组织结构的建立。

4 装饰模式

 用一个类包装给定的类,并为它添加新的功能,将所有未改动的方法传递给下面的类

实例:构造一个类包含控件,改变控件的外观

5 外观模式

 将一些列复杂的对象放在一起,并提供一个新的,更简单的访问这些数据的接口。

实例:数据库访问类

6 享元模式

 把一部分的类数据移到类外部,在执行方法时将数据传递进来,通过这种方式限制那些又小又相似的实例的增加数量

实例:数据库访问方法传入不同的参数,实现不同的功能。

7 代理模式

 为一个比较复杂的对象提供一个简单的占位对象,实例化该复杂对象,在某种程度上比较浪费时间或代价较高。

实例: 图片显示,可以先显示各占位简单图片,然后读入真正图片

 

1.2 行为型模式

行为型模式通常和对象之间的通信有关。1 职责链

 把请求从健中的一个对象传递到下一个对象,直到请求被响应为止,通过这种方式在对象之间去耦合。

2 命令模式

 用简单的对象表示软件命令的执行,支持登录 和取消操作

3 解释器模式

 提供一个如何把语言元素包含在程序中的定义

4 迭代器模式

 提供了一种顺序访问一个类中的一些列数据的方式

5 中介者模式

 定义了如何用一个对象简化对象之间的通信,使对象之间不必互相了解

6 备忘录模式

 定义了如何保存一个类实例的内容以便以后能恢复它

7 观察者模式

 定义了一种把改动通知给多少对象的方式

8 状态模式

 允许一个对象在其内部状态改变时修改它的行为

9 策略模式

 将算法封装到类里

10 模板方法模式

 提供了算法的一个抽象定义

11 访问者模式

 在不改变类的前提下,为一个类添加多种操作。

 

1.3 创建型模式

工厂模式:根据提供给工厂的数据,从一些列相关的类中选择一个类实例并返回。抽象工厂模式:用于返回一组类中的一个,在某些情况下,它实际上为一组类返回一个工厂

生成器模式:根据提供给他的数据及其表示,将一系列对象组装成一个新对象,通常选这何种方式组装对象由工厂决定。

原型模式:当创建新实例代价比较高的时候,原型模式拷贝或克隆一个现有的类,而不是创建一个新实例。

单件模式:可以保证有且只有一个对象实例,并且提供一个该实例的全局访问点。

 

 

 

设计模式六大原则

目录:

设计模式六大原则(1):单一职责原则

设计模式六大原则(2):里氏替换原则

设计模式六大原则(3):依赖倒置原则

设计模式六大原则(4):接口隔离原则

设计模式六大原则(5):迪米特法则

设计模式六大原则(6):开闭原则

 

概要:

只扩展不修改

一个类只负责一个基本功能,一个方法完成一个功能

继承只扩展不修改原有方法

面向接口编程,只依赖抽象

接口最简原则,方法数尽量少,方法多的分解为多个接口

一个类中使用另一个类的对象时要降低耦合度,不要调用其细节内容

 

设计模式六大原则(1):单一职责原则

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

说到单一职责原则,很多人都会不屑一顾。因为它太简单了。稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。

比如:类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)

举例说明,用一个类描述动物呼吸这个场景:

class Animal{

    public void breathe(String animal){

        System.out.println(animal+"呼吸空气");

    }

}

public class Client{

    public static void main(String[] args){

        Animal animal = new Animal();

        animal.breathe("牛");

        animal.breathe("羊");

        animal.breathe("猪");

    }

}

运行结果:

牛呼吸空气

羊呼吸空气

猪呼吸空气

程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,需要将Animal类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:

class Terrestrial{

    public void breathe(String animal){

        System.out.println(animal+"呼吸空气");

    }

}

class Aquatic{

    public void breathe(String animal){

        System.out.println(animal+"呼吸水");

    }

}

 

public class Client{

    public static void main(String[] args){

        Terrestrial terrestrial = new Terrestrial();

        terrestrial.breathe("牛");

        terrestrial.breathe("羊");

        terrestrial.breathe("猪");

        

        Aquatic aquatic = new Aquatic();

        aquatic.breathe("鱼");

    }

}

运行结果:

牛呼吸空气

羊呼吸空气

猪呼吸空气

鱼呼吸水

我们会发现如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:

class Animal{

    public void breathe(String animal){

        if("鱼".equals(animal)){

            System.out.println(animal+"呼吸水");

        }else{

            System.out.println(animal+"呼吸空气");

        }

    }

}

 

public class Client{

    public static void main(String[] args){

        Animal animal = new Animal();

        animal.breathe("牛");

        animal.breathe("羊");

        animal.breathe("猪");

        animal.breathe("鱼");

    }

}

可以看到,这种修改方式要简单的多。但是却存在着隐患:有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛呼吸水”了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。还有一种修改方式:

class Animal{

    public void breathe(String animal){

        System.out.println(animal+"呼吸空气");

    }

 

    public void breathe2(String animal){

        System.out.println(animal+"呼吸水");

    }

}

 

public class Client{

    public static void main(String[] args){

        Animal animal = new Animal();

        animal.breathe("牛");

        animal.breathe("羊");

        animal.breathe("猪");

        animal.breathe2("鱼");

    }

}

可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;

例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。

遵循单一职责原的优点有:

· 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;

· 提高类的可读性,提高系统的可维护性;

· 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

设计模式六大原则(2):里氏替换原则

肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑。其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

定义2:所有引用基类的地方必须能透明地使用其子类的对象。

问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

举例说明继承的风险,我们需要完成一个两数相减的功能,由类A来负责。

class A{

    public int func1(int a, int b){

        return a-b;

    }

}

 

public class Client{

    public static void main(String[] args){

        A a = new A();

        System.out.println("100-50="+a.func1(100, 50));

        System.out.println("100-80="+a.func1(100, 80));

    }

}

运行结果:

100-50=50

100-80=20

后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:

· 两数相减。

· 两数相加,然后再加100。

由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:

class B extends A{

    public int func1(int a, int b){

        return a+b;

    }

    

    public int func2(int a, int b){

        return func1(a,b)+100;

    }

}

 

public class Client{

    public static void main(String[] args){

        B b = new B();

        System.out.println("100-50="+b.func1(100, 50));

        System.out.println("100-80="+b.func1(100, 80));

        System.out.println("100+20+100="+b.func2(100, 20));

    }

}

类B完成后,运行结果:

100-50=150

100-80=180

100+20+100=220

我们发现原本运行正常的相减功能发生了错误。原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

· 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

· 子类中可以增加自己特有的方法。

· 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

· 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?

后果就是:你写的代码出问题的几率将会大大增加。

设计模式六大原则(3):依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。代码如下:

class Book{

    public String getContent(){

        return "很久很久以前有一个阿拉伯的故事……";

    }

}

 

class Mother{

    public void narrate(Book book){

        System.out.println("妈妈开始讲故事");

        System.out.println(book.getContent());

    }

}

 

public class Client{

    public static void main(String[] args){

        Mother mother = new Mother();

        mother.narrate(new Book());

    }

}

运行结果:

妈妈开始讲故事

很久很久以前有一个阿拉伯的故事……

运行良好,假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下:

class Newspaper{

    public String getContent(){

        return "林书豪38+7领导尼克斯击败湖人……";

    }

}

这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。假如以后需求换成杂志呢?换成网页呢?还要不断地修改Mother,这显然不是好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。

我们引入一个抽象的接口IReader。读物,只要是带字的都属于读物:

interface IReader{

    public String getContent();

}

Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:

class Newspaper implements IReader {

    public String getContent(){

        return "林书豪17+9助尼克斯击败老鹰……";

    }

}

class Book implements IReader{

    public String getContent(){

        return "很久很久以前有一个阿拉伯的故事……";

    }

}

 

class Mother{

    public void narrate(IReader reader){

        System.out.println("妈妈开始讲故事");

        System.out.println(reader.getContent());

    }

}

 

public class Client{

    public static void main(String[] args){

        Mother mother = new Mother();

        mother.narrate(new Book());

        mother.narrate(new Newspaper());

    }

}

运行结果:

妈妈开始讲故事

很久很久以前有一个阿拉伯的故事……

妈妈开始讲故事

林书豪17+9助尼克斯击败老鹰……

这样修改后,无论以后怎样扩展Client类,都不需要再修改Mother类了。这只是一个简单的例子,实际情况中,代表高层模块的Mother类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。所以遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。

采用依赖倒置原则给多人并行开发带来了极大的便利,比如上例中,原本Mother类与Book类直接耦合时,Mother类必须等Book类编码完成后才可以进行编码,因为Mother类依赖于Book类。修改后的程序则可以同时开工,互不影响,因为Mother与Book类一点关系也没有。参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。现在很流行的TDD开发模式就是依赖倒置原则最成功的应用。

传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递,相信用过Spring框架的,对依赖的传递方式一定不会陌生。

在实际编程中,我们一般需要做到如下3点:

· 低层模块尽量都要有抽象类或接口,或者两者都有。

· 变量的声明类型尽量是抽象类或接口。

· 使用继承时遵循里氏替换原则。

依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。

设计模式六大原则(4):接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

举例来说明接口隔离原则:

(图1 未遵循接口隔离原则的设计)

这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。对类图不熟悉的可以参照程序代码来理解,代码如下:

interface I {

    public void method1();

    public void method2();

    public void method3();

    public void method4();

    public void method5();

}

 

class A{

    public void depend1(I i){

        i.method1();

    }

    public void depend2(I i){

        i.method2();

    }

    public void depend3(I i){

        i.method3();

    }

}

 

class B implements I{

    public void method1() {

        System.out.println("类B实现接口I的方法1");

    }

    public void method2() {

        System.out.println("类B实现接口I的方法2");

    }

    public void method3() {

        System.out.println("类B实现接口I的方法3");

    }

    //对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,

    //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。

    public void method4() {}

    public void method5() {}

}

 

class C{

    public void depend1(I i){

        i.method1();

    }

    public void depend2(I i){

        i.method4();

    }

    public void depend3(I i){

        i.method5();

    }

}

 

class D implements I{

    public void method1() {

        System.out.println("类D实现接口I的方法1");

    }

    //对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,

    //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。

    public void method2() {}

    public void method3() {}

 

    public void method4() {

        System.out.println("类D实现接口I的方法4");

    }

    public void method5() {

        System.out.println("类D实现接口I的方法5");

    }

}

 

public class Client{

    public static void main(String[] args){

        A a = new A();

        a.depend1(new B());

        a.depend2(new B());

        a.depend3(new B());

        

        C c = new C();

        c.depend1(new D());

        c.depend2(new D());

        c.depend3(new D());

    }

}

可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如图2所示:

(图2 遵循接口隔离原则的设计)

照例贴出程序的代码,供不熟悉类图的朋友参考:

 interface I1 {

    public void method1();

}

 

interface I2 {

    public void method2();

    public void method3();

}

 

interface I3 {

    public void method4();

    public void method5();

}

 

class A{

    public void depend1(I1 i){

        i.method1();

    }

    public void depend2(I2 i){

        i.method2();

    }

    public void depend3(I2 i){

        i.method3();

    }

}

 

class B implements I1, I2{

    public void method1() {

        System.out.println("类B实现接口I1的方法1");

    }

    public void method2() {

        System.out.println("类B实现接口I2的方法2");

    }

    public void method3() {

        System.out.println("类B实现接口I2的方法3");

    }

}

 

class C{

    public void depend1(I1 i){

        i.method1();

    }

    public void depend2(I3 i){

        i.method4();

    }

    public void depend3(I3 i){

        i.method5();

    }

}

 

class D implements I1, I3{

    public void method1() {

        System.out.println("类D实现接口I1的方法1");

    }

    public void method4() {

        System.out.println("类D实现接口I3的方法4");

    }

    public void method5() {

        System.out.println("类D实现接口I3的方法5");

    }

}

接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。

说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。

采用接口隔离原则对接口进行约束时,要注意以下几点:

· 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。

· 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

· 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

设计模式六大原则(5):迪米特法则

定义:一个对象应该对其他对象保持最少的了解。

问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

解决方案:尽量降低类与类之间的耦合。

自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

举一个例子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。

 //总公司员工

class Employee{

    private String id;

    public void setId(String id){

        this.id = id;

    }

    public String getId(){

        return id;

    }

}

 

//分公司员工

class SubEmployee{

    private String id;

    public void setId(String id){

        this.id = id;

    }

    public String getId(){

        return id;

    }

}

 

class SubCompanyManager{

    public ListgetAllEmployee(){ Listlist = new ArrayList(); for(int i=0; i<100; i++){ SubEmployee emp = new SubEmployee(); //为分公司人员按顺序分配一个ID emp.setId("分公司"+i); list.add(emp); } return list; } } class CompanyManager{ public ListgetAllEmployee(){ Listlist = new ArrayList(); for(int i=0; i<30; i++){ Employee emp = new Employee(); //为总公司人员按顺序分配一个ID emp.setId("总公司"+i); list.add(emp); } return list; } public void printAllEmployee(SubCompanyManager sub){ Listlist1 = sub.getAllEmployee(); for(SubEmployee e:list1){ System.out.println(e.getId()); } Listlist2 = this.getAllEmployee(); for(Employee e:list2){ System.out.println(e.getId()); } } } public class Client{ public static void main(String[] args){ CompanyManager e = new CompanyManager(); e.printAllEmployee(new SubCompanyManager()); } }

现在这个设计的主要问题出在CompanyManager中,根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下:

class SubCompanyManager{

    public ListgetAllEmployee(){ Listlist = new ArrayList(); for(int i=0; i<100; i++){ SubEmployee emp = new SubEmployee(); //为分公司人员按顺序分配一个ID emp.setId("分公司"+i); list.add(emp); } return list; } public void printEmployee(){ Listlist = this.getAllEmployee(); for(SubEmployee e:list){ System.out.println(e.getId()); } } } class CompanyManager{ public ListgetAllEmployee(){ Listlist = new ArrayList(); for(int i=0; i<30; i++){ Employee emp = new Employee(); //为总公司人员按顺序分配一个ID emp.setId("总公司"+i); list.add(emp); } return list; } public void printAllEmployee(SubCompanyManager sub){ sub.printEmployee(); Listlist2 = this.getAllEmployee(); for(Employee e:list2){ System.out.println(e.getId()); } } }

修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

设计模式六大原则(6):开闭原则 

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。

在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。

图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。

在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。

到这里,设计模式的六大原则就写完了。主要参考书籍有《设计模式》《设计模式之禅》《大话设计模式》以及网上一些零散的文章,但主要内容主要还是我本人对这六个原则的感悟。写出来的目的一方面是对这六项原则系统地整理一下,一方面也与广大的网友分享,因为设计模式对编程人员来说,的确非常重要。正如有句话叫做一千个读者眼中有一千个哈姆雷特,如果大家对这六项原则的理解跟我有所不同,欢迎留言,大家共同探讨。

 

3 23种设计模式详细介绍

3.1 工厂方法模式(Factory Method

1.工厂方法模式(Factory Method

第一种――工厂方法模式:提供了一个工厂方法类,并提供了一个工厂方法函数(带识别对象的参数),该函数负责创建所有的产品对象。

第二种――多个工厂方法模式:提供了一个工厂方法类,并提供了多个工厂方法函数,不同函数负责创建不同的产品对象,对比上一种方法有效避免因为参数传递错误而不能正确创建产品。

第三种――静态工厂模式(简单工厂模式)将工厂方法置为静态的,可以直接引用工厂类的方法创建产品。避免了上面方法在每次使用时都需要创建一个工厂对象。

应用场景:

凡是出现大量的产品需要创建,并且具有共同的接口时,可以通过工厂模式进行创建。

 

3.2 抽象工厂模式(Abstract Factory)

 

一个工厂承担了所所有产品的创建工作,如果产品的树形结构需要扩展(新产品出现),就必须在工厂类中为新增的产品增加创建功能,这显然违背了开闭原则(在扩展的时候不能修改原有的代码)。为解决这个问题,需要创建一个工厂类接口(它提供一个接口方法来创建一个产品),如果要扩展增加一个新的产品,只需要增加一个新的工厂类即可。同时如果产品类中出现多层继承,也可以在工厂类中进行多层继承。

使用场景:抽象工厂适用于产品结构多变或有可能扩展的产品创建中。当产品结构比较固定也比较简单的时候使用工厂模式方法比较好,毕竟抽象工厂方法增加了好多的工厂类。

 

3.3 单例模式(Singleton)

单例模式是一种特殊的工厂方法模式,它适用于一个类只有一个实例的情况。

基本概念:
Singleton 是一种创建性模型,它用来确保只产生一个实例,并提供一个访问它的全局访问点.对一些类来说,保证只有一个实例是很重要的,比如有的时候,数据库连接或 Socket 连接要受到一定的限制,必须保持同一时间只能有一个连接的存在.

运用:
在于使用static变量;
创建类对象,一般是在构造方法中,或用一个方法来创建类对象。在这里方法中,加对相应的判断即可。

一个标准的单例模式需要包含如下4个要素:

(1)拥有一个私有的静态实例,该实例禁止外部访问;

(2)拥有私有的默认构造函数,防止使用构造函数进行实例化;

(3)拥有一个静态工厂方法,并且必须是同步的,防止多线程环境同时执行;

(4)重写clone()函数,并返回当前实例对象,默认的clone()函数会创建新的实例。

实例代码:

public class SingletonFactory {

private static SingletonFactory _instance = null;

private SingletonFactory() {

}

synchronized public static SingletonFactory getInstance() {

if (_instance == null) {

_instance = new SingletonFactory();

}

return _instance;

}

public SingletonFactory clone() {

return getInstance();

}

}

使用场景:(系统中该类的实例只有一个)

(1)系统的全局变量、存储区域

(2)系统的全局配置文件

(3)系统的全局操作函数


3.4 建造者模式(Builder)

Builder模式的定义是:将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 首先它意图是要构建一个复杂的对像,而这个复杂的对像往往需要由好几个子对象或分步骤来完成最终的这个对象的构建,而这个复杂对象的子对象经常需要不断的变化,但它的构建过程是相对是稳定的。

 

使用场景:

 一般来说在以下两种情况下应当考虑使用建造者模式:
       1、被建造的对象有复杂的组成部分,每一部分的生成算法又不一样。(采用具体建造者不同的方法,实现对象不同部分的构造)

       2、被建造的对象各部分有一定的组成顺序。(用DirectorConcreteBuild方法的调用次序来控制)

例如:

    买肯德基:(Terrylee的例子)典型的儿童餐包括一个主食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。

客户端:顾客。想去买一套套餐(这里面包括汉堡,可乐,薯条),可以有1号和2号两种套餐供顾客选择。
指导者角色:收银员。知道顾客想要买什么样的套餐,并告诉餐馆员工去准备套餐。
建造者角色:餐馆员工。按照收银员的要求去准备具体的套餐,分别放入汉堡,可乐,薯条等。

产品角色:最后的套餐,所有的东西放在同一个盘子里面。

Builder模式的架构


抽象建造者(Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
具体建造者(Concrete Builder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
指导者(Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
产品(Product:要创建的复杂对象。

    程序实现:
    //指导者
    class Director{
        public void build(Builder b){
            b.BuildPartA();
            b.BuildPartB();
        }
    }
    //抽象建造者
    abstract class Builder{
       //创建产品的第一部分
        public abstract void BuildPartA();
        //创建产品的第二部分 
        public abstract void BuildPartB();
    }
    //具体建造者类
    class ConcreteBuilder1 : Builder
    {
        Product p = new Product();
        public override void BuildPartA()
        {
            p.Add("Part1","Part1");
        }
        public override void BuildPartB()
        {
            p.Add("Part2","Part2");
        }
       //返回产品对象 
        public Product GetProduct()
        {
            return p;
        }
    }
    //具体建造者类
    class ConcreteBuilder2 : Builder
    {
       Product p = new Product();
        public override void BuildPartA()
        {
            p.Add("PartA","PartA");
        }
        public override void BuildPartB()
        {
            p.Add("PartB", "PartB");
        }
        //返回产品对象 
        public Product GetProduct()
        {
            return p;
        }
    }
    //产品类
    class Product
    {
        StringDictionary sd = new StringDictionary();
        public void Add(string name, string value)
        {
            sd.Add(name, value);
        }
        public void Show()
        {
            foreach (DictionaryEntry de in sd)
            {
                Console.WriteLine(de.Key+"t"+de.Value+"n");
            }
        }
    }
    //客户程序
    class Client
    {
        public static void Main(string[] args)
        {
            Director d = new Director();
            Builder b1 = new ConcreteBuilder1();
            Builder b2 = new ConcreteBuilder2();
            d.build(b1);
            d.build(b2);
            ((ConcreteBuilder1)b1).GetProduct().Show();
            ((ConcreteBuilder2)b2).GetProduct().Show();
        }
    }

 

3.5 原型模式(Prototype)

 

原型模式的特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的"原型",这个原型是可以定制的。

 

使用场景:原型模式多用于创建复杂的或耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。原型模式的关注点在于大量相似对象的创建。

 

补充知识:

浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象.

深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象.

两者区别在于对象的复制是否需要产生新对象

 

Prototype.java

public class Prototype implements Cloneable, Serializable {

private static final long serialVersionUID = 1L;

private String str;

private SerializableObject obj;

        //浅复制

public Object clone() throws CloneNotSupportedException {

Prototype prototype = (Prototype) super.clone();

return prototype;

}

        //深复制

public Object deepClone() throws IOException, ClassNotFoundException {

                //写入当前对象的二进制流

ByteArrayOutputStream bos = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(this);

                //读出二进制流产生新的对象

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();

}

 

public String getStr() {

return str;

}

 

public void setStr(String str) {

this.str = str;

}

 

public SerializableObject getObj() {

return obj;

}

 

public void setObj(SerializableObject obj) {

this.obj = obj;

}

}

class SerializableObject implements Serializable {

private static final long serialVersionUID = 1L;

}

PrototypeTest.java

public class PrototypeTest {

public static void main(String[] args) throws CloneNotSupportedException,

ClassNotFoundException, IOException {

// 创建对象

Prototype pt = new Prototype();

pt.setStr("Hello World!");

SerializableObject obj = new SerializableObject();

pt.setObj(obj); 

// 浅复制

Prototype pt1 = (Prototype) pt.clone();

System.out.println("浅复制原对象str" + pt.getStr());

System.out.println("浅复制新对象str" + pt1.getStr());

System.out.println("浅复制原对象obj" + pt.getObj());

System.out.println("浅复制新对象obj" + pt1.getObj()); 

// 修改浅复制对象的值

pt1.setStr("Hello China!");

System.out.println("修改后浅复制原对象str" + pt.getStr());

System.out.println("修改后浅复制新对象str" + pt1.getStr());

System.out.println("修改后浅复制原对象obj" + pt.getObj());

System.out.println("修改后浅复制新对象obj" + pt1.getObj()); 

// 深复制

Prototype pt2 = (Prototype) pt.deepClone();

System.out.println("深复制原对象str" + pt.getStr());

System.out.println("深复制新对象str" + pt2.getStr());

System.out.println("深复制原对象obj" + pt.getObj());

System.out.println("深复制新对象obj" + pt2.getObj()); 

// 修改深复制对象的值

pt2.setStr("Hello China!");

System.out.println("修改后深复制原对象str" + pt.getStr());

System.out.println("修改后深复制新对象str" + pt2.getStr());

System.out.println("修改后深复制原对象obj" + pt.getObj());;

System.out.println("修改后深复制新对象obj" + pt2.getObj());

}

}

 

 

3.6 组合模式

组合就是对象的集合, 其中的每个对象既可以是一个组合,也可以是简单的对象。

组合模式的效果:

组合模式定义了包含简单对象和复杂组合对象的类层次结构,并使他们对客户端程序具有一致性。由于这种简化,客户端可以变得相当简单,因为节点和叶子可以用同样的方式去处理。

组合模式使得向集合添加新类型的组件变得容易,只要这些组件提供一个相似的编程接口。另一方面,这也有缺点,就是使你的程序更加一般化,很难限制某个类,而通常都希望能做到这一点。

组合模式的意图是,允许构建一棵由各种相关类组成的树,即使某些类具有不同的属性,某些类是叶子不能有孩子。


使用场景

  引用大话设计模式的片段:当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。


例子:

应用组合模式的会员卡消费

我们就根据我们会员卡的消费,来模拟一下组合模式的实现吧!let's go

首先:

    1.我们的部件有,总店,分店,加盟店!

    2.我们的部件共有的行为是:刷会员卡

    3.部件之间的层次关系,也就是店面的层次关系是,总店下有分店、分店下可以拥有加盟店。

有了我们这几个必要条件后,我的要求就是目前店面搞活动当我在总店刷卡后,就可以累积相当于在所有下级店面刷卡的积分总额,设计的代码如下:

/// 店面类 抽象出来的店面部件

    public abstract class Storefront

    {

        //店名

        protected string storeName = string.Empty;

        public string StoreName

        {

            get

            {

                return storeName;

            }

        }

 

        //添加店面

        public abstract void Add(Storefront store);

        //删除店面

        public abstract void Remove(Storefront store);

 

        //定义所有部件公用的行为 刷卡行为

        public abstract void PayByCard();

    }

 

    public class StoreOrBranch : Storefront

    {

        //构造函数

        public StoreOrBranch() { }

        public StoreOrBranch(string storeName)

        {

            this.storeName = storeName;

        }

        ListmyStoreList = new List();

        //刷卡消费

        public override void PayByCard()

        {

            Console.WriteLine("店面{0}的积分已累加进该会员卡", storeName);

            foreach (Storefront sf in myStoreList)

            {

                sf.PayByCard();

            }

        }

        //增加店面

        public override void Add(Storefront store)

        {

            myStoreList.Add(store);

        }

        //解除店面

        public override void Remove(Storefront store)

        {

            myStoreList.Remove(store);

        }

    } 

    public class JoinInStore : Storefront

    {

        //构造函数

        public JoinInStore() { }

        public JoinInStore(string storeName)

        {

            this.storeName = storeName;

        }

        //刷卡消费

        public override void PayByCard()

        {

            Console.WriteLine("店面{0}的积分已累加进该会员卡", storeName);

        }

 

        public override void Add(Storefront store)

        {

            throw new NotImplementedException();

        }

 

        public override void Remove(Storefront store)

        {

            throw new NotImplementedException();

        }

    }

 

   static void Main(string[] args)

   {

            StoreOrBranch store = new StoreOrBranch("朝阳总店");

            StoreOrBranch brach = new StoreOrBranch("东城分店");

            JoinInStore jstore = new JoinInStore("海淀加盟店一");

            JoinInStore jstore1 = new JoinInStore("上地加盟店二");

 

            brach.Add(jstore);

            brach.Add(jstore1);

            store.Add(brach);

 

            store.PayByCard();

    }

结果:

总店……

分店……

加盟店一……

加盟店二……

    这样在累积所有子店面积分的时候,就不需要去关心子店面的个数了,也不用关系是否是叶子节点还是组合节点了,也就是说不管是总店刷卡,还是加盟店刷卡,都可以正确有效的计算出活动积分。

 

3.7 桥接模式

桥接模式非常像适配器模式,都是用一个类将一种接口转换成另外一种接口,但是适配器模式的意图是:使一个或多个类的接口看起来像一个特定类的接口。桥接模式将类的接口和它的实现分离,无需修改客户端代码就可以改变或替换实现过程

桥接模式的效果:

1 桥接模式可以保持客户端程序的接口不变,而允许读者修改显示类或要使用的类。这样可以防止重新编译一系列复杂的用户接口模块,而只需要重新编译Bridge和实际的最终显示类

2 可以分别扩展实现类和Bridge类,二者之间通常不会有相互作用。

3 对客户端程序很容易隐藏实现细节。

使用场景:

Bridge模式是一种抽象与其实现相分离的模式。它主要应用于:当事物是一组变化量,和对这些事物的操作方法(实现)也是一组变化量的情况,也就是说它们都是多变的。

Bridge模式的应用范例

我们来看看怎么应用Bridge模式来设计汽车类。

抽象 - Abstraction类:汽车类及其子类:
Car:汽车总类
Truck:汽车子类 卡车类。
Bus:汽车子类 公交车类。

行为实现 - Implementor:汽车引擎设置的行为类及子类
SetCarEngine:汽车引擎的设置接口
SetCarEngine1500cc:设置1500cc引擎
SetCarEngine2000cc:设置2000cc引擎

代码:

package zieckey.designpatterns.study.bridge;

//测试

public class Client
{
    public static void main( String[] argv )
    {
        Engine engine1500 = new Engine1500CC();
        Engine engine2200 = new Engine2200CC();
       
        Vehicle bus1500 = new Bus( engine1500 );
        Vehicle bus2200 = new Bus( engine2200 );
        bus1500.setEngine();
        bus2200.setEngine();
       
        Vehicle truck1500 = new Truck( engine1500 );
        Vehicle truck2200 = new Truck( engine2200 );
        truck1500.setEngine();
        truck2200.setEngine();
    }
}

package zieckey.designpatterns.study.bridge;

/**
 *
 * 汽车类(Vehicle),假设有2个子类,卡车类(Truck)与公交车类(Bus),
 * 它们有[设置引擎]这个动作行为,通过不同引擎规格的设置,
 * 可以将它们设置为比如为1500ccCar1500),和2000ccCar2000)的车。
 * 这样,不管是1500cc的卡车还是2000cc的卡车,又或是1500cc的公交车还是2000cc的公交车,它们都可以是汽车类的子类,而且:
 *     - 存在相对并列的子类属性。汽车的种类,与汽车引擎规格是汽车的2个并列的属性,没有概念上的重复。
 *     - 存在概念上的交叉。不管是卡车还是公交车,都有1500cc2000cc引擎规格的车。
 *     - 可变性。除了卡车,公交车之外,可能还有救火车;除了有1500cc2000cc引擎规格的车之外,还可能有2500cc的车等等。
 *
 * @author
 * @since 2008/06/23
 */
public abstract class Vehicle
{
    private Engine engine;

    Vehicle( Engine engine )
    {
        this.setEngine( engine );
    }
   
    public abstract void setEngine();

    public void setEngine( Engine engine )
    {
        this.engine = engine;
    }

    public Engine getEngine()
    {
        return engine;
    }
}

package zieckey.designpatterns.study.bridge;

//Abstraction子类:这里为汽车抽象类的子类

public class Bus extends Vehicle
{
    public Bus( Engine engine)
    {
        super( engine );
    }
   
    @Override
    public void setEngine()
    {
        System.out.print("Set Bus Engine: ");
        getEngine().setEngine();
    }
}

package zieckey.designpatterns.study.bridge;

//Abstraction子类:这里为汽车抽象类的子类

public class Truck extends Vehicle
{
    public Truck( Engine engine )
    {
        super( engine );
    }

    @Override
    public void setEngine()
    {
        System.out.print("Set Truck Engine: ");
        getEngine().setEngine();
    }

}

package zieckey.designpatterns.study.bridge;

//汽车类的行为接口

public interface Engine
{
    public void setEngine();
}

package zieckey.designpatterns.study.bridge;

/** ConcreteImplementor */
//行为实现子类

public class Engine2200CC implements Engine
{

    public void setEngine()
    {
        System.out.println("engine 2200CC");
    }   
   
}

package zieckey.designpatterns.study.bridge;

/** ConcreteImplementor */
//行为实现子类

public class Engine1500CC implements Engine
{

    public void setEngine()
    {
        System.out.println("engine 1500CC");
    }   
   
}

 

3.8 装饰器模式

装饰模式提供一种方法:改变单个对象的行为,但不需要创建一个新的派生类。

装饰模式提供了一种给一个类添加职责的方法,它比使用继承更加灵活,因为它能将职责加到类的指定实例中,它也允许定制一个类,而无需在继承层次结构中创建高层次子类。

装饰模式的缺点:

1 Decorator和它包含的组件是不一样的,这样,检测对象类型时会失败。

2 装饰模式会使一个系统带有“大量的小对象”,对于维护代码的程序员来说,它们看起来都差不多,维护起来很困难。

使用场景:保持接口,增强功能。(必须有一个被装饰的对象、拥有与被装饰对象相同的接口、它可以给被装饰对象添加额外的功能)

 

3.9 适配器模式

适配器模式可以将一个类的程序设计接口转换成另外一个接口。当我们想让不相关的类在一个程序里一起工作时,可以使用适配器模式。适配器的概念相当简单:编写一个具有所需要的接口的类,然后让它和拥有不同接口的类进行通信。

类适配器:新建适配器类继承原始类,实现目标接口(原始类与目标接口需要有相同的函数接口),最终实现目标接口对原始类的调用。

对象适配器:实现目标接口,在适配器类中新建原始类对象,最终实现目标接口对原始类的调用。

接口适配器:为原有接口类实现一个默认的抽象类,在该抽象类中编写每一个接口的默认默认实现,当我们需要编写一个具体类的时候,只需要继承自该抽象类,而不需要实现原有的接口。并且此时我们不需要实现所有的接口方法,只实现需要的函数即可。


使用场景:

类适配器:当希望将一个接口转换成另一个接口时。

对象适配器:当希望将一个对象转换成另一个接口的时候。

接口适配器:当不希望实现一个接口所有方法的时候,可以先构造一个抽象类,给出所有方法的默认实现,这样这个抽象类再继承下去的子类就不必实现所有的方法了。

 

3.10 代理模式

代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。

适配器模式是将一个类A转换成另一个类B

装饰器模式是为了一个类A增加新的功能,从而变成类B

代理模式是为了一个类A转换操作类B

Java动态代理主要涉及到两个类:

· InvocationHandler:该接口中仅定义了一个Object : invoke(Object proxy, Method method, Object[] args);参数proxy指代理类,method表示被代理的方法,argsmethod中的参数数组,返回值Object为代理实例的方法调用返回的值。这个抽象方法在代理类中动态实现。

· Proxy:所有动态代理类的父类,提供用于创建动态代理类和实例的静态方法。

所谓动态代理类是在运行时生成的class,在生成它时,你必须提供一组interface给它,则动态代理类就宣称它实现了这些interface。当然,动态代理类就充当一个代理,你不要企图它会帮你干实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

 

3.11 外观模式

外观模式隔离了客户与子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受影响。尽管客户端使用由外观提供的简单接口,但是当需要的时候,客户端还是可以视外观不存在,直接访问子系统中的底层次接口。

使用场景:

外观模式可以将一些列复杂的类包装成一个简单的封闭接口。外观模式对客户屏蔽了复杂的子系统组件,并为一般用户提供了一个比较简单的程序设计接口。但是它并没有限制高级用户在需要时使用深层次的、较复杂的类。

 

外观模式中包含3种角色:目标类、外观类、客户端类。以计算机的加载和关闭过程为例,这3类角色如下:

目标类:包括CPU、内存、硬盘

外观类:即为计算机类。

客户端类:即为用户。

例子:

package structure.facade;

/**

 * @author liuzhongbing

 * 外观模式

 */

public class Computer {

private CPU cpu;

private Memory memory;

private Disk disk;

 

public Computer() {

cpu = new CPU();

memory = new Memory();

disk = new Disk();

}

 

public void startup() {

System.out.println("开始启动计算机");

cpu.startup();

memory.startup();

disk.startup();

System.out.println("启动计算机完成");

}

 

public void shutdown() {

System.out.println("开始关闭计算机");

cpu.shutdown();

memory.shutdown();

disk.shutdown();

System.out.println("关闭计算机完成"); 

}

}

测试:

package structure.facade;


public class User {

public static void main(String args[]) {

Computer computer = new Computer();

computer.startup();

computer.shutdown();

}

}


3.12 享元模式

享元可以理解为共享元对象,也就是共享细粒度对象,享元模式就是通过共享的方式达到高效地支持大量细粒度对象。它的目的就是节省占用的空间资源,从而实现系统性能得到改善。

享元对象的主要作用就是实现对象共享,即使用共享池,从而减少内存分配的开销。享元模式通常与工厂模式一起使用,它包含了多个共享的组合对象,因此:享元模式=单例模式+工厂模式+组合模式

使用场景:可以共享的对象,也就是说返回的同一类型的对象其实是同一实例,当客户端要求生成一个对象时,工厂会检测是否存在此对象的实例,如果存在那么直接返回此对象实例,如果不存在就创建一个并保存起来,这点有些单例模式的意思。通常工厂类会有一个集合类型的成员变量来用以保存对象,如hashtable,vector等。在java中,数据库连接池,线程池等即是用享元模式的应用。

 

3.12 策略模式

策略模式

一.策略模式是对象的行为模式,用意是对一组算法的封装。动态的选择需要的算法并使用

UML示意图

二.组成部分:

1. 环境角色:持有一个策略类引用

2. 抽象策略

3. 具体策略:相关的算法或操作

三.代码例子

1. 抽象策略

package com.eekq.strategy;

 

public interface IStrategy {

 

    /**策略方法*/

    public abstract double add();

}

2. 具体策略,这里我以两个具体策略为例

package com.eekq.strategy;

 

public class ConcreteStrategy1 implements IStrategy {

 

    /**示意性算法*/

    public double add() {

        // TODO 自动生成方法存根

        System.out.println(this.getClass().getName() + "的加法运算");

        return 0;

    }

 

}

package com.eekq.strategy;

 

public class ConcreteStrategy2 implements IStrategy {

 

    public double add() {

        // TODO 自动生成方法存根

        System.out.println(this.getClass().getName() + "的加法运算");

        return 0;

    }

 

}

3.环境角色

package com.eekq.strategy;

 

public class Context {

    /**环境角色类*/

    private IStrategy strategy;

 

    public Context(IStrategy strategy) {

        this.strategy = strategy;

    }

 

    /**策略方法*/

    public double add() {

        this.strategy.add();

        return 0;

    }

}

4.客户端调用

package com.eekq.strategy;

 

public class Main {

 

    /**

     *@paramargs

     */

    public static void main(String[] args) {

        // TODO 自动生成方法存根

        Context context = new Context(new ConcreteStrategy1());

        context.add();//执行算法1

        context = new Context(new ConcreteStrategy2());

        context.add();//执行算法2

    }

 

}

5.执行结果:

com.eekq.strategy.ConcreteStrategy1的加法运算

com.eekq.strategy.ConcreteStrategy2的加法运算

6.总结

优点:动态改变算法或行为

缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类,必须对每一个算法了解

 

3.13 模板方法模式

模板方法模式

定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法可以使子类在不改变一个算法结构的前提下就可以重定义该算法的某些特定步骤。

方法模式的特点:把不变的行为搬到超类,去除子类中重复的代码来体现他的优势。当不变的和可变的行为在方法中混合在一起时,不变的行为就会在子类中重复出现,模板方法模式就是将这些不变的行为搬移到一个超类中,避免重复代码。

应用场景:需要定义一些顶级逻辑或者是一个操作中算法的骨架,希望一些步骤的执行推迟到其子类中时应该考虑模板模式。

   在此写了3java类来描述说明Templete设计模式;
  1Templete.java      定义了顶级逻辑的抽象类
  2TempleteImpl.java  实现了抽象类中抽象方法的子类
  3TempletTest.java   带有main方法的测试类

===============   1Templete.java

package templete;

public abstract class Templete {

  //顶级逻辑

  public void topOperation(){

    //1

    beforeOperation();

   

    //2

    operation();

   

    //3

    afterOperation();

  }

 

  //需要在操作前执行的方法

  private void beforeOperation(){

    System.out.println("This acton before the operation!");

  }

 

  //需要在操作后执行的方法

  private void afterOperation(){

    System.out.println("This acton after the operation!");

  }

 

  //需要推迟到子类(实现类中执行

  protected abstract void operation();

}

===============   1 end

 

===============   2TempleteImpl.java

package templete;

public class TempleteImpl extends Templete {

  @Override

  protected void operation() {

    // TODO Auto-generated method stub

    System.out.println("The operation action is executed in the method of ServiceA instance! ");

  }

}

===============   2 end

 

===============   3TempletTest.java

package templete;

public class TempletTest {

  public static void main(String[] args) {

    Templete templete = new TempleteImpl();

    templete.topOperation();

  }

}

===============   3 end

 

 

3.14 观察者模式

观察者模式

    网上商店中的商品在名称、价格发生变化时,必须自动通知会员,JavaAPI为我们提供了Observer接口和Observable类来实现所谓观察者模式。Observable(可观察者)类允许在自身发生改变时,通知其它对象(实现接口Observer,观察者)。

应用场景:Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口。使得有各种各样不同的观察者。因此,使用观察者模式可以实现业务与数据逻辑的通知机制。

下面是一个可观察者(产品类):

import java.util.*;

public class product extends Observable{ 

   private String name;产品名

   private float price;价格

   public String getName(){ return name;}

   public void setName(String name){

    this.name=name;

   设置变化点 

    setChanged();

    notifyObservers(name);通知观察者

   }   

   public float getPrice(){ return price;}

   public void setPrice(float price){

    this.price=price;

   设置变化点

    setChanged();

    notifyObservers(new Float(price));

   }

   以下可以是数据库更新 插入命令.

   public void saveToDb(){

   System.out.println("saveToDb");

    }

}

下面是两个观察者:

import java.util.*;

public class NameObserver implements Observer{

   private String name=null;

   public void update(Observable obj,Object arg){

     if (arg instanceof String){

      name=(String)arg;

      产品名称改变值在name

      System.out.println("NameObserver :name changet to "+name);

     }

      }

   }

import java.util.*;

public class PriceObserver implements Observer{

   private float price=0;

   public void update(Observable obj,Object arg){

     if (arg instanceof Float){

          price=((Float)arg).floatValue();

          System.out.println("PriceObserver :price changet to "+price);

     }

   }

}

下面是测试类:

public class Test {

    public static void main(String args[]){

        Product product=new Product();

        NameObserver nameobs=new NameObserver();

        PriceObserver priceobs=new PriceObserver();

        加入观察者

        product.addObserver(nameobs);

        product.addObserver(priceobs);

        product.setName("applet");

        product.setPrice(9.22f);

  }

}

3.15 迭代子模式

迭代子模式又称Cursor(游标)模式,是对象行为模式。迭代子模式可以顺序地访问聚集中的对象而不必显露聚集的内部表象。

迭代子模式的作用是访问一个聚集,因此它需要包含两类对象。

聚集对象:聚集对象中提供了一系列的数据集合,它还需要提供访问该对象中元素的方法,以供迭代器使用。

迭代器对象:它提供了迭代聚集对象的功能,根据需要可以前移、后移、首、尾的各种方法。

 

迭代子的作用就是用来方便地查询一个集合中的数据。凡是有聚集数据存在的地方,都可以使用迭代子模式进行数据迭代。

使用迭代子模式的优点:

1.迭代子模式简化了聚集的界面;

2.因为每一个聚集对象可以有多个迭代子对象,每个迭代子状态是独立的;

3.由于遍历算法被封装在迭代子角色里面,因此迭代的算法可以独立于聚集对象。

但是,迭代子模式给客户端一个聚集被顺序化的感觉,而且它给出的聚集元素没有类型特征。

 

3.16 责任链模式 

很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这可以使系统在不影响客户端的情况下动态地重新组织链和分配责任。

责任链包括3种对象:

1.责任链接口Handler:定义了操作的接口方法。

2.责任链抽象类AbstractHandler:定义一个Handler对象的应用,以设定和返回下家的引用。

3.责任链具体类MyHandler:具体类实现了上面的接口并将继承抽象类,在操作函数中取得下一个引用对象,可以选择将其处理掉,也可以将请求传给下一家。由于处理者持有下家引用。因此,如果需要,具体类可以访问下家。

实例:

public interface Handler {

public void operation();

}

public abstract class AbstractHandler {

        //定义责任链对象

private Handler chain;

public Handler getChain() {

return chain;

}

public void setChain(Handler chain) {

this.chain = chain;

}

}

public class MyHandler extends AbstractHandler implements Handler {

private String name;

public MyHandler(String name) {

this.name = name;

}

        //操作并调用责任链对象

public void operation() {

System.out.println(name + "处理代码");

if (getChain() != null) {

getChain().operation();

}

}

}

public class Test {

public static void main(String[] args) {

MyHandler handler1 = new MyHandler("handler1");

MyHandler handler2 = new MyHandler("handler2");

//设置链接

handler1.setChain(handler2);

MyHandler handler3 = new MyHandler("handler3");

// 设置链接

handler2.setChain(handler3);

handler1.operation();

}

}

应用场景:

责任链模式降低了请求的发送端与接收端之间的耦合,使多个对象有机会处理这个请求。一个链可以是一条线,一个树,也可以是一个环。责任链模式要求在同一时间内,命令只允许传给下一家或处理掉,而不允许传给多家。

 

3.17 命令模式

命令模式

是一种对象行为模式,它主要解决的问题:在软件构建过程中,行为发起者和行为实现者之间的紧耦合问题。它将一个发起者请求封装成一个对象,将发起者与执行者分开来,通过命令的方式来实现。

实例:

public class Invoker {

// 命令

private Command command;

public Invoker(Command command) {

this.command = command;

}

//执行命令

public void action() {

command.execute();

}

}

public class Receiver {

public void action() {

System.out.println("执行命令");

}

}

public interface Command {

public void execute();

}

public class MyCommand implements Command {

private Receiver receiver;

public MyCommand(Receiver receiver) {

this.receiver = receiver;

}

public void execute() {

receiver.action();

}

}

public class Test {

public static void main(String[] args) {

Receiver receiver = new Receiver();

Command command = new MyCommand(receiver);

Invoker invoker = new Invoker(command);

invoker.action();

}

}

应用场景: 

命令模式的根本目的在于将行为请求者和行为实现者解耦,在面向对象语言中,常见的实现手段是"将行为抽象为对象"。因此,凡是需要将调用和实现分开处理的情况都可以使用命令模式。比如:需要将表现层与业务层分开实现,在表现层负责展示界面,通过命令模式来实现对业务层的调用。这也是命令模式典型的应用场景。

 

3.18 备忘录模式

备忘录模式

主旨是存储一个对象的快照,即在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以在以后将对象恢复到原先保存的状态。

包括3个重要角色:

备忘录类Memento:备忘录用来存储原始类的内部状态,原始类根据需要决定备忘录存储原始类的哪些内部状态,防止原始类以外的其他对象访问备忘录。

原始类ClassA:创建一个备忘录对象,用以记录当前时刻它的内部状态,使用备忘录恢复内部状态。

备忘录存储类ClassB:只负责保存好备忘录,不能对备忘录的内容进行操作和检查。

简单总结一下: ClassA 把信息放到备忘录对象Memento中,然后由ClassB负责存储,需要恢复信息的时候再通过ClassB取出备忘录对象ClassB,然后由ClassA获取原始信息。

实例:

public class ClassA {

private String value;

public ClassA(String value) {

this.value = value;

}

public String getValue() {

return value;

}

public void setValue(String value) {

this.value = value;

}

public Memento createMemento() {

return new Memento(value);

}

public void restoreMemento(Memento memento) {

this.value = memento.getValue();

}

}

public class Memento {

private String value;

public Memento(String value) {

this.value = value;

}

public String getValue() {

return value;

}

public void setValue(String value) {

this.value = value;

}

}

public class ClassB {

private Memento memento; 

public ClassB(Memento memento) {

this.memento = memento;

}

public Memento getMemento() {

return memento;

}

public void setMemento(Memento memento) {

this.memento = memento;

}

}

public class Test {

public static void main(String[] args) {

ClassA classa = new ClassA("张三");

// 创建备忘录

ClassB classb = new ClassB(classa.createMemento());

// 修改自身状态

classa.setValue("李四");

// 恢复备忘录

classa.restoreMemento(classb.getMemento());

}

}

应用场景:

一个对象在执行一系列的操作过程中,可能会为了防止因操作失败而致使前面所做的一切白费或是其他原因需要对执行过程的状态进行保存。然而该复杂对象的状态中的内容又不希望被除它之外的任何对象修改(即使该状态可能会保存在外部对象中),这个时候需要使用备忘录模式。

Java中典型应用:SessionApplication

3.19 状态模式

状态模式的核心思想:在对象改变的时候同时改变其行为。例如:改变QQ在线状态可以做不同的事情,在线的时候可以收发文件。在状态模式中,状态是核心,因为状态的改变而引起的行为改变则是状态模式的用意所在。

状态模式包含两个对象:

状态类State:它自身包含了状态变量value,并提供了根据状态变量的不同值进行切换的操作函数。

状态切换类Context:它负责根据State的不同状态来切换不同的调用。

实例:

public class State {

private String value; 

public String getValue() {

return value;

}

public void setValue(String value) {

this.value = value;

}

public void operation1() {

System.out.println("执行操作1");

}

public void operation2() {

System.out.println("执行操作2");

}

}

public class Context {

private State state;

public Context(State state) {

this.state = state;

}

public State getState() {

return state;

}

public void setState(State state) {

this.state = state;

}

public void operation() {

if (state.getValue().equals("type1")) {

state.operation1();

} else if (state.getValue().equals("type2")) {

state.operation2();

}

}

}

public class Test {

public static void main(String[] args) {

State state = new State();

Context context = new Context(state);

// 设置第一种状态

state.setValue("type1");

context.operation(); 

// 设置第二种状态

state.setValue("type2");

context.operation();

}

}

应用场景:

适用于大量状态的切换,我们常用if elseif else进行状态切换,如果针对状态的这样判断切换反复出现,我们就要联想到是否可以采用状态模式了。

此外,不同属性对应不同对象的行为也要考虑使用状态模式。

 

3.20 访问者模式

访问者模式的核心思想:操作改变,数据结构不变。访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类或类群(即被访问者)增加新的操作(即访问者)而无需为它们进行任何修改。

实例:

public interface Visitor {

public void visit(Subject subject);

}

public class MyVisitor implements Visitor {

public void visit(Subject subject) {

System.out.println("访问了主题:" + subject.getSubject());

}

}

public interface Subject {

public void accept(Visitor visitor);

public String getSubject();

}

public class MySubject implements Subject {

public void accept(Visitor visitor) {

visitor.visit(this);

}

public String getSubject() {

return "这是一个主题";

}

}

public class Test {

public static void main(String[] args) {

Visitor visitor = new MyVisitor();

Subject subject = new MySubject();

subject.accept(visitor);

}

}

从以上VistorSubject之间的关系看,它们两者之间相互依赖:Vistor拥有一个访问Subject对象的方法visit(Subject subject),是主动方;Subject拥有一个接受Vistor访问的方法accept(Vistor vistor),是被动方,并且在接受访问时实现对visit()的调用,即实现了访问。

应用场景:

1) 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作污染这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。

4) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

当系统中存在着固定的数据结构,而有着不同的行为,那么访问者模式也许是个不错的选择。

 

3.21 中介者模式

中介者模式

当多个对象彼此间都有联系的时候,我们就可以应用Mediator将对象间的多对多关系转换为一对多的关系,这样做,可以使各个对象间的耦合松散,统一管理对象间的交互。使用中介者模式就只需要关心各个对象和中介者之间的关系。但也可能使得Mediator对象成为一个系统中的庞然大物,难以维护,毕竟中介者可能集中了太多的责任,所有有关的用户对象都要由它来控制,因此在使用中介者模式的时候要注意控制中介者角色的大小。

实例:

public interface Mediator {

public void createMediator();

public void workAll();

}

//中介者实现类负责管理多个用户对象,并负责真正实现交互操作的内容。

public class MyMediator implements Mediator {

private User user1;

private User user2; 

public User getUser1() {

return user1;

public User getUser2() {

return user2;

}

public void createMediator() {

user1 = new MyUser1(this);

user2 = new MyUser2(this);

}

public void workAll() {

user1.work();

user2.work();

}

}

//用户接口只能与中介者交互,不能与其它用户对象交互,因此它拥有一个Mediator对象。

public abstract class User {

private Mediator mediator; 

public Mediator getMediator() {

return mediator;

}

public User(Mediator mediator) {

this.mediator = mediator;

}

public abstract void work();

}

public class MyUser1 extends User { 

public MyUser1(Mediator mediator) {

super(mediator);

}

public void work() {

System.out.println("User1执行");

}

}

public class MyUser2 extends User {

 

public MyUser2(Mediator mediator) {

super(mediator);

}

 

public void work() {

System.out.println("User2执行");

}

}

/*

User的不同对象进行操作,只需要通过中介者来进行统一的管理,而不需要直接访问用户对象。创建一个Mediator对象,只需要执行workAll()函数即可以实现对所有用户的操作。

*/

public class Test {

public static void main(String[] args) {

Mediator mediator = new MyMediator();

mediator.createMediator();

mediator.workAll();

}

}

应用场景:

一组对象以定义良好但复杂的方式进行通信,产生了混乱的依赖关系,导致对象难以复用。

其实MVC中的controller就是一种Mediator,是UI层和后端应用sevice层间的中介者。中介者将交互的复杂性变为中介者的复杂性。

 

3.21 解释器模式

解释器模式是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言而言,用来解释预先定义的文法。简单地说,解释器模式是一种简单的语法解释器架构。

解释器模式包括以下3类对象:

上下文环境Context:用来存储解释器的上下文环境,比如需要解释的文法等。

解释器接口Expression:定义了语法解释器的接口。

解释器具体实现类:对各种语法进行解释的具体实现,如加法、减法、乘法、除法。

实例:

public class Context {

private int num1;

private int num2; 

public Context(int num1, int num2) {

this.num1 = num1;

this.num2 = num2;

public int getNum1() {

return num1;

}

public void setNum1(int num1) {

this.num1 = num1;

}

public int getNum2() {

return num2;

}

public void setNum2(int num2) {

this.num2 = num2;

}

}

public interface Expression {

public int interpret(Context context);

}

public class Plus implements Expression {

public int interpret(Context context) {

return context.getNum1() + context.getNum2();

}

}

public class Minus implements Expression {

public int interpret(Context context) {

return context.getNum1() - context.getNum2();

}

}

public class Multiply implements Expression {

public int interpret(Context context) {

return context.getNum1() * context.getNum2();

}

}

public class Devide implements Expression {

public int interpret(Context context) {

return context.getNum1() / context.getNum2();

}

}

public class Test {

public static void main(String[] args) {

// 计算:(10 + 5 - 3) * 2 / 6 = 4

int result = 

new Devide().interpret(new Context(

new Multiply().interpret(new Context(

new Minus().interpret(new Context(

new Plus().interpret(new Context(10, 5)),

3)),

2)),

6));

System.out.println("(10 + 5 - 3) * 2 / 6 = " + result);

}

}

应用场景:

解释器模式描述了如何构造一个简单的语言解释器,主要应用在使用面向对象语言开发编译器中。在实际应用中,我们可能很少去构造一个语言的文法。Java中典型应用是正则表达式解释器Pattern

 

 

追女生与23种设计模式

下面用轻松的语言比喻了java32种模式,有很好的启发作用,但可惜没有给出具体的意思,我就在后边加上了。这些都是最简单的介绍,要学习的话建议你看一下阎宏博士的《Java与模式》一书。 

  创建型模式

   1FACTORY―MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说来四个鸡翅就行了。麦当劳和肯德基就是生产鸡翅的Factory

   工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

   2BUILDER―MM最爱听的就是我爱你这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到MM我只要按对应的键,它就能够用相应的语言说出我爱你这句话了,国外的MM也可以轻松搞掂,这就是我的我爱你”builder。(这一定比美军在伊拉克用的翻译机好卖)

   建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。

   3FACTORY METHOD―MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说要一个汉堡,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。

   工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

   4PROTOTYPE―MMQQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。(100块钱一份,你要不要)

   原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。

   5SINGLETON―俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道老公,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事)

   单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的单一实例的需求时才可使用。

  结构型模式

   6ADAPTER―在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)

   适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。

   7BRIDGE―早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我早上碰到MM新做了个发型怎么说这种问题,自己用BRIDGE组合一下不就行了

   桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。

   8COMPOSITE―Mary今天过生日。我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”MM都会用Composite模式了,你会了没有?

   合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。

   9DECORATOR―Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?

   装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

   10FACADE―我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。

   门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

   11FLYWEIGHT―每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是FlyweightMM的名字就是提取出来的外部特征,根据上下文情况使用。

   享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

   12PROXY―MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。

   代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

  行为模式

   13CHAIN OF RESPONSIBLEITY―晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!

   责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接

   起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

   14COMMAND―俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。

   命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

   15INTERPRETER―俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了。

   解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。

   16ITERATOR―我爱上了Mary,不顾一切的向她求婚。

   Mary想要我跟你结婚,得答应我的条件

   我:什么条件我都答应,你说吧

   Mary我看上了那个一克拉的钻石

   我:我买,我买,还有吗?

   Mary我看上了湖边的那栋别墅

   我:我买,我买,还有吗?

   Mary我看上那辆法拉利跑车

   我脑袋嗡的一声,坐在椅子上,一咬牙:我买,我买,还有吗?

   ……

   迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。

   17MEDIATOR―四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。

   调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。

   18MEMENTO―同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。

   备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

   19OBSERVER―想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦

   观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

   20STATE―MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说有事情啦,对你不讨厌但还没喜欢上的MM就会说好啊,不过可以带上我同事么?,已经喜欢上你的MM就会说几点钟?看完电影再去泡吧怎么样?,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。

   状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

   21STRATEGY―跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。

   策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

   22TEMPLATE METHOD――看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现)

   模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

   23VISITOR―情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;

   访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值