Skip to content

Latest commit

 

History

History
3150 lines (2400 loc) · 92.7 KB

设计模式.md

File metadata and controls

3150 lines (2400 loc) · 92.7 KB

Table of Contents

UML类图

五分钟读懂UML类图

平时阅读一些远吗分析类文章或是设计应用架构时没少与UML类图打交道。实际上,UML类图中最常用到的元素五分钟就能掌握,下面赶紧来一起认识一下它吧:

一、类的属性的表示方式

在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和email这3个属性,以及modifyInfo()方法。

img

那么属性/方法名称前加的加号和减号是什么意思呢?它们表示了这个属性或方法的可见性,UML类图中表示可见性的符号有三种:

· + :表示public

· - :表示private

· #:表示protected(friendly也归入这类)

因此,上图中的Employee类具有3个私有属性和一个公有方法。

实际上,属性的完整表示方式是这样的:

可见性 名称 :类型 [ = 缺省值]

中括号中的内容表示是可选的

二、类的方法的表示方式

上图中我们已经看到了方法的表示形式。实际上,方法的完整表示方式如下:

可见性 名称(参数列表) [ : 返回类型]

同样,中括号中的内容是可选的。

比如在下图的Demo类中,定义了3个方法:

img

· public方法method1接收一个类型为Object的参数,返回值类型为void

· protected方法method2无参数,返回值类型为String

· private方法method3接收类型分别为int、int[]的参数,返回值类型为int

三、类与类之间关系的表示方式

1、关联关系

关联关系又可进一步分为单向关联、双向关联和自关联。

(1)单向关联

img

我们可以看到,在UML类图中单向关联用一个带箭头的直线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。

(2)双向关联

img

从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个Product[]数组,表示一个顾客购买了那些产品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。

(3)自关联

img

自关联在UML类图中用一个带有箭头且指向自身的直线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。

2、聚合关系

img

上图中的Car类与Engine类就是聚合关系(Car类中包含一个Engine类型的成员变量)。由上图我们可以看到,UML中聚合关系用带空心菱形和箭头的直线表示。聚合关系强调是“整体”包含“部分”,但是“部分”可以脱离“整体”而单独存在。比如上图中汽车包含了发动机,而发动机脱离了汽车也能单独存在。

3、组合关系

组合关系与聚合关系见得最大不同在于:这里的“部分”脱离了“整体”便不复存在。比如下图:

img

显然,嘴是头的一部分且不能脱离了头而单独存在。在UML类图中,组合关系用一个带实心菱形和箭头的直线表示。

4、依赖关系

img

从上图我们可以看到,Driver的drive方法只有传入了一个Car对象才能发挥作用,因此我们说Driver类依赖于Car类。在UML类图中,依赖关系用一条带有箭头的虚线表示。

5、继承关系

继承关系对应的是extend关键字,在UML类图中用带空心三角形的直线表示,如下图所示中,Student类与Teacher类继承了Person类。

img

6、接口实现关系

这种关系对应implement关键字,在UML类图中用带空心三角形的虚线表示。如下图中,Car类与Ship类都实现了Vehicle接口。

img

到了这里,UML类图中最常见的表示方式我们就介绍完了,有了这些我们就能读懂常见的UML类图了,剩下的遇到时再查即可。

概述

什么是设计模式

Christopher Alexander说过:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。

模式分类

模式依据其目的可分为创建型(Creational)、结构型(Structural)、或行为型(Behavioral)三种。

创建型模式涉及到将对象实例化,这类模式都提供一个方法,将客户从所需要实例化的对象中解耦。

只要是行为型模式,都涉及到类和对象如何交互及分配职责。

结构型模式用来描述类和对象如何被组合以建立新的结构或新的功能。

设计模式分类

类模式描述类之间的关系如何通过继承定义。类模式的关系是在编译时建立的。

对象模式描述对象之间的关系,而且主要是利用组合定义。对象模式的关系通常在运行时建立,而且更加动态、更有弹性。

模式按照类和对象分类

创建型

单例(重点)

package chapter2;
/**
 * Created by ryder on 2017/6/7.
 * 单例模式
 * 定义:指实现了特殊模式的类,该类仅能被实例化一次,产生唯一的一个对象
 * 应用举例:windows的任务管理器,回收站,web应用的配置对象,spring中的bean默认也是单例
 * 分类:饿汉式,懒汉式,双检锁,静态内部类,枚举
 * 评价指标有:单例(必须),线程安全,延迟加载,防止反序列化产生新对象,防止反射攻击
 * 实现方法的选择:一般情况下直接使用饿汉式就好了,要求延迟加载时倾向于用静态内部类,涉及到反序列化创建对象或反射问题最好选择枚举
 */
public class P32_Singleton {
    public static void main(String[] args){
        //调用方式
        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton2 singleton2 = Singleton2.getInstance();
        Singleton3 singleton3 = Singleton3.getInstance();
        Singleton4 singleton4 = Singleton4.getInstance();
        Singleton5 singleton5 = Singleton5.getInstance();
        Singleton6 singleton6 = Singleton6.getInstance();
        Singleton7 singleton7 = Singleton7.instance;
        singleton7.setAttribute("aaa");
    }
}

//版本一:饿汉式
//特点:线程安全;在类初始化执行到静态属性时就分配了资源,有资源浪费问题;
//利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniquelnstance	  //静态变量之前,一定先创建此实例。
class Singleton1{
    //或者将私有静态final成员设为公有成员,可省去getInstance公有函数
    private static final Singleton1 instance = new Singleton1();
    private Singleton1(){}
    public static Singleton1 getInstance(){
        return instance;
    }
}

//版本二:懒汉式(非线程安全)
//特点:在第一次调用获取实例方法时分配内存,实现了懒加载;非线程安全;
class Singleton2{
    private static Singleton2 instance= null;
    private Singleton2(){}
    public static Singleton2 getInstance(){
        if(instance==null){
            instance = new Singleton2();
        }
        return instance;
    }
}

//版本三:懒汉式变种(synchronized同步方法,支持多线程)
//特点:线程安全;synchronized而造成的阻塞致使效率低,而且很多的阻塞都是没必要的。
class Singleton3{
    private static Singleton3 instance = null;
    private Singleton3(){}
    public static synchronized Singleton3 getInstance(){
        if(instance == null)
            instance = new Singleton3();
        return instance;
    }
}

//版本四:懒汉式变种(synchronized同步块,支持多线程)
//特点:写法不同,但与版本三有一样的问题
class Singleton4{
    private static Singleton4 instance = null;
    private Singleton4(){}
    public static Singleton4 getInstance(){
        synchronized(Singleton4.class) {
            if (instance == null)
                instance = new Singleton4();
        }
        return instance;
    }
}

