码迷,mamicode.com
首页 > 其他好文 > 详细

设计模式

时间:2015-02-26 11:11:54      阅读:316      评论:0      收藏:0      [点我收藏+]

标签:

一、设计模式基本原则

1.1、单一职责原则

      单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因。

      如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。

      软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。

1.2、里氏替换原则

      里氏替换原则,就是所有引用基类的地方必须能透明地使用其子类的对象。

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

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

1.3、依赖倒置原则

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

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

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

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

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

  • 低层模块尽量都要有抽象类或接口,或者两者都有。
  • 变量的声明类型尽量是抽象类或接口。
  • 使用继承时遵循里氏替换原则。

1.4、接口隔离原则

      定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

      问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

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

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

  • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

1.5、迪米特法则

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

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

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

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

1.6、开闭原则

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

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

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

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

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

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

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

      参考:http://blog.csdn.net/zhengzhb/article/details/7296944

二、创建型模式

2.0、简单工厂模式

      考虑编写一个运算器,我们可以按照面向过程的方法编写,也可以按照面向对象思想编写,还可以带着设计模式的经验编写:

1、面向过程版本:

public static void operate() {
      double numberA = ...read from console;    
      double numberB = ...read from console;
      String operation = ...read from console;

      switch(operation) {
            case "+":...;
            case "-" :...;
            case "*" :...;
            case "/" :....; 
      }
}

2、面向对象版本:

      以上代码把所有的功能写在了一起,但是我们其实可以把界面(读取操作)与业务(加减乘除)分离:  

public class Operation{
      public static double getResult(double numberA, double numberB, String operation) {
        double result = 0;
        switch(operation) {
            case "+":...;
            case "-" :...;
            case "*":...;
            case "/":...;
        }
        return result;
      }
}    

  客户端读取输入代码:

public static void main() {
      double NumberA = ...read from console;    
      double NumberB = ...read from console;
      String operation = ...read from console;
      double result = Opertion.getResult(numberA, numberB, operation)
}

3、简单工厂模式

      上面代码的问题在于,如果我们想增加一种新运算,那么就需要修改原有代码并重新编译,修改原有代码可能导入新的问题,更好的方法应该是,能够通过增加新的类,实现功能扩展:

public abstract class Operation {
    private double numberA;
    private double numberB;
    
    public abstract double getResult();
    //getters and setters...
}

public class OperationAdd extends Operation {
    public double getResult(){...}
}

public class OperationSub extends Operation {
    public double getResult(){...}
}

public class OperationMul extends Operation {
    public double getResult(){...}
}

public class OperationDiv extends Operation {
    public double getResult(){...}
}

  使用简单工厂创建实例:

public class OperationFactory{
    public static Operation createOperation(String operate) {
        Operation oper = null;
        switch(oper) {
            case "+":...;
            case "-" :...;
            case "*":...;
            case "/":...;
        }
        return oper;
    }
}          

      现在我们想要增加一个新的运算,依然需要修改代码,不同的是我们不需要修改Operation类了,只需要修改OperationFactory即可,当然,我们能连修改OperationFactory也一起避免掉,只需要使用"反射+配置文件"即可。

4、"反射+配置文件"改进简单工厂

配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<operation value="OperationAdd">
<numberA value="12">
<numberB value="13">
</configuration>

  读取配置文件:

public static void main(String[] args) {
      String OperationStr = ...;
      Operation oper = Class.forName(OperationStr);
      NumberA = ...;
      NumberB = ...;
oper.setNumberA();
oper.setNumberB();
oper.getResult(); }

      注意,这里我们使用了“反射+配置文件”的方法避免了增加新功能修改原有代码,下面在介绍工厂方法时,我们会看到另外一种避免修改已有代码的技术。

2.1、Abstract Factory——抽象工厂模式

      抽象工厂模式,为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。抽象工厂模式的类图如下:

技术分享

      抽象工厂模式是“工厂族设计模式”中最复杂的,建议学习顺序为简单工厂模式->工厂方法模式->抽象工厂模式,抽象工厂模式与工厂方法模式的区别是,抽象工厂模式引入了产品族概念,如果只有一个产品那么优先使用工厂方法模式,如果有多个产品,那么可以使用抽象工厂模式。 

2.2、 Builder——建造者模式

      建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。当我们需要以相同的构造过程创建不同的“变象”时,我们就可以使用建造者模式。建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同内部表象的产品对象。建造者模式的类图如下:

技术分享

      从类图中可以看到,该模式的关键是Director对象,该对象封装了产品的一致生成过程,当我们需要生成不同的产品时,只需要传入不同的ConcreteBuilder即可。建造者模式的基本代码如下:

class Prouct {
    List<String> parts = new  ArrayList<String>();
    public void add(String part) {
        parts.add(part);
    }
    public void show(){...}
}    

abstract class Builder{
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract Product getResult();
}

class concreteBuilder1 extends Build {
    Product product = new Product();
    public void buildPartA() {product.add("部件A");}
    public void buildPartB() {product.add("部件B");}
    public void getResult() {return product;}
}

class concreteBuilder2 extends Build {
    Product product = new Product();
    public void buildPartA() {product.add("部件X");}
    public void buildPartB() {product.add("部件Y");}
    public void getResult() {return product;}
}