//版本五:双检锁DCL,支持多线程-懒汉式(不推荐使用,执行速度没有静态内部类快)
//特点:线程安全;多进行一次if判断,加入volatile修饰,优点是只有在第一次实例化时加锁,之后不会加锁,提升了效率,缺点写法复杂
//不加入volatile,可能出现第一个if判断不为null,但还并未执行构造函数的情况,因为java编译器会进行指令重排;
//volatile的两大作用:
//1防止编译器对被修饰变量相关代码进行指令重排;2读写操作都不会调用工作内存而是直接取主存,保证了内存可见性
//指令重排:
//instance = new Singleton5()可主要分为三步:1分配内存,2调用构造函数,3instance指向被分配的内存(此时instance不为null了)
//正常顺序为123,指令重排可能执行顺序为132,会造成已不为null但未执行构造函数的问题
//内存可见性:
//如果字段是被volatile修饰的,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。
//这意味着:1一旦完成写入,任何访问这个字段的线程将会得到最新的;2在写入前,任何更新过的数据值是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
//因此volatile可提供一定的线程安全,但不适用于写操作依赖于当前值的情况,如自增,自减
//简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
//还能在双检锁上进行优化,引入一个局部变量,但个人觉得效率提成并不大,不再赘述。
//volatile参考:http://blog.csdn.net/qq_29923439/article/details/51273812
class Singleton5{
    private volatile static Singleton5 instance = null;
    private Singleton5(){}
    public  static Singleton5 getInstance(){
        if(instance==null){
            synchronized (Singleton5.class){
                if(instance==null)
                    instance = new Singleton5();//因为instance使用volatile修饰,所以这里修改的话其他线程可以看到
            }
        }
        return instance;//如果没有使用volatile修饰的话,获取一个已构造好的Resource引用(return语句),并没有使用同步。这就是问题所在,线程可能看到一个仅被部分构造的Resource。
    }
}

//版本六:静态内部类,支持多线程-懒汉式(推荐使用)
//特点:利用静态内部类(只有在出现它的引用时才被加载),完成懒加载;final保证线程安全;
//类的加载顺序:http://blog.csdn.net/u012123160/article/details/53224469
//final的作用:
//1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
//2. 初次读一个包含final域的对象的引用,与随后读这个final域,这两个操作之间不能重排序。
//扩展:static变量初始化遵循以下规则:
//1.静态变量会按照声明的顺序先依次声明并设置为该类型的默认值,但不赋值为初始化的值。
//2.声明完毕后,再按声明的顺序依次设置为初始化的值,如果没有初始化的值就跳过。
//static变量初始化参考:http://www.jb51.net/article/86629.htm
class Singleton6{
    private Singleton6(){}
    public static Singleton6 getInstance(){
        return Singleton6Holder.instance;//可以实现延迟初始化
    }
    private static class Singleton6Holder{
        public static final Singleton6 instance = new Singleton6();
    }
}

//版本七:通过枚举实现
//一个完美的单例需要做到:单例,懒加载,线程安全,防止反序列化产生新对象,防止反射攻击
//而枚举的特性保证了以上除了懒加载以外的所有要求,而且实现代码及其简单
//Enum的单例模式参考:http://www.jianshu.com/p/83f7958b0944
enum Singleton7{
    instance;
    private String attribute;
    void setAttribute(String attribute){
        this.attribute = attribute;
    }
    String getAttribute(){
        return this.attribute;
    }
}

工厂(重点)

我们模拟了一个披萨店,它需要实现Pizza类和PizzaStore类,PizzaStore类需要包含orderPizza方法。在orderPizza方法中需要对菜单中的某几种披萨进行实例化。如果直接使用new进行实例化,当我们想更新菜单时,就必须对orderPizza中的代码进行更改,很明显违背了我们**“对修改封闭”**的原则。

public class PizzaStore {
	SimplePizzaFactory factory;
 
	public PizzaStore(SimplePizzaFactory factory) { 
		this.factory = factory;
	}
 
	public Pizza orderPizza(String type) {
 
		Pizza pizza = null;

		if (type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
			pizza = new VeggiePizza();
		}
 
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

}

简单工厂

package headfirst.designpatterns.factory.pizzas;

public class PizzaStore {
	SimplePizzaFactory factory;
 
	public PizzaStore(SimplePizzaFactory factory) { 
		this.factory = factory;
	}
 
	public Pizza orderPizza(String type) {
		Pizza pizza;
 
		pizza = factory.createPizza(type);
 
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

}
package headfirst.designpatterns.factory.pizzas;

public class SimplePizzaFactory {

	public Pizza createPizza(String type) {
		Pizza pizza = null;

		if (type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
			pizza = new VeggiePizza();
		}
		return pizza;
	}
}

工厂方法

考虑地域不同导致创建的产品不同的话,如果利用SimplePizzaFactory,写出三种不同的工厂,分别是NYPizzaFactory、 ChicagopizaFactory、CaliformiaPizaFactory,那么各地加盟店都有适合的工厂可以使用,这是一种做法。

在推广SimpleFactory时,你发现加盟店的确是采用你的工厂创建比萨,但是其他部分,却开始采用他们自创的流程:烘烤的做法有些差异、不要切片、使用其他厂商的盒子。再想想这个问题,你真的希望能够建立一个框架,把加盟店和创建比萨捆绑在一起的同时又保持一定的弹性。

所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类。

//提供一个抽象的创建对象的方法
package headfirst.designpatterns.factory.pizzafm;

public abstract class PizzaStore {
 
	abstract Pizza createPizza(String item);
 
	public Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}
//实现工厂方法
package headfirst.designpatterns.factory.pizzafm;

public class NYPizzaStore extends PizzaStore {

	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

工厂方法

理解

将产品的“实现”从“使用”中解耦。如果增加产品或者改变产品的实现,Creator并不会受到影响(因为Creator与任何ConcreteProduct之间都不是紧耦合)。

定义

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

设计原则:依赖倒置

要依赖抽象,不要依赖具体类。

首先,这个原则听起来很像是“针对接口编程,不针对实现编程”,不是吗?的确很相似,然而这里更强调“抽象”。这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。

违反依赖倒置(不可能全部满足)

变量不可以持有具体类的引用。 不要让类派生自具体类。 不要覆盖基类中已实现的方法。

抽象工厂模式

承接工厂方法的例子,如果为了确保每家披萨加盟店都使用高质量的原料,我们需要建造一个生产原料的工厂,并将原料运送到各家加盟店。为这个创建原料的工厂定义的接口就是抽象工厂:

public interface PizzaIngredientFactory {
 
	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClam();
 
}

不同区域的原料工厂均实现改接口,且可以根据当地的特色口味进行个性化。这样一来,原料工厂就从具体的原料中被解耦。

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

抽象工厂模式

工厂方法是不是潜伏在抽象工厂里面

你的观察力很敏锐!是的,抽象工厂的方法经常以工厂方法的方式实现,这很有道理,对吧?抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。

要点

所有的工厂都是用来封装对象的创建

工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。

抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中。

所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。

工厂方法允许类将实例化延迟到子类进行。

抽象工厂创建相关的对象家族,而不需要依赖它们的具体类。

依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。

生成器

封装一个对象的构造过程,并允许按步骤构造。


以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。
public class AbstractStringBuilder {
    protected char[] value;

    protected int count;

    public AbstractStringBuilder(int capacity) {
        count = 0;
        value = new char[capacity];
    }