class Director{
    public void constructor(Builder builder) {
        builder.buildPartA();
        builder.buildPartB();
    }
}

class Main() {
    public static void main(String[] args) {
         Director director = new Director();
         Builder b1 = new ConcreteBuilder1();
         Builder b2 = new ConcreteBuilder2();
        
         director.construct(b1);
         Product p1 = b1.getResult();
         p1.show();

         director.construct(b2);
         Product p2 = b2.getResult();
         p2.show();
    }
}

2.3、Factory Method——工厂方法模式

      在2.0节的第三部分,我们介绍了简单工厂模式,第三部分的代码问题在于,如果想要增加一个新的运算,依然需要修改代码,在第四部分我们使用“反射+配置文件”的方法避免了这个问题,这一节将介绍另一种设计模式,这种设计模式也能避免这个问题,它就是工厂方法,该模式的类图如下:

技术分享

      工厂方法,定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到器之类。

      还是以2.0节的计算器为例,Operation类的代码依然不变:

public abstract class Operation {
    private double numberA;
    private double numberB;
    
    public abstract double getResult();
    //getters and setters...
}

public class OperationAdd extends Operation {
    public double getResult(){...}
}

public class OperationSub extends Operation {
    public double getResult(){...}
}

public class OperationMul extends Operation {
    public double getResult(){...}
}

public class OperationDiv extends Operation {
    public double getResult(){...}
}

  现在要修改的是工厂类,在工厂方法模式下,我们不是只创建一个工厂类,而是会对加减乘除每个运算各创建一个工厂类:

interface IFactory{
    Operation createOperation();
}

class AddFactory implements IFactory {
    Operation createOperation(){
        return new OperationAdd();
    }
}
class SubFactory implements IFactory{...}
class MulFactory implements IFactory{...}
class DivFactory implements IFactory{...}

      现在,我们要增加一个新的运算,不需要修改原有代码,只需要继承新的Operation以及新的Factory即可,就不需要修改现有代码了。

2.4、Prototype——原型模式

      原型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

      原型模式通过clone操作,避免了对象实例化操作。在Java中,Clone接口实现了原型模式。

2.5、Singleton——单例模式

      单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

三、结构型模式

3.1、Adapter——适配器模式

      适配器模式,将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

      系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,适配器模式的类图如下:

技术分享

3.2、Bridge——桥接模式

      桥接模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式的类图如下:

技术分享

      桥接模式所说的“将抽象部分与它的实现部分分离”并不是很好理解,我们可以这样理解,实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少他们之间的耦合。

      举一个生活中的例子:就拿汽车在路上行驶的来说。即有小汽车又有公共汽车,它们都不但能在市区中的公路上行驶,也能在高速公路上行驶。这你会发现,对于交通工具(汽车)有不同的类型,然而它们所行驶的环境(路)也在变化,在软件系统中就要适应两个方面的变化?怎样实现才能应对这种变化呢?

      传统的做法是通过类继承实现的,uml图如下:

技术分享

      但是我们说这样的设计是脆弱的,仔细分析就可以发现,它还是存在很多问题,首先它在遵循开放-封闭原则的同时,违背了类的单一职责原则,即一个类只有一个引起它变化的原因,而这里引起变化的原因却有两个,即路类型的变化和汽车类型的变化;其次是重复代码会很多,不同的汽车在不同的路上行驶也会有一部分的代码是相同的;再次是类的结构过于复杂,继承关系太多,难于维护,最后最致命的一点是扩展性太差。如果变化沿着汽车的类型和不同的道路两个方向变化,我们会看到这个类的结构会迅速的变庞大。

      在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。

      使用Bridge模式实现的uml图如下:

技术分享

技术分享

      类代码如下:

public abstract class AbstractRoad {
	protected AbstractCar car;
	public abstract void Run();
	public void setCar(AbstractCar car) {
		this.car = car;
	}
}
public class SpeedWay extends AbstractRoad {
	@Override
	public void Run() {
		car.Run();
		System.out.println("高速公路上行驶");
	}
}
public class Street extends AbstractRoad {
	@Override
	public void Run() {
		 car.Run();
		 System.out.println("市区街道上行驶");
	}
}


public abstract class AbstractCar {
	public abstract void Run();
}
public class Bus extends AbstractCar{
	@Override
	public void Run() {
		System.out.print("公共汽车在");
	}
}
public class Car extends AbstractCar{
	@Override
	public void Run() {
		System.out.print("小汽车在");
	}
}


public class Run {
	public static void main(String[] args) {
		// 小汽车在高速公路上行驶;
		AbstractRoad Road1 = new SpeedWay();
		Road1.car = new Car();
		Road1.Run();
		
		System.out.println("=========================");

		// 公共汽车在高速公路上行驶;
		AbstractRoad Road2 = new SpeedWay();
		Road2.car = new Bus();
		Road2.Run();
	}
}

  可以看到,通过对象组合的方式,Bridge 模式把两个角色之间的继承关系改为了耦合的关系,从而使这两者可以从容自若的各自独立的变化,这也是Bridge模式的本意。
      这样增加了客户程序与路与汽车的耦合。其实这样的担心是没有必要的,因为这种耦合性是由于对象的创建所带来的,完全可以用创建型模式去解决。在应用时结合创建型设计模式来处理具体的问题。

      参考:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html。