    public AbstractStringBuilder append(char c) {
        ensureCapacityInternal(count + 1);
        value[count++] = c;
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
}
public class StringBuilder extends AbstractStringBuilder {
    public StringBuilder() {
        super(16);
    }

    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
}
public class Client {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        final int count = 26;
        for (int i = 0; i < count; i++) {
            sb.append((char) ('a' + i));
        }
        System.out.println(sb.toString());
    }
}
abcdefghijklmnopqrstuvwxyz

原型

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。


```java public abstract class Prototype { abstract Prototype myClone(); } ```
public class ConcretePrototype extends Prototype {

    private String filed;

    public ConcretePrototype(String filed) {
        this.filed = filed;
    }

    @Override
    Prototype myClone() {
        return new ConcretePrototype(filed);
    }

    @Override
    public String toString() {
        return filed;
    }
}
public class Client {
    public static void main(String[] args) {
        Prototype prototype = new ConcretePrototype("abc");
        Prototype clone = prototype.myClone();
        System.out.println(clone.toString());
    }
}
abc

行为型

责任链

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

  • Handler:定义处理请求的接口,并且实现后继链(successor)

```java public abstract class Handler {
protected Handler successor;


public Handler(Handler successor) {
    this.successor = successor;
}


protected abstract void handleRequest(Request request);

}


```java
public class ConcreteHandler1 extends Handler {

    public ConcreteHandler1(Handler successor) {
        super(successor);
    }


    @Override
    protected void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE1) {
            System.out.println(request.getName() + " is handle by ConcreteHandler1");
            return;
        }
        if (successor != null) {
            successor.handleRequest(request);
        }
    }
}
public class ConcreteHandler2 extends Handler {

    public ConcreteHandler2(Handler successor) {
        super(successor);
    }


    @Override
    protected void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE2) {
            System.out.println(request.getName() + " is handle by ConcreteHandler2");
            return;
        }
        if (successor != null) {
            successor.handleRequest(request);
        }
    }
}
public class Request {

    private RequestType type;
    private String name;


    public Request(RequestType type, String name) {
        this.type = type;
        this.name = name;
    }


    public RequestType getType() {
        return type;
    }


    public String getName() {
        return name;
    }
}
public enum RequestType {
    TYPE1, TYPE2
}
public class Client {

    public static void main(String[] args) {

        Handler handler1 = new ConcreteHandler1(null);
        Handler handler2 = new ConcreteHandler2(handler1);

        Request request1 = new Request(RequestType.TYPE1, "request1");
        handler2.handleRequest(request1);

        Request request2 = new Request(RequestType.TYPE2, "request2");
        handler2.handleRequest(request2);
    }
}
request1 is handle by ConcreteHandler1
request2 is handle by ConcreteHandler2

命令

命令(Command)模式将**“请求”**封装成对象,以便使用不同的请求队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

命令模式将发出请求的对象和执行请求的对象解耦。

在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作

调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。

具有以下作用:

  • 使用命令来参数化其它对象
  • 将命令放入队列中进行排队
  • 将命令的操作记录到日志中
  • 支持可撤销的操作

类图

  • Command:命令
  • Receiver:命令接收者,也就是命令真正的执行者
  • Invoker:通过它来调用命令
  • Client:可以设置命令与命令的接收者

直接用一个[实例](https://github.com/CrowHawk/DesignPattern-Learning/tree/master/Command/src)来加以说明: 该代码模拟了一个智能家居遥控器,这个遥控器有七个可编程的插槽(每个插槽对应一个不同的家电装置),每个插槽都有一对相对应的开/关按钮。遥控器应该知道如何解读按钮被按下的动作,然后向家电发出正确的请求,但是遥控器不需知道这些家电自动化的细节,或者如何打开热水器。

命令模式可将“动作的请求者”从“动作的执行者”对象中解耦,在设计中采用**“命令对象”**,把请求(例如打开电灯)封装成一个特定对象(例如客厅电灯对象)。如果对每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能和正确的对象沟通,把事情做好就可以了,这样一来,**遥控器(即发出请求的对象,Invoker)各个家电对象(即接受与执行这些请求的对象,Receiver)**就解耦了。


```java public interface Command { void execute(); } ```
public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}
public class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}
public class Light {

    public void on() {
        System.out.println("Light is on!");
    }

    public void off() {
        System.out.println("Light is off!");
    }
}
/**
 * 遥控器
 */
public class Invoker {
    private Command[] onCommands;
    private Command[] offCommands;
    private final int slotNum = 7;

    public Invoker() {
        this.onCommands = new Command[slotNum];
        this.offCommands = new Command[slotNum];
    }

    public void setOnCommand(Command command, int slot) {
        onCommands[slot] = command;
    }

    public void setOffCommand(Command command, int slot) {
        offCommands[slot] = command;
    }

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }
}
public class Client {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Light light = new Light();
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
        invoker.setOnCommand(lightOnCommand, 0);
        invoker.setOffCommand(lightOffCommand, 0);
        invoker.onButtonWasPushed(0);
        invoker.offButtonWasPushed(0);
    }
}

解释器

为语言创建解释器,通常由语言的语法和语法分析来定义。

  • TerminalExpression:终结符表达式,每个终结符都需要一个 TerminalExpression。
  • Context:上下文,包含解释器之外的一些全局信息。

以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。

例如一颗解析树为 D And (A Or (B C)),文本 "D A" 满足该解析树定义的规则。

这里的 Context 指的是 String。

public abstract class Expression {
    public abstract boolean interpret(String str);
}
public class TerminalExpression extends Expression {

    private String literal = null;

    public TerminalExpression(String str) {
        literal = str;
    }

    public boolean interpret(String str) {
        StringTokenizer st = new StringTokenizer(str);
        while (st.hasMoreTokens()) {
            String test = st.nextToken();
            if (test.equals(literal)) {
                return true;
            }
        }
        return false;
    }
}
public class AndExpression extends Expression {

    private Expression expression1 = null;
    private Expression expression2 = null;

    public AndExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    public boolean interpret(String str) {
        return expression1.interpret(str) && expression2.interpret(str);
    }
}
public class OrExpression extends Expression {
    private Expression expression1 = null;
    private Expression expression2 = null;

    public OrExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    public boolean interpret(String str) {
        return expression1.interpret(str) || expression2.interpret(str);
    }
}
public class Client {

    /**
     * 构建解析树
     */
    public static Expression buildInterpreterTree() {
        // Literal
        Expression terminal1 = new TerminalExpression("A");
        Expression terminal2 = new TerminalExpression("B");
        Expression terminal3 = new TerminalExpression("C");
        Expression terminal4 = new TerminalExpression("D");
        // B C
        Expression alternation1 = new OrExpression(terminal2, terminal3);
        // A Or (B C)
        Expression alternation2 = new OrExpression(terminal1, alternation1);
        // D And (A Or (B C))
        return new AndExpression(terminal4, alternation2);
    }

    public static void main(String[] args) {
        Expression define = buildInterpreterTree();
        String context1 = "D A";
        String context2 = "A B";
        System.out.println(define.interpret(context1));
        System.out.println(define.interpret(context2));
    }
}
true
false

迭代器(重点)

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

如果你有一个统一的方法访问聚合中的每一个对象,你就可以编写多态的代码和这些聚合搭配,使用。迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得更简洁,也可以让聚合更专注在它所应该专注的事情上面(也就是管理对象集合),而不必去理会遍历的事情。

  • Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator;
  • Iterator 主要定义了 hasNext() 和 next() 方法。
  • Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。

下面用一个具体实例来加以说明。

我们模拟了一个西餐厅、一个煎饼屋和一个咖啡厅,现在我们想要让顾客在同一个地方能够同时点这三种餐馆的菜色。

在原来的菜单实现中,西餐厅的菜单用数组来记录菜单项,煎饼屋的菜单用ArrayList来记录菜单项,而咖啡厅的菜单用Hashtable来记录菜单项。

我们抽取出的菜单接口为

import java.util.Iterator;

public interface Menu {
	public Iterator createIterator();
}

菜单项的类为

public class MenuItem {
	String name;
	String description;
	boolean vegetarian;
	double price;
 
	public MenuItem(String name, 
	                String description, 
	                boolean vegetarian, 
	                double price) 
	{
		this.name = name;
		this.description = description;
		this.vegetarian = vegetarian;
		this.price = price;
	}
  
	public String getName() {
		return name;
	}
  
	public String getDescription() {
		return description;
	}
  
	public double getPrice() {
		return price;
	}
  
	public boolean isVegetarian() {
		return vegetarian;
	}
}

我们需要创建一个Java版本的女招待,她需要能应对顾客的需要打印定制的菜单,甚至告诉你是否某个菜单项是素食的,而无需询问厨师。

我们需要找出一个方法,让这些餐馆的菜单实现一个相同的接口。在之前的文章中讲到,我们有一个重要的设计原则就是封装变化的部分。很明显,在这里发生变化的是由不同的集合类型所造成的遍历。我们需要借助迭代器模式来解决这一问题。

迭代器模式依赖于一个名为迭代器的接口

public interface Iterator {
	boolean hasNext();
	Object next();
}

对于煎饼屋的ArrayList菜单和咖啡厅的Hashtable菜单,Java均已为其实现了默认的迭代器,而对于西餐厅的数组菜单,则需要我们自己实现一个迭代器:

import java.util.Iterator;
  
public class DinerMenuIterator implements Iterator {
	MenuItem[] list;
	int position = 0;
 
	public DinerMenuIterator(MenuItem[] list) {
		this.list = list;
	}
 
	public Object next() {
		MenuItem menuItem = list[position];
		position = position + 1;
		return menuItem;
	}
 
	public boolean hasNext() {
		if (position >= list.length || list[position] == null) {
			return false;
		} else {
			return true;
		}
	}
  
	public void remove() {
		if (position <= 0) {
			throw new IllegalStateException
				("You can't remove an item until you've done at least one next()");
		}
		if (list[position-1] != null) {
			for (int i = position-1; i < (list.length-1); i++) {
				list[i] = list[i+1];
			}
			list[list.length-1] = null;
		}
	}
}

相应的西餐厅菜单实现为

import java.util.Iterator;

public class DinerMenu implements Menu {
	static final int MAX_ITEMS = 6;
	int numberOfItems = 0;
	MenuItem[] menuItems;
  
	public DinerMenu() {
		menuItems = new MenuItem[MAX_ITEMS];
 
		addItem("Vegetarian BLT",
			"(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
		addItem("BLT",
			"Bacon with lettuce & tomato on whole wheat", false, 2.99);
		addItem("Soup of the day",
			"Soup of the day, with a side of potato salad", false, 3.29);
	}
  
	public void addItem(String name, String description, 
	                     boolean vegetarian, double price) 
	{
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		if (numberOfItems >= MAX_ITEMS) {
			System.err.println("Sorry, menu is full! Can't add item to menu");
		} else {
			menuItems[numberOfItems] = menuItem;
			numberOfItems = numberOfItems + 1;
		}
	}
 
	public MenuItem[] getMenuItems() {
		return menuItems;
	}
  
	public Iterator createIterator() {
		return new DinerMenuIterator(menuItems);
		//return new AlternatingDinerMenuIterator(menuItems);
	}
 
	// other menu methods here
}

设计原则——单一责任原则

如果我们允许我们的聚合实现它们内部的集合,以及相关的操作和遍历的方法,这样我们就允许一个类不但要完成自己的事情(管理某种聚合),还同时要负担更多的责任(例如遍历)时,我们就给了这个类两个变化的原因:

  • 如果这个集合改变的话,这个类也必须跟着改变
  • 如果我们遍历的方式改变的话,这个类也必须跟着改变

所以,我们的老朋友“改变”,又一次成为了我们设计原则的中心——**单一责任原则:一个类应该只有一个引起变化的原因。

内聚

当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

迭代器模式

送代器与集合

我们所使用的这些类都属于Java Collection Framework的一部分。这里所谓的“framework”(框架)指的是一群类和接口,其中包括了ArrayList、Vector、LinkedList、Stack和PriorityQueue。这些类都实现了java.util.Collection接口。这个接口包含了许多有用的方法,可以操纵一群对象。

中介者

集中相关对象之间复杂的沟通和控制方式。

  • Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。
  • Colleague:同事,相关对象

Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:

使用中介者模式可以将复杂的依赖结构变成星形结构:

```java public abstract class Colleague { public abstract void onEvent(Mediator mediator); } ```
public class Alarm extends Colleague {

    @Override
    public void onEvent(Mediator mediator) {
        mediator.doEvent("alarm");
    }

    public void doAlarm() {
        System.out.println("doAlarm()");
    }
}
public class CoffeePot extends Colleague {
    @Override
    public void onEvent(Mediator mediator) {
        mediator.doEvent("coffeePot");
    }

    public void doCoffeePot() {
        System.out.println("doCoffeePot()");
    }
}
public class Calender extends Colleague {
    @Override
    public void onEvent(Mediator mediator) {
        mediator.doEvent("calender");
    }

    public void doCalender() {
        System.out.println("doCalender()");
    }
}
public class Sprinkler extends Colleague {
    @Override
    public void onEvent(Mediator mediator) {
        mediator.doEvent("sprinkler");
    }

    public void doSprinkler() {
        System.out.println("doSprinkler()");
    }
}
public abstract class Mediator {
    public abstract void doEvent(String eventType);
}
public class ConcreteMediator extends Mediator {
    private Alarm alarm;
    private CoffeePot coffeePot;
    private Calender calender;
    private Sprinkler sprinkler;

    public ConcreteMediator(Alarm alarm, CoffeePot coffeePot, Calender calender, Sprinkler sprinkler) {
        this.alarm = alarm;
        this.coffeePot = coffeePot;
        this.calender = calender;
        this.sprinkler = sprinkler;
    }

    @Override
    public void doEvent(String eventType) {
        switch (eventType) {
            case "alarm":
                doAlarmEvent();
                break;
            case "coffeePot":
                doCoffeePotEvent();
                break;
            case "calender":
                doCalenderEvent();
                break;
            default:
                doSprinklerEvent();
        }
    }

    public void doAlarmEvent() {
        alarm.doAlarm();
        coffeePot.doCoffeePot();
        calender.doCalender();
        sprinkler.doSprinkler();
    }

    public void doCoffeePotEvent() {
        // ...
    }