3.3、Composite——组合模式

       组合模式(Composite),将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

      组合模式uml图如下:

技术分享

      我们一般在当发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式。对于组合模式的实现分为透明方式和安全方式。      

     (1)透明方式:在Component中声明所有用来管理对象的方法,其中包括Add(),Remove()等。这样实现Component接口的所有子类都具备了Add(),Remove()。这样做的好处是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类本身不具备Add(),Remove()方法的功能,所以实现它没有意义。

    (2)安全方式:在Component接口中不去声明Add(),Remove()方法,那么子类Leaf就不需要实现它,而是在Composite声明所有用来管理子类对象的方法,但是这样做由于不够透明,所以树叶和树枝类将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。

 

3.4、Decorator——装饰者模式

      装饰模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活。

      技术分享

      装饰模式利用setComponent()方法进行包装,这样每个对象的实现和使用该对象得到了分离,每个装饰对象只需要关心自己的功能,不需要涉及如何添加到装饰链。

      装饰模式是一个为已有功能添加更多功能提供便利的方式,当系统需要新的功能时,只需要向旧的类中添加新的功能代码,这些代码通常装饰了原有类的核心职责或主要行为,体现了开放封闭原则,即对扩展开放对修改封闭。     

3.5、Facade——外观模式

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

      外观模式UML类图:

技术分享

       如上图所示为外观模式结构图,其中Facade类即外观类,它需要了解所有的子系统的方法或属性,进行组合以备外界调用,它知道哪些子系统类负责处理请求,将客户的请求代理给适当的子系统对象。SubSystem classes为子系统类集合,实现子系统功能,处理Facade对象指派的任务,注意各子类中没有Facade的任何信息,即没有对Facade对象的引用。

3.6、Flyweight——享元模式

      享元模式(FlyWeight),运用共享技术有效的支持大量细粒度的对象。也就是说当项目中需要应用大量相同或相似的功能代码时,那么对于硬盘、内存、CPU、数据库空间的等服务器资源均可达到共享,以减少服务器资源,提高利用率。

      技术分享

      如上图所示,FlyWeight类,它是所有具体享元类的超类或者接口,通过这个接口,FlyWeight可以接受并作用于外部状态;ConcreteFlyWeight是继承FlyWeight超类或者实现FlyWeight接口,并为内部状态增加存储空间;UnsharedConcreteFlyWeight是指那些不需要共享的FlyWeight子类。因为FlyWeight接口共享成为可能,但它并不强制共享;FlyWeightFactory是一个享元工厂,用来创建并管理FlyWeight对象,它主要是用来确保合理地共享FlyWeight,当用户请求一个flyWeight时,FlyWeightFactory对象提供一个已创建的实例或者创建一个(如果没有的话)。

      在享元对象内部并且不会随环境改变而改变的共享部分,可以称之为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式FlyWeight执行时所需要的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyWeight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用FlyWeight对象的操作时,将该状态传递给它。

       如果一个应用程序使用了大量的对象,而大量的这些对象造成了存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑用享元模式。

      下面介绍一个网站共享的案例,案例来自《大话设计模式》: 

public class User {
	public String name;

	public User(String name) {
		this.name = name;
	}
}

public abstract class WebSite {
	public abstract void use(User user);//use方法需要传递User对象,User对象就属于外部状态
}

public class ConcreteWebSite extends WebSite {
	private String name = "";

	public ConcreteWebSite(String name) {
		this.name = name;
	}

	public void use(User user){
		System.out.println("网站分类:" + name + "  用户:" + user.name);
	}
}

//工厂类,控制对象的创建
public class WebSiteFactory {
	private HashMap<String, WebSite> flyweights = new HashMap();

	public WebSite getWebSite(String key) {
		if (!flyweights.containsKey(key))
			flyweights.put(key, new ConcreteWebSite(key));
		return ((WebSite) flyweights.get(key));
	}

	public int getWebSiteCount() {
		return flyweights.size();
	}
}

public class Run {
	public static void main(String[] args) {
		WebSiteFactory wsf = new WebSiteFactory();

		WebSite ws1 = wsf.getWebSite("产品展示");
		ws1.use(new User("小菜"));

		WebSite ws2 = wsf.getWebSite("产品展示");
		ws2.use(new User("大鸟"));

		WebSite ws3 = wsf.getWebSite("产品展示");
		ws3.use(new User("娇娇"));

		WebSite ws4 = wsf.getWebSite("博客");
		ws4.use(new User("老顽童"));

		WebSite ws5 = wsf.getWebSite("博客");
		ws5.use(new User("南海鳄神"));

		WebSite ws6 = wsf.getWebSite("博客");
		ws6.use(new User("桃谷六仙"));

		System.out.println("得到网站分类总数为:" + wsf.getWebSiteCount());
	}
}

 

3.7、Proxy——代理模式

      代理模式,为其他对象提供一种代理以控制对这个对象的访问。而对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。它是给某一个对象提供一个替代者(占位者),使之在client对象和subject对象之间编码更有效率。代理可以提供延迟实例化(lazy instantiation),控制访问, 等等,包括只在调用中传递。 一个处理纯本地资源的代理有时被称作虚拟代理。远程服务的代理常常称为远程代理。强制 控制访问的代理称为保护代理。

      代理模式的uml图如下:

技术分享

      在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用 Proxy模式。下面是一些可以使用Proxy模式常见情况:
        1) 远程代理(Remote  Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
        2) 虚拟代理(Virtual Proxy)根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 
        3) 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
        4) 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。
        5) Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

      参考:http://blog.csdn.net/hguisu/article/details/7542143。

4、行为模式

4.1、Chain Of responsibility——职责链模式

      职责链模式(Chain Of Responsibility),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

     这里发出这个请求的客户端并不知道这当中的哪一个对象最终处理该请求,这样系统的更改可以在不影响客户端的情况下动态的重新组织和分配责任。

职责链模式UML类图:

技术分享

      如上图所示:Handler类,定义一个处理请示的接口;ConcreteHandler 类,具体处理者类,处理它所负责的请求,可访问它的后继者,如果可处理该请求就处理之,否则就将该请求转发给它的后继者;客户端代码,向链上的具体处理者对象提交请求。

职责链模式应用案例—小菜加薪:

public class Request {
    public String requestType;  
    public String requestContent;  
    public int number;  
}

public abstract class Manager {
	protected String name;
	protected Manager superior;

	public Manager(String name) {
		this.name = name;
	}

	public void setSuperior(Manager superior) {
		this.superior = superior;
	}

	public abstract void requestApplication(Request request);
}

public class CommonManager extends Manager {
	public CommonManager(String name) {
		super(name);
	}

	public void requestApplication(Request request) {
		if (request.requestType == "请假" && request.number <= 2) {
			System.out.println(name + ":" + request.requestContent + ",数量:"
					+ request.number + "被批准。");
		} else if (superior != null) {
			superior.requestApplication(request);
		}
	}
}

public class MajorDomo extends Manager {
	public MajorDomo(String name) {
		super(name);
	}

	@Override
	public void requestApplication(Request request) {
		if (request.requestType == "请假" && request.number <= 5) {
			System.out.println(name + ":" + request.requestContent + ",数量:"
					+ request.number + "被批准。");
		} else if (superior != null) {
			superior.requestApplication(request);
		}
	}
}

public class GeneralManager extends Manager {
	public GeneralManager(String name) {
		super(name);
	}

	@Override
	public void requestApplication(Request request) {
		if (request.requestType == "请假") {
			System.out.println(name + ":" + request.requestContent + ",数量:"
					+ request.number + "被批准。");
		} else if (request.requestType == "加薪" && request.number <= 500) {
			System.out.println(name + ":" + request.requestContent + ",数量:"
					+ request.number + "被批准。");
		} else if (request.requestType == "加薪" && request.number > 500) {
			System.out.println(name + ":" + request.requestContent + ",数量:"
					+ request.number + "再说吧。");
		}
	}
}

public class Run {
	public static void main(String[] args) {
		CommonManager commonManager = new CommonManager("经理");
		MajorDomo majorDomo = new MajorDomo("总监");
		GeneralManager generalManager = new GeneralManager("总经理");

		commonManager.setSuperior(majorDomo);
		majorDomo.setSuperior(generalManager);

		Request request1 = new Request();
		request1.requestType = "请假";
		request1.requestContent = "小菜请假";
		request1.number = 1;

		commonManager.requestApplication(request1);

		Request request2 = new Request();
		request2.requestType = "请假";
		request2.requestContent = "小菜请假";
		request2.number = 3;

		commonManager.requestApplication(request2);

		Request request3 = new Request();
		request3.requestType = "请假";
		request3.requestContent = "小菜请假";
		request3.number = 6;

		commonManager.requestApplication(request3);

		Request request4 = new Request();
		request4.requestType = "加薪";
		request4.requestContent = "小菜请求加薪";
		request4.number = 300;

		commonManager.requestApplication(request4);

		Request request5 = new Request();
		request5.requestType = "加薪";
		request5.requestContent = "小菜请求加薪";
		request5.number = 1000;

		commonManager.requestApplication(request5);
	}
}

  职责链模式中最关键的就是当客户提交一个请求时,请求时沿链传递直至有一个ConcreteHandler对象负责处理它为止.这就使得接收者和发送者都没有对方的明确信息,且链中的对象自己也不知道链的结构。职责链可简化对象的相互链接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。职责链模式使得可以随时的增加或修改处理一个请求的结构增强了给对象指派职责的灵活性。

4.2、Command——命令模式

      命令模式(Command),将一个请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式UML类图:

技术分享

      如上图所示:Command类是用来声明执行操作的接口;ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Execute;Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者;Invoker类,要求该命令执行这个命令。

命令模式案例—烧烤摊:

public class Barbecuer {/* 烤肉串者类 */
	public void BakeMutton() {
		System.out.println("烤羊肉串...");
	}

	public void BackChickenWing() {
		System.out.println("烤鸡翅...");
	}
}

public abstract class Command {
	protected Barbecuer receiver; // 命令接受者

	public Command(Barbecuer receiver) { // 抽象命令类,只需要确定“烤肉串者”是谁
		this.receiver = receiver;
	}
	
	public abstract void ExcuteCommand();// 执行命令
}