    public void doCalenderEvent() {
        // ...
    }

    public void doSprinklerEvent() {
        // ...
    }
}
public class Client {
    public static void main(String[] args) {
        Alarm alarm = new Alarm();
        CoffeePot coffeePot = new CoffeePot();
        Calender calender = new Calender();
        Sprinkler sprinkler = new Sprinkler();
        Mediator mediator = new ConcreteMediator(alarm, coffeePot, calender, sprinkler);
        // 闹钟事件到达,调用中介者就可以操作相关对象
        alarm.onEvent(mediator);
    }
}
doAlarm()
doCoffeePot()
doCalender()
doSprinkler()

备忘录

在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。

  • Originator:原始对象
  • Caretaker:负责保存好备忘录
  • Menento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。

以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。

实现参考:Memento Pattern - Calculator Example - Java Sourcecode

/**
 * Originator Interface
 */
public interface Calculator {

    // Create Memento
    PreviousCalculationToCareTaker backupLastCalculation();

    // setMemento
    void restorePreviousCalculation(PreviousCalculationToCareTaker memento);

    int getCalculationResult();

    void setFirstNumber(int firstNumber);

    void setSecondNumber(int secondNumber);
}
/**
 * Originator Implementation
 */
public class CalculatorImp implements Calculator {

    private int firstNumber;
    private int secondNumber;

    @Override
    public PreviousCalculationToCareTaker backupLastCalculation() {
        // create a memento object used for restoring two numbers
        return new PreviousCalculationImp(firstNumber, secondNumber);
    }

    @Override
    public void restorePreviousCalculation(PreviousCalculationToCareTaker memento) {
        this.firstNumber = ((PreviousCalculationToOriginator) memento).getFirstNumber();
        this.secondNumber = ((PreviousCalculationToOriginator) memento).getSecondNumber();
    }

    @Override
    public int getCalculationResult() {
        // result is adding two numbers
        return firstNumber + secondNumber;
    }

    @Override
    public void setFirstNumber(int firstNumber) {
        this.firstNumber = firstNumber;
    }

    @Override
    public void setSecondNumber(int secondNumber) {
        this.secondNumber = secondNumber;
    }
}
/**
 * Memento Interface to Originator
 *
 * This interface allows the originator to restore its state
 */
public interface PreviousCalculationToOriginator {
    int getFirstNumber();
    int getSecondNumber();
}
/**
 *  Memento interface to CalculatorOperator (Caretaker)
 */
public interface PreviousCalculationToCareTaker {
    // no operations permitted for the caretaker
}
/**
 * Memento Object Implementation
 * <p>
 * Note that this object implements both interfaces to Originator and CareTaker
 */
public class PreviousCalculationImp implements PreviousCalculationToCareTaker,
        PreviousCalculationToOriginator {

    private int firstNumber;
    private int secondNumber;

    public PreviousCalculationImp(int firstNumber, int secondNumber) {
        this.firstNumber = firstNumber;
        this.secondNumber = secondNumber;
    }

    @Override
    public int getFirstNumber() {
        return firstNumber;
    }

    @Override
    public int getSecondNumber() {
        return secondNumber;
    }
}
/**
 * CareTaker object
 */
public class Client {

    public static void main(String[] args) {
        // program starts
        Calculator calculator = new CalculatorImp();

        // assume user enters two numbers
        calculator.setFirstNumber(10);
        calculator.setSecondNumber(100);

        // find result
        System.out.println(calculator.getCalculationResult());

        // Store result of this calculation in case of error
        PreviousCalculationToCareTaker memento = calculator.backupLastCalculation();

        // user enters a number
        calculator.setFirstNumber(17);

        // user enters a wrong second number and calculates result
        calculator.setSecondNumber(-290);

        // calculate result
        System.out.println(calculator.getCalculationResult());

        // user hits CTRL + Z to undo last operation and see last result
        calculator.restorePreviousCalculation(memento);

        // result restored
        System.out.println(calculator.getCalculationResult());
    }
}
110
-273
110

观察者(重点)

定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。

主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。


主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。

观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。


天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。

```java public interface Subject { void registerObserver(Observer o);
void removeObserver(Observer o);

void notifyObserver();

}


```java
public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObserver();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }
}
public interface Observer {
    void update(float temp, float humidity, float pressure);
}
public class StatisticsDisplay implements Observer {

    public StatisticsDisplay(Subject weatherData) {
        weatherData.reisterObserver(this);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure);
    }
}
public class CurrentConditionsDisplay implements Observer {

    public CurrentConditionsDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure);
    }
}
public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        weatherData.setMeasurements(0, 0, 0);
        weatherData.setMeasurements(1, 1, 1);
    }
}
CurrentConditionsDisplay.update: 0.0 0.0 0.0
StatisticsDisplay.update: 0.0 0.0 0.0
CurrentConditionsDisplay.update: 1.0 1.0 1.0
StatisticsDisplay.update: 1.0 1.0 1.0

松耦合

观察者模式体现了第四个设计原则:为了交互对象之间的松耦合设计而努力。我们可以独立地复用主题和观察者,改变主题或观察者其中的一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的相互依赖性降到了最低。

设计原则

1.找出程序中会变化的方面,然后将其和固定不变的方面相分离。

在观察者模式中,会改变的是主题的状态,以及观察者的数自和类型。用这个楼式,你可以改变依赖于主题状态的对象,却不必改变主题。这就叫提前规划!

2.针对接口编程,不针对实现编程。

主题与观察专都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两专之间运作正常,又同时只有松耦合的优点。

3.多用组合,少用继承。

观察者模式利用“组合”将许多观察专组合进主题中。对系之间的这种关系不是通过继承产生的,而是在运行响利用组合的方式而产生的。

状态

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

直接看定义好像有些晦涩,我们先看例子,待会儿再回到定义来。

我们现在模拟一个糖果机的工作状态,它的状态变化图如下所示img找出所有的状态:

  • 没有25分钱
  • 有25分钱
  • 糖果售罄
  • 售出糖果

现在我们需要用代码来实现这些状态。我们可以创建一个实例变量来持有目前的状态,然后定义每个状态的值:

final static int SOLD_OUT=0; 
final static int NO_QUARTER=1; 
final static int HAS_QUARTER=2; 
final static int SOLD=3; 

int state =SOLD_OUT;

但是显然可以预见的是,这种做法会带来一个弊端,当你需要扩展状态时,修改代码会变得非常困难。比如说,我们需要为糖果机新增一个功能,“售出糖果”状态有10%的几率会导致掉下两颗糖果,而不是一颗,这显然需要增加一个状态,用原有的实现方式会让我们陷入混乱的状态中无法脱身。

为此,我们应该试着局部化每个状态的行为,遵守**“封装变化”**原则,将每个状态的行为都放在各自的类中,那么每个状态只要实现它自己的动作就可以了。我们要做的事情是

  • 我们可以定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法。
  • 然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。
  • 最后,将动作委托到状态类。

State接口

public interface State {
	public void insertQuarter();
	public void ejectQuarter();
	public void turnCrank();
	public void dispense();
}

每个状态对应的状态类

import java.util.Random;