public class BakeMuttonCommand extends Command {
	public BakeMuttonCommand(Barbecuer receiver) {
		super(receiver);
	}

	@Override
	public void ExcuteCommand() {
		receiver.BakeMutton();
	}
}

public class BackChickenWingCommand extends Command {
	public BackChickenWingCommand(Barbecuer receiver) {
		super(receiver);
	}

	@Override
	public void ExcuteCommand() {
		this.receiver.BackChickenWing();
	}
}

public class Waiter {
	private List<Command> orders = new ArrayList<Command>();

	public void SetOrder(Command command) {// 设置订单
		if (command.getClass().toString().equals("class command.BackChickenWingCommand")) {
			System.out.println("服务员:鸡翅没有了,请点别的烧烤...");
		} else {
			orders.add(command);
			System.out.println("增加订单:" + command.getClass().toString() + "时间:"
					+ new Date().toString());
		}
	}

	public void CancelOrder(Command command) {// 取消订单
		orders.remove(command);
		System.out.println("取消订单:" + command.getClass().toString() + "时间:"
				+ new Date().toString());
	}

	public void Notify() {// 通知全部执行
		for (Command c : orders) {
			c.ExcuteCommand();
		}
	}
}

public class Run {
	public static void main(String[] args) {
		// 开店前的准备
		Barbecuer boy = new Barbecuer();
		Command bakeMuttonCommand = new BakeMuttonCommand(boy);
		Command bakeChickenCommand = new BackChickenWingCommand(boy);

		Waiter girl = new Waiter();

		// 开门营业 顾客点菜
		girl.SetOrder(bakeMuttonCommand);
		girl.SetOrder(bakeMuttonCommand);
		girl.SetOrder(bakeChickenCommand);

		// 订单下好后,一次性通知厨房
		girl.Notify();
	}
}

 

命令模式有以下优点:

       第一,命令模式能较容易的设计一个命令队列;

       第二,在需要的情况下,可以较容易的将命令记入日志;

       第三,允许接收请求的一方决定是否要否决该请求;

       第四,可以容易的实现对请求的撤销和重构;

       第五,由于加入新的具体命令类不影响其它的类,因此增加新的具体命令很容易;

       第六,命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