public class HasQuarterState implements State {
	Random randomWinner = new Random(System.currentTimeMillis());
	GumballMachine gumballMachine;
 
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
  
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}
 
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	public void turnCrank() {
		System.out.println("You turned...");
		int winner = randomWinner.nextInt(10);
		if ((winner == 0) && (gumballMachine.getCount() > 1)) {
			gumballMachine.setState(gumballMachine.getWinnerState());
		} else {
			gumballMachine.setState(gumballMachine.getSoldState());
		}
	}

    public void dispense() {
        System.out.println("No gumball dispensed");
    }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}
public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	public void dispense() {
		System.out.println("You need to pay first");
	} 
 
	public String toString() {
		return "waiting for quarter";
	}
}
public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You can't insert a quarter, the machine is sold out");
	}
 
	public void ejectQuarter() {
		System.out.println("You can't eject, you haven't inserted a quarter yet");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there are no gumballs");
	}
 
	public void dispense() {
		System.out.println("No gumball dispensed");
	}
 
	public String toString() {
		return "sold out";
	}
}
public class SoldState implements State {
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}
 
	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}
 
	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
 
	public String toString() {
		return "dispensing a gumball";
	}
}
public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void turnCrank() {
		System.out.println("Turning again doesn't get you another gumball!");
	}
 
	public void dispense() {
		System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter");
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() == 0) {
			gumballMachine.setState(gumballMachine.getSoldOutState());
		} else {
			gumballMachine.releaseBall();
			if (gumballMachine.getCount() > 0) {
				gumballMachine.setState(gumballMachine.getNoQuarterState());
			} else {
            	System.out.println("Oops, out of gumballs!");
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}
		}
	}
 
	public String toString() {
		return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
	}
}

下面是我们的糖果机代码

public class GumballMachine {
 
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;
 
	State state = soldOutState;
	int count = 0;
 
	public GumballMachine(int numberGumballs) {
		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);

		this.count = numberGumballs;
 		if (numberGumballs > 0) {
			state = noQuarterState;
		} 
	}
 
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}

	void setState(State state) {
		this.state = state;
	}
 
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;
		}
	}
 
	int getCount() {
		return count;
	}
 
	void refill(int count) {
		this.count = count;
		state = noQuarterState;
	}

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

测试代码如下

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = 
			new GumballMachine(10);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

测试结果为imgimg

现在我们回到文章开头状态模式的定义,这个描述的第一部分**“状态模式允许对象在内部状态改变时改变它的行为”**附有相当多的涵义,因为这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,行为会随着内部的状态改变而改变。

定义的第二部分**“对象看起来好像修改了它的类”**是什么意思呢?从客户的视角看:如果你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

状态模式的类图

状态模式

策略模式和状态模式的区别

类图是一样的,但是这两个模式的差别在于它们的“意图”。

以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。

而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。比方说,在第1章,有些鸭子(例如绿头鸭)被设置成利用典型的飞翔行为进行飞翔,而有些鸭子(例如橡皮鸭和诱饵鸭)使用的飞翔行为只能让他们紧贴地面。

策略(重点)

定义一系列算法,封装每个算法,并使它们可以互换。

策略模式可以让算法独立于使用它的客户端。

  • Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
  • Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。

**继承带来的问题**

我用代码模拟了鸭子的行为,包括不同品种的鸭子乃至橡皮鸭、诱饵鸭。活鸭子都会游泳戏水,会呱呱叫,会飞,但是诱饵鸭不会飞也不会叫,橡皮鸭不会飞但会叫,如果采用传统的继承方式设计程序,设计一个Duck类,在超类中加上fly()和quack(),让所有鸭子继承这个类,会导致所有的子类具备fly()和quack(),连那些不该具备的子类也无法免除。

为了解决这个问题,先用到第一个设计原则:封装变化。即找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让**“系统中的某部分改变不会影响其他部分”**。 根据该原则,我们知道Duck类的fly()和quack()会随着鸭子的不同而改变。为了把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

针对实现编程的问题

我们利用接口代表每个行为,比方说,FlyBehavior与Quack-Behavior,而行为的每个实现都将实现其中的一个接口。 所以这次鸭子类不会负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就称为“行为”类。由行为类而不是Duck类来实现行为接口。这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑得死死的,没办法更改行为(除非写更多代码)。

在设计鸭子的行为类时,我们用到了第二个设计原则:针对接口编程,而不是针对实现编程,这里针对接口编程真正的意思是针对超类型编程,超类型通常是一个接口或一个抽象类。我们可以制造出FlyBehavior接口和QuackBehavior接口,再制造一组其他类专门实现FlyBehavior和QuackBehavior。

public interface QuackBehavior {
    void quack();
}
public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("quack!");
    }
}
public class Squeak implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("squeak!");
    }
}
public class Duck {

    private QuackBehavior quackBehavior;

    public void performQuack() {
        if (quackBehavior != null) {
            quackBehavior.quack();
        }
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}
public class Client {

    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.setQuackBehavior(new Squeak());
        duck.performQuack();
        duck.setQuackBehavior(new Quack());
        duck.performQuack();
    }
}

//squeak!
//quack!

在我们的代码设计中,每一个鸭子都有一个FlyBehavior和一个QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。当你将两个类结合起来使用,这就是组合,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。这里体现了第三个设计原则:多用组合,少用继承

设计原则

1.封装变化

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。把会变化的部分取出并“封装”起来,好让其他部分不会受到影响。结果如何?代码变化引起的不经意后果变少,系统变得更有弹性。

2.接口编程

针对接口编程,而不是针对实现编程。这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑得死死的,没办法更改行为(除非写更多代码)。 Java interface的情况下,“针对接口编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。

3.多用组合,少用继承

使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可

模板方法(重点)

定义算法框架,并将一些步骤的实现延迟到子类。

通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。


冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。

```java public abstract class CaffeineBeverage {
final void prepareRecipe() {
    boilWater();
    brew();
    pourInCup();
    addCondiments();
}

abstract void brew();

abstract void addCondiments();

void boilWater() {
    System.out.println("boilWater");
}

void pourInCup() {
    System.out.println("pourInCup");
}

}


```java
public class Coffee extends CaffeineBeverage {
    @Override
    void brew() {
        System.out.println("Coffee.brew");
    }

    @Override
    void addCondiments() {
        System.out.println("Coffee.addCondiments");
    }
}
public class Tea extends CaffeineBeverage {
    @Override
    void brew() {
        System.out.println("Tea.brew");
    }

    @Override
    void addCondiments() {
        System.out.println("Tea.addCondiments");
    }
}
public class Client {
    public static void main(String[] args) {
        CaffeineBeverage caffeineBeverage = new Coffee();
        caffeineBeverage.prepareRecipe();
        System.out.println("-----------");
        caffeineBeverage = new Tea();
        caffeineBeverage.prepareRecipe();
    }
}
boilWater
Coffee.brew
pourInCup
Coffee.addCondiments
-----------
boilWater
Tea.brew
pourInCup
Tea.addCondiments

访问者

为一个对象结构(比如组合结构)增加新能力。

  • Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作
  • ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
  • ObjectStructure:对象结构,可以是组合结构,或者是一个集合。

```java public interface Element { void accept(Visitor visitor); } ```
class CustomerGroup {

    private List<Customer> customers = new ArrayList<>();

    void accept(Visitor visitor) {
        for (Customer customer : customers) {
            customer.accept(visitor);
        }
    }

    void addCustomer(Customer customer) {
        customers.add(customer);
    }
}
public class Customer implements Element {

    private String name;
    private List<Order> orders = new ArrayList<>();

    Customer(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    void addOrder(Order order) {
        orders.add(order);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
        for (Order order : orders) {
            order.accept(visitor);
        }
    }
}
public class Order implements Element {

    private String name;
    private List<Item> items = new ArrayList();

    Order(String name) {
        this.name = name;
    }

    Order(String name, String itemName) {
        this.name = name;
        this.addItem(new Item(itemName));
    }

    String getName() {
        return name;
    }

    void addItem(Item item) {
        items.add(item);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);

        for (Item item : items) {
            item.accept(visitor);
        }
    }
}
public class Item implements Element {

    private String name;

    Item(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
public interface Visitor {
    void visit(Customer customer);

    void visit(Order order);

    void visit(Item item);
}
public class GeneralReport implements Visitor {

    private int customersNo;
    private int ordersNo;
    private int itemsNo;

    public void visit(Customer customer) {
        System.out.println(customer.getName());
        customersNo++;
    }

    public void visit(Order order) {
        System.out.println(order.getName());
        ordersNo++;
    }

    public void visit(Item item) {
        System.out.println(item.getName());
        itemsNo++;
    }

    public void displayResults() {
        System.out.println("Number of customers: " + customersNo);
        System.out.println("Number of orders:    " + ordersNo);
        System.out.println("Number of items:     " + itemsNo);
    }
}
public class Client {
    public static void main(String[] args) {
        Customer customer1 = new Customer("customer1");
        customer1.addOrder(new Order("order1", "item1"));
        customer1.addOrder(new Order("order2", "item1"));
        customer1.addOrder(new Order("order3", "item1"));

        Order order = new Order("order_a");
        order.addItem(new Item("item_a1"));
        order.addItem(new Item("item_a2"));
        order.addItem(new Item("item_a3"));
        Customer customer2 = new Customer("customer2");
        customer2.addOrder(order);

        CustomerGroup customers = new CustomerGroup();
        customers.addCustomer(customer1);
        customers.addCustomer(customer2);

        GeneralReport visitor = new GeneralReport();
        customers.accept(visitor);
        visitor.displayResults();
    }
}
customer1
order1
item1
order2
item1
order3
item1
customer2
order_a
item_a1
item_a2
item_a3
Number of customers: 2
Number of orders:    4
Number of items:     6

结构型

适配器

把一个类接口转换成另一个用户需要的接口。



鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。

要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!

public interface Duck {
    void quack();
}
public interface Turkey {
    void gobble();
}
public class WildTurkey implements Turkey {
    @Override
    public void gobble() {
        System.out.println("gobble!");
    }
}
public class TurkeyAdapter implements Duck {
    Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();
    }
}
public class Client {
    public static void main(String[] args) {
        Turkey turkey = new WildTurkey();
        Duck duck = new TurkeyAdapter(turkey);
        duck.quack();
    }
}

桥接

将抽象与实现分离开来,使它们可以独立变化。

  • Abstraction:定义抽象类的接口
  • Implementor:定义实现类接口

RemoteControl 表示遥控器,指代 Abstraction。

TV 表示电视,指代 Implementor。

桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。

public abstract class TV {
    public abstract void on();

    public abstract void off();

    public abstract void tuneChannel();
}
public class Sony extends TV {
    @Override
    public void on() {
        System.out.println("Sony.on()");
    }

    @Override
    public void off() {
        System.out.println("Sony.off()");
    }

    @Override
    public void tuneChannel() {
        System.out.println("Sony.tuneChannel()");
    }
}
public class RCA extends TV {
    @Override
    public void on() {
        System.out.println("RCA.on()");
    }

    @Override
    public void off() {
        System.out.println("RCA.off()");
    }

    @Override
    public void tuneChannel() {
        System.out.println("RCA.tuneChannel()");
    }
}
public abstract class RemoteControl {
    protected TV tv;

    public RemoteControl(TV tv) {
        this.tv = tv;
    }

    public abstract void on();

    public abstract void off();

    public abstract void tuneChannel();
}
public class ConcreteRemoteControl1 extends RemoteControl {
    public ConcreteRemoteControl1(TV tv) {
        super(tv);
    }

    @Override
    public void on() {
        System.out.println("ConcreteRemoteControl1.on()");
        tv.on();
    }

    @Override
    public void off() {
        System.out.println("ConcreteRemoteControl1.off()");
        tv.off();
    }

    @Override
    public void tuneChannel() {
        System.out.println("ConcreteRemoteControl1.tuneChannel()");
        tv.tuneChannel();
    }
}
public class ConcreteRemoteControl2 extends RemoteControl {
    public ConcreteRemoteControl2(TV tv) {
        super(tv);
    }

    @Override
    public void on() {
        System.out.println("ConcreteRemoteControl2.on()");
        tv.on();
    }

    @Override
    public void off() {
        System.out.println("ConcreteRemoteControl2.off()");
        tv.off();
    }

    @Override
    public void tuneChannel() {
        System.out.println("ConcreteRemoteControl2.tuneChannel()");
        tv.tuneChannel();
    }
}
public class Client {
    public static void main(String[] args) {
        RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA());
        remoteControl1.on();
        remoteControl1.off();
        remoteControl1.tuneChannel();
        RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony());
         remoteControl2.on();
         remoteControl2.off();
         remoteControl2.tuneChannel();
    }
}

组合

将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。

组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。

组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。


```java public abstract class Component { protected String name;
public Component(String name) {
    this.name = name;
}

public void print() {
    print(0);
}

abstract void print(int level);

abstract public void add(Component component);

abstract public void remove(Component component);

}


```java
public class Composite extends Component {

    private List<Component> child;

    public Composite(String name) {
        super(name);
        child = new ArrayList<>();
    }

    @Override
    void print(int level) {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println("Composite:" + name);
        for (Component component : child) {
            component.print(level + 1);
        }
    }

    @Override
    public void add(Component component) {
        child.add(component);
    }

    @Override
    public void remove(Component component) {
        child.remove(component);
    }
}
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    void print(int level) {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println("left:" + name);
    }

    @Override
    public void add(Component component) {
        throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则,这样就不用考虑是叶子节点还是组合节点
    }

    @Override
    public void remove(Component component) {
        throw new UnsupportedOperationException();
    }
}
public class Client {
    public static void main(String[] args) {
        Composite root = new Composite("root");
        Component node1 = new Leaf("1");
        Component node2 = new Composite("2");
        Component node3 = new Leaf("3");
        root.add(node1);
        root.add(node2);
        root.add(node3);
        Component node21 = new Leaf("21");
        Component node22 = new Composite("22");
        node2.add(node21);
        node2.add(node22);
        Component node221 = new Leaf("221");
        node22.add(node221);
        root.print();
    }
}
Composite:root
--left:1
--Composite:2
----left:21
----Composite:22
------left:221
--left:3