4.3、Interpreter——解释器模式

      解释器模式(Interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

      解释器模式需要解决的问题是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

解释器模式UML类图:

技术分享

 

      如上图所示:AbstractExpression(抽象表达式),声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享;TerminalExpression(终结符表达式),实现与文法中的终结符相关联的解释操作,实现抽象表达式中所要求的 接口,主要是一个interpret()方法,文法中每一个终结符都有一个具体终结表达式与之相对应;NonterminalExpression(非终结符表达式),为文法中的非终结符实现解释操作。对文法中每一条规则R1,R2......Rn 都需要一个具体的非终结符表达式类。通过实现抽象表达式的interpret()方法实现解释操作。解释操作以递归形式调用上面所提到的代表R1,R2......Rn中各个符号的实例变量;Context,包含解释器之外的一些全局信息;客户端代码,构建表示该文法定义的语言中一个特定的句子的抽象语法树。

 

4.4、Iterator——迭代器模式

      迭代器模式,提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

      当你需要访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑使用迭代器模式。另外,当需要对聚集有多种方式遍历时,可以考虑去使用迭代器模式。迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。

      迭代器模式的uml类图如下:

技术分享     

4.5、Mediator——中介者模式

      中介者模式(Mediator),用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。

中介者模式UML类图:

技术分享

 

      由上图可以看出:Mediator 抽象中介者类,定义了同事对象到中介者对象的接口;Colleague抽象同事类;ConcreteMediator具体中介者类,实现抽象类的方法,它需要知道所有的具体同事类,并从同事接收消息,向具体同事对象发出命令;具体同事类,每个具体同事对象只知道自己的行为,而不了解其它同事类的情况,但它们却都认识中介者对象。 ——  总的来说,Mediator持有两个Colleague实例,Colleague不持有Colleague实例,只持有Mediator实例。

中介者模式总结:

      对于中介者模式,该设计模式很容易在系统中应用,也很容易在系统中误用。当系统出现了‘多对多’交互复杂的对象群时,不要急于使用中介者模式,而要先反思你的系统在设计上是不是合理。

      中介者模式的使用有很多优点,首先,Mediator的出现减少了各个Colleague的耦合,使得可以独立的改变和复用各个Colleague类和Mediator;

      其次,由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到了它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。

      然而,由于ConcreteMediator控制了集中化,于是就把交互复杂性变成了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague更复杂。

4.6、Memento——备忘录模式

      备忘录模式(Memento),在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以将该对象状态恢复到原先保存的状态。

备忘录模式UML类图: 

技术分享

      由上图可知,Originator(发起人)类:负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态,Originator可根据需要决定Memento存储Originator的哪些内部状态 。

      Memento(备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其它对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到Memento的窄接口,它只能将备忘录传递给其它对象,Originator看到一个宽接口允许它访问返回到先前状态所需的所有数据。

      Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。

备忘录模式案例——游戏进度备忘 

 

public class GameRole {
	public int vit;// 生命力
	public int atk;// 攻击力
	public int def;// 防御力

	public void InitState() {/* 设置初始状态 */
		this.vit = 100;
		this.atk = 100;
		this.def = 100;
	}

	public void ShowState() {/* 当前状态显示 */
		System.out.println("角色当前状态:");
		System.out.println("生命力:" + this.vit);
		System.out.println("攻击力:" + this.atk);
		System.out.println("防御力:" + this.def);
	}

	public void Fight() {/* 战斗 */
		this.vit = 0;
		this.atk = 0;
		this.def = 0;
	}

	public RoleStateMemento SaveRoleState() {/* 保存角色状态 */
		return new RoleStateMemento(this.vit, this.atk, this.def);
	}

	public void RecoveryState(RoleStateMemento rsm) {/* 恢复角色状态 */
		this.vit = rsm.vit;
		this.atk = rsm.atk;
		this.def = rsm.def;
	}
}

public class RoleStateMemento {
	public int vit;// 生命力
	public int atk;// 攻击力
	public int def;// 防御力

	public RoleStateMemento(int vit, int atk, int def) {
		this.vit = vit;
		this.atk = atk;
		this.def = def;
	}
}

public class RoleStateCaretaker {
	public RoleStateMemento rsm;

	public RoleStateMemento getRsm() {
		return this.rsm;
	}
}

public class Run {
	public static void main(String[] args) {
		// 大战boss前
		System.out.println("大战boss前...");
		GameRole gr = new GameRole();
		gr.InitState();
		gr.ShowState();

		// 保存进度
		RoleStateCaretaker rsc = new RoleStateCaretaker();
		rsc.rsm = gr.SaveRoleState();

		// 大战boss,损耗严重
		System.out.println("大战boss,损耗严重...");
		gr.Fight();
		gr.ShowState();

		// 恢复进度
		System.out.println("恢复进度...");
		gr.RecoveryState(rsc.getRsm());
		gr.ShowState();
	}
}

备忘录模式总结:  

       (1)使用备忘录模式,也就是将要保存的细节给封装在了Memento中,哪一天要更改保存的细节也不用影响客户端了。

      (2)备忘录模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。

      (3)如果在某个系统中使用命令模式时,需要实现命令模式的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。

      (4)在当角色的状态改变的时候有可能这个状态无效,这时,就可以使用暂时存储起来的备忘录将状态复原。

4.7、Observer——观察者模式

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

UML类图:

技术分享

      Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时,更新自己这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个 Update方法,这个方法叫做更新方法。

        Subject类,可翻译为主题或者抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用,保存在一个聚集里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

       ConcreteSubject类叫做具体主题或者具体通知者,将有关状态存入具体观察者对象:在具体主题的内部状态得到改变时,给所有登记过的观察者发出通知。具体主题通常用一个具体子类实现。

       ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口。以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用,具体 观察者角色通常由一个具体子类实现。

观察者模式(Observer)实现:

/* 
 * Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时,更新自己 
 * 这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个 
 * Update方法,这个方法叫做更新方法。 
 */ 
public abstract class Observer {
	public abstract void Update();
}

/* 
 * Subject类,可翻译为主题或者抽象通知者,一般用一个抽象类或者一个接口实现。 
 * 它把所有对观察者对象的引用,保存在一个聚集里,每个主题都可以有任意数量的观察者。 
 * 抽象主题提供一个接口,可以增加和删除观察者对象。 
 */
public abstract class Subject {
	private List<Observer> observers = new ArrayList<Observer>();

	// 增加观察者
	public void Attach(Observer observer) {
		observers.add(observer);
	}

	// 移除观察者
	public void Detach(Observer observer) {
		observers.remove(observer);
	}

	// 通知
	public void Notify() {
		for (Observer o : observers) {
			o.Update();// 通知到每一个观察者,使得每一个观察者修改自身状态
		}
	}
}

/* 
 * ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口。以便使本身 
 * 的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用,具体 
 * 观察者角色通常由一个具体子类实现。 
 */ 
public class ConcreteObserver extends Observer {
	private String name;
	private String observerState;
	private ConcreteSubject cSubject;

	public ConcreteObserver(ConcreteSubject cSubject, String name) {
		this.cSubject = cSubject;
		this.name = name;
	}

	public ConcreteSubject getCSubject() {
		return this.cSubject;
	}

	public void setCSubject(ConcreteSubject cSubject) {
		this.cSubject = cSubject;
	}

	public void Update() {
		this.observerState = this.cSubject.getSubjectState();
		System.out.println("观察者" + this.name + "的状态是" + this.observerState);
	}
}

/* 
 * ConcreteSubject类叫做具体主题或者具体通知者,将有关状态存入具体观察者对象: 
 * 在具体主题的内部状态得到改变时,给所有登记过的观察者发出通知。具体主题通常 
 * 用一个具体子类实现。 
 */
public class ConcreteSubject extends Subject {
	private String subjectState;

	public String getSubjectState() {
		return this.subjectState;
	}

	public void setSubjectState(String state) {
		this.subjectState = state;
	}
}

public class Run {
	public static void main(String[] args) {
		ConcreteSubject cSubject = new ConcreteSubject();

		cSubject.Attach(new ConcreteObserver(cSubject, "X"));
		cSubject.Attach(new ConcreteObserver(cSubject, "Y"));
		cSubject.Attach(new ConcreteObserver(cSubject, "Z"));

		cSubject.setSubjectState("ABC");
		cSubject.Notify();
	}
}

  

4.8、State——状态模式

      状态模式(State),当一个对象的内在状态改变时,允许改变其行为,这个对象看起来好像改变了其类。状态模式主要解决的是当控制一个对象的状态转换条件表达式过于复杂时的情况。把状态的判断逻辑转移到不同状态的一系列类中,可以把复杂的判断逻辑简化。

状态模式UML类图: 

技术分享

由上图可知:

(1)State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为;

(2)ConcreteState类,具体状态,每一个子类实现一个与Context的一个特定状态相关的行为;

(3)Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态

状态模式案例—工作状态

public abstract class State {
	public abstract void writeProgram(Work work);
}

public class ForenoonState extends State {
	public void writeProgram(Work work) {
		if (work.hour < 12) {
			System.out.println("当前时间" + work.hour + ",上午状态,精神百倍!");
		} else {
			work.state = new NoonState();
			work.writeProgram();
		}
	}
}

public class NoonState extends State {
	public void writeProgram(Work work) {
		if (work.hour < 13) {
			System.out.println("当前时间" +  work.hour + ",午饭时间!");
		} else {
			work.state = new AfterNoonState();
			work.writeProgram();
		}
	}
}

public class AfterNoonState extends State {
	public void writeProgram(Work work) {
		if (work.hour < 17) {
			System.out.println("当前时间" + work.hour + ",下午状态,继续工作!");
		} else {
			work.state = new EveningState();
			work.writeProgram();
		}
	}
}

public class EveningState extends State {
	public void writeProgram(Work work) {
		if (work.isFinish) {
			work.state = new RestState();
			work.writeProgram();
		} else {
			if (work.hour < 21) {
				System.out.println("当前时间" + work.hour + ",加班状态,疲惫!");
			} else {
				work.state = new SleepState();
				work.writeProgram();
			}
		}
	}
}

public class RestState extends State {
	public void writeProgram(Work work) {
		System.out.println("当前时间" + work.hour + ",完成工作,下班回家!");
	}
}

public class SleepState extends State {
	public void writeProgram(Work work) {
		System.out.println("当前时间" + work.hour + ",睡觉状态!");
	}
}

public class Work {
	public double hour;
	public State state;
	public boolean isFinish;

	public Work(State state) {
		this.state = state;
	}

	public void writeProgram() {
		this.state.writeProgram(this);
	}
}

public class Run {
	public static void main(String[] args) {
		Work work = new Work(new ForenoonState());
		work.hour = 9;
		work.writeProgram();
		work.hour = 10;
		work.writeProgram();
		work.hour = 12;
		work.writeProgram();
		work.hour = 13;
		work.writeProgram();
		work.hour = 15;
		work.writeProgram();
		work.hour = 17;
		work.writeProgram();

		work.isFinish = false;
		work.hour = 19;
		work.writeProgram();
		work.hour = 21;
		work.writeProgram();
		work.hour = 22;
		work.writeProgram();
	}
}

  状态模式总结: 

       (1)状态模式的好处就是将于特定状态相关的行为局部化,并且将不同状态的行为分割开来。意即,将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。

       (2)这样做的目的是为了消除庞大的条件分支语句,大的分支判断会使得它们难以修改和扩展,状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。

       (3)当一个对象的行为取决于它们的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。

4.9、Strategy——策略模式

      策略模式(Strategy)定义了一个算法家族,对每个算法分别封装成为一个单独的类,让他们之间可以相互的替换,此模式让算法的变化不会影响到使用算法的客户。

策略模式的UML类图:

技术分享

 

      策略模式可以减少各种算法类与使用算法类之间的耦合。策略模式包含Strategy、context两个模块,Strategy为Context定义一系列可供重用的算法或行为,继承有助于析取出这些算法中的公用功能。

     策略模式就是用来封装算法的,在实践中,我们可以用它来封装几乎封装任何类型的规则,只要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑用策略模式处理这种变化的可能性。

       在策略模式中,选择所用具体实现的职责有客户端负责,然后转给策略模式的Context对象。

策略模式实现商场促销:

public abstract class CashSuper {//促销策略基类
	public abstract double acceptCash(double money);
}

public class CashNormal extends CashSuper{//无促销策略
	public double acceptCash(double money) {
		return money;
	}
}

public class CashRebate extends CashSuper {//打折促销
    private double rebate = 1;
    
	public CashRebate(double rebate) {
		super();
		this.rebate = rebate;
	}

	@Override
	public double acceptCash(double money) {
		return money * rebate;
	}
}

public class CashReturn extends CashSuper {// 满300返100
	private double c = 0;
	private double r = 0;

	public CashReturn(double c, double r) {
		this.c = c;
		this.r = r;
	}

	@Override
	public double acceptCash(double money) {
		double result = money;
		if (money > c)
			result = money - Math.floor(money / c) * r;
		return result;
	}
}

public class CashContext {
	public CashSuper cs;

	public CashContext(CashSuper cs) {// 通过构造函数,传入具体的收费策略
		this.cs = cs;
	}

	public double getResult(double money) {
		return cs.acceptCash(money);
	}
}

public class Run {
	public static void main(String[] args) {
		CashContext cc = new CashContext(new CashReturn(300, 100));
		System.out.println(cc.getResult(400));
	}
}

      分析对比策略模式和简单工厂设计模式,我们可以发现,这两个设计模式的用法思路非常相似,唯一的不同之处就是,对于简单工厂设计模式来讲,选择具体实现方法是由工厂类Factory来负责,而在策略模式中是由客户端程序来负责的。因此,为了即达到定以算法家族实现独立算法的目的,又可以不让客户端来负责具体实现的选择,在具体的实践项目中我们可以将简单工厂设计模式与策略模式结合起来使用。

 

4.10、Template Method——模板方法

      模板方法模式(TemplateMethod):定义一个操作中的算法的骨架,而将一些步骤延迟到子类。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

策略模式的UML类图:

     技术分享

       TemplateClass是抽象类,其实也就是一个抽象模板,定义并实现一个模板方法,这个模板方法一般是一个具体的方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现,顶级逻辑也有可能调用一些具体方法;

       ConcreteClass实现父类所定义的一个或者多个抽象方法,每一个TemplateClass都可以有任意多个ConcreteClass与之对应,而每一个ConcreteClass都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同;

模板方法模式特点:

      模板方法模式是通过把不变行为搬移到父类,去除子类中的重复代码来体现它的优势。也就是说模板方法模式就是提供了一个很好的代码复用平台,因为有时候,我们会遇到一系列步骤构成的过程需要执行。这个过程从层次上看是相同的,但有些步骤的实现可能不同,这时候通常就考虑使用模板方法模式。

       碰到这个情况,当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现,我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。

       模板方法模式是一个很常用的模式,对继承和多态有很大帮助,比如在.NET或java类库的设计中,通常都会利用模板方法模式提取类库中的公共行为到抽象类中。

4.11、Visitor——访问者模式

      访问者模式(Visitor),表示一个作用于某对象结构中各元素的操作,它使你可以不改变各元素的类的前提下定义作用于这些元素的新操作。

      访问者模式适合于数据结构相对稳定的系统。它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。

      访问者模式的目的是要把处理从数据结构分离出来,很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。

访问者模式UML类图:

技术分享 

      如上图所示:Visitor类,为该对象结构中ConcreteElement的每一个类声明一个Visit操作;ConcreteVisitor1和ConcreteVisitor2类,具体访问者,实现每个由Visitor声明的操作,每个操作实现算法的一部分,而该算法片断乃是对应于结构中对象的类;Element类,定义一个Accept操作,它以一个访问者为参数;ConcreteElementA和ConcreteElementB类,具体操作实现Accept操作;ObjectStructure类,能枚举它的元素,可以提供一个高层的接口以允许访问它的元素。

访问者模式案例:

public abstract class Person {
	// 用来获得状态对象
	public abstract void Accept(Action action);
}

public class Man extends Person {
	@Override
	public void Accept(Action action) {
		action.GetManConclusion(this);
	}
}

public class Woman extends Person {
	@Override
	public void Accept(Action action) {
		action.GetWomamConclusion(this);
	}
}

/*状态的抽象类*/
public abstract class Action {
	// 得到男人结论或反应
	public abstract void GetManConclusion(Man m);

	// 得到女人结论或反应
	public abstract void GetWomamConclusion(Woman w);
}

public class Fail extends Action {
	@Override
	public void GetManConclusion(Man m) {
		System.out.println("man fail时,闷头喝酒谁也不用劝");
	}

	@Override
	public void GetWomamConclusion(Woman w) {
		System.out.println("woman fail时,眼泪汪汪,谁也劝不了");
	}
}

public class Love extends Action {
	@Override
	public void GetManConclusion(Man m) {
		System.out.println("man success时,凡事不懂也要装懂");
	}

	@Override
	public void GetWomamConclusion(Woman w) {
		System.out.println("woman success时,遇事懂也装不懂");
	}
}

public class Success extends Action{
	@Override
	public void GetManConclusion(Man m) {
		System.out.println("man success时,背后多半有一个成功的女人");
	}

	@Override
	public void GetWomamConclusion(Woman w) {
		System.out.println("woman success时,背后多半有一个不成功的男人");
	}
}

public class ObjectStructure {
	private List<Person> persons = new ArrayList<Person>();

	// 增加
	public void Attach(Person p) {
		persons.add(p);
	}

	// 移除
	public void Detach(Person p) {
		persons.remove(p);
	}

	// 查看显示
	public void Display(Action a) {
		for (Person p : persons) {
			p.Accept(a);
		}
	}
}

public class Run {
	public static void main(String[] args) {
		ObjectStructure os = new ObjectStructure();
		os.Attach(new Man());
		os.Attach(new Woman());

		// 成功时的反应
		Success s = new Success();
		os.Display(s);

		// 失败时反应
		Fail f = new Fail();
		os.Display(f);

		// 恋爱时的反应
		Love l = new Love();
		os.Display(l);
	}
}

访问者模式总结:

      访问者模式优点是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。

      那访问者模式的缺点是是增加新的数据结构变得困难了。

  

 

设计模式

标签:

原文地址:http://www.cnblogs.com/timlearn/p/4279738.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!