装饰(重点)

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

装饰者和被装饰对象有相同的超类型。 你可以用一个或多个装饰者包装一个对象。 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。 组合和委托可用于在运行时动态地加上新的行为。


设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。

下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。


```java public interface Beverage { double cost(); } ```
public class DarkRoast implements Beverage {
    @Override
    public double cost() {
        return 1;
    }
}
public class HouseBlend implements Beverage {
    @Override
    public double cost() {
        return 1;
    }
}
public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;
}
public class Milk extends CondimentDecorator {

    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 1 + beverage.cost();
    }
}
public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 1 + beverage.cost();
    }
}
public class Client {

    public static void main(String[] args) {
        Beverage beverage = new HouseBlend();
        beverage = new Mocha(beverage);
        beverage = new Milk(beverage);
        System.out.println(beverage.cost());
    }
}
3.0

设计原则

类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。饮料可以动态添加新的配料,而不需要去修改饮料的代码。

不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。

外观

提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。


观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
public class SubSystem {
    public void turnOnTV() {
        System.out.println("turnOnTV()");
    }

    public void setCD(String cd) {
        System.out.println("setCD( " + cd + " )");
    }

    public void startWatching(){
        System.out.println("startWatching()");
    }
}
public class Facade {
    private SubSystem subSystem = new SubSystem();

    public void watchMovie() {
        subSystem.turnOnTV();
        subSystem.setCD("a movie");
        subSystem.startWatching();
    }
}
public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.watchMovie();
    }
}

设计原则

最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。

享元

利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。

  • Flyweight:享元对象
  • IntrinsicState:内部状态,享元对象共享内部状态
  • ExtrinsicState:外部状态,每个享元对象的外部状态不同

```java public interface Flyweight { void doOperation(String extrinsicState); } ```
public class ConcreteFlyweight implements Flyweight {

    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void doOperation(String extrinsicState) {
        System.out.println("Object address: " + System.identityHashCode(this));
        System.out.println("IntrinsicState: " + intrinsicState);
        System.out.println("ExtrinsicState: " + extrinsicState);
    }
}
public class FlyweightFactory {

    private HashMap<String, Flyweight> flyweights = new HashMap<>();

    Flyweight getFlyweight(String intrinsicState) {
        if (!flyweights.containsKey(intrinsicState)) {
            Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
            flyweights.put(intrinsicState, flyweight);
        }
        return flyweights.get(intrinsicState);
    }
}
public class Client {

    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight flyweight1 = factory.getFlyweight("aa");
        Flyweight flyweight2 = factory.getFlyweight("aa");
        flyweight1.doOperation("x");
        flyweight2.doOperation("y");
    }
}
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: x
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: y

Java 利用缓存来加速大量小对象的访问时间。

  • java.lang.Integer#valueOf(int)
  • java.lang.Boolean#valueOf(boolean)
  • java.lang.Byte#valueOf(byte)
  • java.lang.Character#valueOf(char)

代理(重点)

控制对其它对象的访问。

代理有以下四类:

  • 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。(**静态代理**)
public interface Image {
    void showImage();
}
public class HighResolutionImage implements Image {

    private URL imageURL;
    private long startTime;
    private int height;
    private int width;

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public HighResolutionImage(URL imageURL) {
        this.imageURL = imageURL;
        this.startTime = System.currentTimeMillis();
        this.width = 600;
        this.height = 600;
    }

    public boolean isLoad() {
        // 模拟图片加载,延迟 3s 加载完成
        long endTime = System.currentTimeMillis();
        return endTime - startTime > 3000;
    }

    @Override
    public void showImage() {
        System.out.println("Real Image: " + imageURL);
    }
}
public class ImageProxy implements Image {

    private HighResolutionImage highResolutionImage;

    public ImageProxy(HighResolutionImage highResolutionImage) {
        this.highResolutionImage = highResolutionImage;
    }

    @Override
    public void showImage() {
        while (!highResolutionImage.isLoad()) {
            try {
                System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        highResolutionImage.showImage();
    }
}
public class ImageViewer {

    public static void main(String[] args) throws Exception {
        String image = "http://image.jpg";
        URL url = new URL(image);
        HighResolutionImage highResolutionImage = new HighResolutionImage(url);
        ImageProxy imageProxy = new ImageProxy(highResolutionImage);
        imageProxy.showImage();
    }
}

JDK动态代理

​ 利用代理可以在运行时创建一个实现了一组给 定接口的新类 :

​ 要想创建一个代理对象, 需要使用 Proxy 类的 newProxylnstance 方法。 这个方法有三个 参数:

​ 1.一个类加栽器(class loader)。作为 Java 安全模型的一部分, 对于系统类和从因特网 上下载下来的类,可以使用不同的类加载器。有关类加载器的详细内容将在卷 n 第 9 章中讨论。目前, 用 null 表示使用默认的类加载器。

​ 2.一个 Class 对象数组, 每个元素都是需要实现的接口。

​ 3.一个调用处理器

调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)
package test;

import java.lang.reflect.*;
import java.util.*;
public class ProxyTest
{
   public static void main(String[] args) {
	   Object[] elements = new Object[1000];

	      // fill elements with proxies for the integers 1 ... 1000
	      for (int i = 0; i < elements.length; i++)
	      {
	         Integer value = i + 1;
	         InvocationHandler handler = new TraceHandler(value);
	         Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler);
	         elements[i] = proxy;
	      }

	      // construct a random integer
	      Integer key = new Random().nextInt(elements.length) + 1;

	      // search for the key
       	  //由于数组中填充了代理对象, 所以 compareTo 调用了 TraceHander 类中的 invoke 方法。
		//这个方法打印出了方法名和参数, 之后用包装好的 Integer 对象调用 compareTo
	      int result = Arrays.binarySearch(elements, key);

	      // print match if found
	      if (result >= 0) System.out.println(elements[result]);
   }
}

/**
 * An invocation handler that prints out the method name and parameters, then
 * invokes the original method
 */
class TraceHandler implements InvocationHandler
{
   private Object target;//代理的目标对象

   /**
    * Constructs a TraceHandler
    * @param t the implicit parameter of the method call
    */
   public TraceHandler(Object t)
   {
      target = t;
   }

   public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
   {
      // print implicit argument
      System.out.print(target);
      // print method name
      System.out.print("." + m.getName() + "(");
      // print explicit arguments
      if (args != null)
      {
         for (int i = 0; i < args.length; i++)
         {
            System.out.print(args[i]);
            if (i < args.length - 1) System.out.print(", ");
         }
      }
      System.out.println(")");

      // invoke actual method
      return m.invoke(target, args);
   }
}

代理特性

1.代理类是在程序运行过程中创建的。 然而, 一旦被创建, 就变成了常规类, 与虚拟机中的任何其他 类没有什么区别

2.都覆盖了 Object 类中的方法 toString、 equals 和 hashCode。

3.对于特定的类加载器和预设的一组接口来说, 只能有一个代理类

4.代理类一定是 public 和 final。 如果代理类实现的所有接口都是 public, 代理类就不属于 某个特定的包;否则, 所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。

参考

Head First Design Patterns

设计模式