原文出处:单例模式

单例模式

标签 : Java与设计模式

经典的《设计模式:可复用面向对象软件的基础》一书归纳出23种设计模式, 23种设计模式又可划分为3类: 创建型模式, 结构型模式, 行为型模式.

创建型模式

社会分工越来越细, 在软件设计方面自然也是如此,对象的创建和使用分开也就成了必然趋势: 如果对象创建会消耗很多系统资源, 那么单独对对象的创建进行研究,从而高效地创建对象就是 创建型模式 要探讨的问题. 有6个具体的创建型模式可供研究,它们分别是: 单例模式工厂模式(简单工厂/工厂方法/抽象工厂)建造者模式原型模式.

单例模式

保证一个类只有一个实例, 并提供一个访问他的全局访问点.

实现

常见的单例模式实现方式有五种: 饿汉式, 懒汉式, 双重检测锁, 静态内部类, enum枚举.

此处输入图片的描述

1. 饿汉式

/**
 * 饿汉式
 * 问题: 如果只是加载本类, 而没有调用getInstance方法, 会造成资源浪费
 * Created by jifang on 15/12/4.
 */
public class HungerSingleton {
    /**
     * 类初始化时理解初始化该实例
     * 类加载时, 天然的线程安全时刻
     */
    private static final HungerSingleton instance = new HungerSingleton();
    private HungerSingleton() {
    }
    /**
     * 方法没有同步(synchronized), 调用效率高
     */
    public static HungerSingleton getInstance() {
        return instance;
    }
    @Override
    public String toString() {
        return "HungerSingleton{}";
    }
}

2. 懒汉式

/**
 * 懒汉式
 * 问题: 每次调用getInstance都要同步(synchronized), 效率降低
 * Created by jifang on 15/12/4.
 */
public class LazySingleton {
    /**
     * 类加载时并没初始化, 延迟加载
     */
    private static LazySingleton instance;
    private LazySingleton() {
    }
    /**
     * 注意synchronized, 线程安全
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    @Override
    public String toString() {
        return "LazySingleton{}";
    }
}

3. 双重检测锁

由于同步(synchronized)只在第一次实例化Instance时才需要,也就是单例类实例创建时, 因此我们使用双重检测锁(double checked locking pattern)实现:

/**
 * 双重锁定实现
 * 问题: 适用于JDK1.5之后的版本
 * Created by jifang on 15/12/4.
 */
public class DoubleCheckSingleton {
    /**
     * 需要使用volatile
     * 保证所有的写(write)都将先行发生于读(read)
     */
    private static volatile DoubleCheckSingleton instance;
    private DoubleCheckSingleton() {
    }
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {                          //Single Checked
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {                  // Double Checked
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
    @Override
    public String toString() {
        return "DoubleCheckSingleton{}";
    }
}

注: 有文章中指出 由于JVM底层内部模型原因, 双重锁定偶尔会出问题, 不建议使用, 但自1.5开始, 该问题已经被解决,因此可放心使用, 详见如何在Java中使用双重检查锁实现单例.

4. 静态内部类

/**
 * 静态内部类实现Singleton
 * Created by jifang on 15/12/4.
 */
public class StaticInnerSingleton {
    /**
     * 外部类没有static属性, 因此加载本类时不会立即初始化对象
     */
    private static class InnerClassInstance {
        private static final StaticInnerSingleton instance = new StaticInnerSingleton();
    }
    private StaticInnerSingleton() {
    }
    /**
     * 只有真正调用getInstance方法时, 才会加载静态内部类(延迟加载), 而且加载类是天然的线程安全的(线程安全), 没有synchronized(调用效率高)
     *
     * @return
     */
    public static StaticInnerSingleton getInstance() {
        return InnerClassInstance.instance;
    }
    @Override
    public String toString() {
        return "StaticInnerSingleton{}";
    }
}

5. 枚举

/**
 * 枚举实现单例
 * 基于JVM底层实现, Enum天然的单例以及线程安全
 * Created by jifang on 15/12/5.
 */
public enum EnumSingleton {
    /**
     * 构造方法默认为private
     */
    INSTANCE;
    /**
     * 可以添加其他操作
     * other operation
     */
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "EnumSingleton{" +
                "name='" + name + '\'' +
                '}';
    }
}

实现对比

方式 优点 缺点

饿汉式

线程安全, 调用效率高

不能延迟加载

懒汉式

线程安全, 可以延迟加载

调用效率不高

双重检测锁

线程安全, 调用效率高, 可以延迟加载

-

静态内部类

线程安全, 调用效率高, 可以延迟加载

-

枚举

线程安全, 调用效率高

不能延迟加载

破解与防御

反射破解单例

可以利用Java的反射机制破解单例模式(Enum无法破解, 由于基于JVM底层实现),下面仅破解双重检测锁, 其他类同不再赘述:

/**
 * 单例破解
 * Created by jifang on 15/12/4.
 */
public class TestCase {
    @Test
    public void testBreakDoubleCheck() {
        try {
            Class<DoubleCheckSingleton> clazz = (Class<DoubleCheckSingleton>) Class.forName("com.feiqing.singleton.DoubleCheckSingleton");
            Constructor<DoubleCheckSingleton> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            DoubleCheckSingleton instance1 = constructor.newInstance();
            DoubleCheckSingleton instance2 = constructor.newInstance();
            System.out.println("singleton? " + (instance1 == instance2));
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

序列化破解单例

public class LazySingleton implements Serializable{
    private static final long serialVersionUID = 8511876423469188139L;
    /**
     * 类加载时并没初始化, 延迟加载
     */
    private static LazySingleton instance;
    private LazySingleton() {
        if (instance != null){
            throw new RuntimeException();
        }
    }
    /**
     * 注意synchronized, 线程安全
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    @Override
    public String toString() {
        return "LazySingleton{}";
    }
}
public class TestCase {
    private static final String SYSTEM_FILE = "/tmp/save.txt";
    @Test
    public void testBreakLazy() {
        LazySingleton instance1 = LazySingleton.getInstance();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SYSTEM_FILE));
            oos.writeObject(instance1);
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SYSTEM_FILE));
            LazySingleton instance2 = (LazySingleton) ois.readObject();
            System.out.println("singleton? " + (instance1 == instance2));
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

序列化防御

/**
     * 反序列化时, 如果定义了readResolve方法, 则直接返回此方法制定的对象.
     *
     * @return
     */
    private Object readResolve() {
        return instance;
    }

详细可参考: 深入理解Java对象序列化.

性能测试

/**
 * 单例性能测试
 * Created by jifang on 15/12/4.
 */
public class TestCase {
    private static final String SYSTEM_FILE = "/tmp/save.txt";
    private static final int THREAD_COUNT = 10;
    private static final int CIRCLE_COUNT = 100000;
    @Test
    public void testSingletonPerformance() throws IOException, InterruptedException {
        final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        FileWriter writer = new FileWriter(new File(SYSTEM_FILE), true);
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; ++i) {
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            for (int i = 0; i < CIRCLE_COUNT; ++i) {
                                Object instance = HungerSingleton.getInstance();
                            }
                            latch.countDown();
                        }
                    }
            ).start();
        }
        latch.await();
        long end = System.currentTimeMillis();
        writer.append("HungerSingleton 共耗时: " + (end - start) + " 毫秒\n");
        writer.close();
    }
}

1

HungerSingleton

30 毫秒

2

LazySingleton

48 毫秒

3

DoubleCheckSingleton

25 毫秒

4

StaticInnerSingleton

16 毫秒

5

EnumSingleton

6 毫秒

Enum毫无疑问的成为了实现单例的王者, <Effective Java>中也推荐使用, 因此Enum成为Java中实现单例的最好方式, 但是Enum也有其自身的限制, 因此在使用时还需要做一番权衡.

小结

由于单例模式只生成一个实例, 减少了系统性能开销, 因此 当一个对象的产生需要比较多的资源时(如读取配置/产生其他依赖对象), 则可以通过只产生一个单例对象, 然后永久驻留内存的方式来提高系统整体性能.


原文出处: 工厂模式

工厂模式

工厂模式
用工厂方法代替了new操作, 将选择实现类, 创建对象统一管理和控制.从而将调用者(Client)与实现类进行解耦.实现了创建者与调用者分离;

静态工厂模式

静态工厂模式是工厂模式中最简单的一种,他可以用比较简单的方式隐藏创建对象的细节,一般只需要告诉工厂类所需要的类型,工厂类就会返回需要的产品类,而客户端看到的也只是产品的抽象对象(interface),因此无需关心到底是返回了哪个子类

/**
 * 运算符接口
 * Created by jifang on 15/12/7.
 */
public interface Operator<T> {
    T getResult(T... args);
}
public class AddOperator implements Operator<Integer> {
    @Override
    public Integer getResult(Integer... args) {
        int result = 0;
        for (int arg : args) {
            result += arg;
        }
        return result;
    }
}


public class MultiOperator implements Operator<Integer> {
    @Override
    public Integer getResult(Integer... args) {
        int result = 1;
        for (int arg : args) {
            result *= arg;
        }
        return result;
    }
}
/**
 * 静态工厂(注: 只返回产品的抽象[即接口])
 * 包含两种实现策略
 * 1. 根据传入的operator名进行实例化对象
 * 2. 直接调用相应的构造实例的方法
 * Created by jifang on 15/12/7.
 */
public class OperatorFactory {
    public static Operator<Integer> createOperator(String operName) {
        Operator<Integer> operator;
        switch (operName) {
            case "+":
                operator = new AddOperator();
                break;
            case "*":
                operator = new MultiOperator();
                break;
            default:
                throw new RuntimeException("Wrong Operator Name: " + operName);
        }
        return operator;
    }
    /* ** 第二种实现策略 ** */
    public static Operator<Integer> createAddOper() {
        return new AddOperator();
    }
    public static Operator<Integer> createMultiOper() {
        return new MultiOperator();
    }
}
public class Client {
    @Test
    public void testAdd() {
        Operator<Integer> operator = OperatorFactory.createOperator("+");
        System.out.println(operator.getResult(1, 2, 3, 4, 6));
    }
    @Test
    public void testMultiplication() {
        Operator<Integer> operator = OperatorFactory.createOperator("*");
        System.out.println(operator.getResult(1, 2, 3, 4, 6));
    }
    @Test
    public void testAddName(){
        Operator<Integer> operator = OperatorFactory.createAddOper();
        System.out.println(operator.getResult(1, 2, 3, 4, 6));
    }
    @Test
    public void testMultiplicationName() {
        Operator<Integer> operator = OperatorFactory.createMultiOper();
        System.out.println(operator.getResult(1, 2, 3, 4, 6));
    }
}

工厂方法模式

由于静态工厂方法模式不满足OCP, 因此就出现了工厂方法模式; 工厂方法模式和静态工厂模式最大的不同在于:_静态工厂_模式只有一个(对于一个项目/独立模块)只有一个工厂类, 而_工厂方法_模式则有一组实现了相同接口的工厂类.

/**
 * Created by jifang on 15/12/7.
 */
public interface Factory<T> {
    Operator<T> createOperator();
}
/**
 * 加法运算符工厂
 * Created by jifang on 15/12/7.
 */
public class AddFactory implements Factory<Integer> {
    @Override
    public Operator<Integer> createOperator() {
        return new AddOperator();
    }
}


/**
 * 乘法运算符工厂
 * Created by jifang on 15/12/7.
 */
public class MultiFactory implements Factory<Integer> {
    @Override
    public Operator<Integer> createOperator() {
        return new MultiOperator();
    }
}

Operator, AddOperatorMultiOperator与上例相同.此时, 如果要在_静态工厂_中新增加一个开根运算类, 要么需要在createOperator方法中增加一种case,要么得增加一个createSqrtOper方法, 都是需要修改原来的代码的. 而在_工厂方法_中只需要再添加一个SqrtFactory即可:

/**
 * 开根运算符
 * Created by jifang on 15/12/7.
 */
public class SqrtOperator implements Operator<Double> {
    @Override
    public Double getResult(Double... args) {
        if (args != null && args.length >= 1) {
            return Math.sqrt(args[0]);
        } else {
            throw new RuntimeException("Params Number Error " + args.length);
        }
    }
}


/**
 * 开根工厂
 * Created by jifang on 15/12/7.
 */
public class SqrtFactory implements Factory<Double> {
    @Override
    public Operator<Double> createOperator() {
        return new SqrtOperator();
    }
}

优点
基本与静态工厂模式一致,多的一点优点就是遵循了开放-封闭原则,使得模式的灵活性更强。

缺点
静态工厂模式差不多, 但是增加了类组织的复杂性;

小结
虽然根据理论原则, 需要使用工厂方法模式, 但实际上, 常用的还是静态工厂模式.


抽象工厂模式

抽象工厂模式: 提供一个创建一系列相关或相互依赖对象的接口, 而无需指定他们具体的类.

抽象工厂模式与工厂方法模式的区别:

在抽象工厂模式中,提出了产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族(如Engine, Tyre, Seat)。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构.

示例:

LuxuryCarFactoryLowCarFactory分别代表一类产品族的两款产品, 类似于数据库产品族中有MySQL, Oracle, SqlServer

1. 产品
public interface Engine {
    void start();
    void run();
}
class LowEngine implements Engine {
    @Override
    public void start() {
        System.out.println("启动慢 ...");
    }
    @Override
    public void run() {
        System.out.println("转速慢 ...");
    }
}
class LuxuryEngine implements Engine {
    @Override
    public void start() {
        System.out.println("启动快 ...");
    }
    @Override
    public void run() {
        System.out.println("转速快 ...");
    }
}
public interface Tyre {
    void revolve();
}
class LowTyre implements Tyre {
    @Override
    public void revolve() {
        System.out.println("旋转 - 不耐磨 ...");
    }
}
class LuxuryTyre implements Tyre {
    @Override
    public void revolve() {
        System.out.println("旋转 - 不磨损 ...");
    }
}

注意: 其中并没有车类

2. 产品族Factory
/**
 * Created by jifang on 15/12/7.
 */
public interface CarFactory {
    Engine createEngine();
    Seat createSeat();
    Tyre createTyre();
}
public class LowCarFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LowEngine();
    }
    @Override
    public Seat createSeat() {
        return new LowSeat();
    }
    @Override
    public Tyre createTyre() {
        return new LowTyre();
    }
}
public class LuxuryCarFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LuxuryEngine();
    }
    @Override
    public Seat createSeat() {
        return new LuxurySeat();
    }
    @Override
    public Tyre createTyre() {
        return new LuxuryTyre();
    }
}
3. Client
/**
 * Created by jifang on 15/12/7.
 */
public class Client {
    @Test
    public void testLow(){
        CarFactory factory = new LowCarFactory();
        Engine engine = factory.createEngine();
        engine.start();
        engine.run();
        Seat seat = factory.createSeat();
        seat.massage();
        Tyre tyre = factory.createTyre();
        tyre.revolve();
    }
    @Test
    public void testLuxury(){
        CarFactory factory = new LuxuryCarFactory();
        Engine engine = factory.createEngine();
        engine.start();
        engine.run();
        Seat seat = factory.createSeat();
        seat.massage();
        Tyre tyre = factory.createTyre();
        tyre.revolve();
    }
}

使用静态工厂优化抽象工厂

由于抽象工厂模式存在结构臃肿以及改动复杂的缺点(比如我们每次需要构造Car, 都需要进行CarFactory factory = new XxxCarFactory();, 而一般一个项目中只会生产一种Car, 如果我们需要更改生产的车的类型, 那么客户端的每一处调用都需要修改),因此我们可以使用静态工厂对其进行改造, 我们使用CarCreator来统一创建一个产品族不同产品, 这样如果我们的工厂将来更改了产品路线,改为生产高端车时, 我们仅需改变CAR_TYEP的值就可以了:

/**
 * Created by jifang on 15/12/7.
 */
public class CarCreator {
    private static final String CAR_TYPE = "low";
    private static final String CAR_TYPE_LOW = "low";
    private static final String CAR_TYPE_LUXURY = "luxury";
    public static Engine createEngine() {
        Engine engine = null;
        switch (CAR_TYPE) {
            case CAR_TYPE_LOW:
                engine = new LowEngine();
                break;
            case CAR_TYPE_LUXURY:
                engine = new LuxuryEngine();
                break;
        }
        return engine;
    }
    public static Seat createSeat() {
        Seat seat = null;
        switch (CAR_TYPE) {
            case CAR_TYPE_LOW:
                seat = new LowSeat();
                break;
            case CAR_TYPE_LUXURY:
                seat = new LuxurySeat();
                break;
        }
        return seat;
    }
    public static Tyre createTyre() {
        Tyre tyre = null;
        switch (CAR_TYPE) {
            case CAR_TYPE_LOW:
                tyre = new LowTyre();
                break;
            case CAR_TYPE_LUXURY:
                tyre = new LuxuryTyre();
                break;
        }
        return tyre;
    }
}

其实我们还可以通过反射, 将CarCreator中的switch-case去掉, 而且在实际开发中, 字符串的值我们还可以从配置文件中读取, 这样, 如果需要更改产品路线, 我们连程序代码都懒得改了, 只需要修改配置文件就可以了.

/**
 * Created by jifang on 15/12/7.
 */
public class CarCreatorReflect {
    /**
     * 在实际开发中, 下面这些常量可以从配置文件中读取
     */
    private static final String PACKAGE = "com.feiqing.abstractfactory";
    private static final String ENGINE = "LuxuryEngine";
    private static final String TYRE = "LuxuryTyre";
    private static final String SEAT = "LuxurySeat";
    public static Engine createEngine() {
        String className = PACKAGE + "." + ENGINE;
        try {
            return (Engine) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static Seat createSeat() {
        String className = PACKAGE + "." + SEAT;
        try {
            return (Seat) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static Tyre createTyre() {
        String className = PACKAGE + "." + TYRE;
        try {
            return (Tyre) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这样, 客户端调起来就清爽多了

/**
 * Created by jifang on 15/12/7.
 */
public class StaticClient {
    @Test
    public void testLow() {
        Engine engine = CarCreator.createEngine();
        engine.run();
        engine.start();
        Seat seat = CarCreator.createSeat();
        seat.massage();
        Tyre tyre = CarCreator.createTyre();
        tyre.revolve();
    }
    @Test
    public void testLuxury() {
        Engine engine = CarCreatorReflect.createEngine();
        engine.run();
        engine.start();
        Seat seat = CarCreatorReflect.createSeat();
        seat.massage();
        Tyre tyre = CarCreatorReflect.createTyre();
        tyre.revolve();
    }
}

小结

分类 说明

静态工厂模式

用来生成同一等级结构中的任意产品, 对于增加新的产品, 需要修改已有代码

工厂方法模式

用来生成同一等级结构的固定产品, 支持增加任意产品;

抽象工厂模式

用来生成不同产品族的全部产品, 对于增加新的产品无能为力;


原文出处:原型模式

原型模式

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

原型模式就是通过一个对象再创建另一个可定制的对象, 而且不需要知道任何创建的细节. 因此, 原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。标准的原型模式的URL类图为:

但由于Java类库已经帮我们实现了一个java.lang.Cloneable接口, 因此我们的实现就简单了些,下面我们模仿多利羊的诞生过程:

/**
 * 该羊支持克隆
 * (浅复制)
 * Created by jifang on 15/12/9.
 */
public class Sheep implements Cloneable {
    private String name;
    private Date birthday;
    public Sheep() {
    }
    public Sheep(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}';
    }
    @Override
    protected Sheep clone() throws CloneNotSupportedException {
        return (Sheep) super.clone();
    }
}
public class Client {
    @Test
    public void client() throws CloneNotSupportedException {
        Date birthday = new Date();
        Sheep mother = new Sheep("多利母亲", birthday);
        System.out.println(mother);
        Sheep dolly = mother.clone();
        dolly.setName("多利");
        System.out.println(dolly);
    }
}

浅复制与深复制

@Test
public void client() throws CloneNotSupportedException {
    Date birthday = new Date();
    Sheep mother = new Sheep("多利母亲", birthday);
    System.out.println(mother);
    Sheep dolly = mother.clone();
    dolly.setName("多利");
    System.out.println(dolly);
    birthday.setTime(123123123123L);
    System.out.println("dolly birthday: " + dolly.getBirthday());
}

运行上面的代码我们可以看到更改了mother的birthday, dolly的birthday也更改了. 这个问题是由浅复制引起的.

浅复制:
创建当前对象的浅复制版本, 方法是创建一个新对象, 然后将当前对象的非静态字段复制到该新对象. 如果字段是值类型, 则对该字段执行逐位复制; 如果字段是引用类型, 则复制引用但不复制引用的对象; 因此原始对象及其副本引用同一对象;

此时运行的内存简化图如下:

可以看到mother和dolly的birthday指向同一个date对象.

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

@Override
protected Sheep clone() throws CloneNotSupportedException {
    Sheep sheep = (Sheep) super.clone();
    // 添加下面代码支持深复制
    sheep.birthday = (Date) this.birthday.clone();
    return sheep;
}

因此我们把Sheep的clone方法改成上面实现后, 再调用client方法就不会出现问题了, 此时的内存简图如下:

@Override
protected Sheep clone() throws CloneNotSupportedException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try {
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        return (Sheep) ois.readObject();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}

原型模式性能测试

假设创建Sheep是比较耗时的, 在此基础上进行原型模式的额性能测试:

/**
 * 模拟耗时的对象创建过程
 *
 * @param name
 * @param birthday
 */
public Sheep(String name, Date birthday) {
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    this.name = name;
    this.birthday = birthday;
}
public class Client {
    private static final int OBJECT_COUNT = 1000;
    private long testNew() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < OBJECT_COUNT; ++i) {
            Sheep sheep = new Sheep("name", new Date());
        }
        return System.currentTimeMillis() - start;
    }
    private long testPrototype() throws CloneNotSupportedException {
        long start = System.currentTimeMillis();
        Sheep sheep = new Sheep("name", new Date());
        for (int i = 0; i < OBJECT_COUNT; ++i) {
            Sheep newSheep = sheep.clone();
            newSheep.setName("new name");
        }
        return System.currentTimeMillis() - start;
    }
    @Test
    public void client() throws CloneNotSupportedException {
        System.out.println(testNew());
        System.out.println(testPrototype());
    }
}

运行上面程序, 可以很明显看出new与原型模式的性能差异, 如果吧Sheep构造器中的sleep注释掉, new与clone虽然有差异, 但是差距较小.

应用场景:


原文出处:建造者模式

建造者模式: 又称生成器模式, 可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示).这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节.

实现

需求: 模仿宇宙飞船的建造过程
    假设宇宙飞船有很多零部件: 引擎轨道舱逃逸塔各种小零件... 因此宇宙飞船的建造/装配非常复杂(需要很好的生产/装配技术),而建造者模式可以将部件的建造与装配分开:

产品与部件

产品AirShip由多个零部件(Engine/EscapeTower/OrbitalModule)组成:

/**
 * 目标对象 - 宇宙飞船
 * (代表复杂对象, 拥有复杂的建造过程)
 * Created by jifang on 15/12/8.
 */
public class AirShip {
    private Engine engine;
    private EscapeTower escapeTower;
    private OrbitalModule orbitalModule;
    public Engine getEngine() {
        return engine;
    }
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
    public EscapeTower getEscapeTower() {
        return escapeTower;
    }
    public void setEscapeTower(EscapeTower escapeTower) {
        this.escapeTower = escapeTower;
    }
    public OrbitalModule getOrbitalModule() {
        return orbitalModule;
    }
    public void setOrbitalModule(OrbitalModule orbitalModule) {
        this.orbitalModule = orbitalModule;
    }
    @Override
    public String toString() {
        return "AirShip{" +
                "engine=" + engine +
                ", escapeTower=" + escapeTower +
                ", orbitalModule=" + orbitalModule +
                '}';
    }
}
class Engine {
    private String description;
    public Engine(String description) {
        this.description = description;
    }
    @Override
    public String toString() {
        return "Engine{" +
                "description='" + description + '\'' +
                '}';
    }
}
class EscapeTower {
    private String description;
    public EscapeTower(String description) {
        this.description = description;
    }
    @Override
    public String toString() {
        return "EscapeTower{" +
                "description='" + description + '\'' +
                '}';
    }
}
class OrbitalModule {
    private String description;
    public OrbitalModule(String description) {
        this.description = description;
    }
    @Override
    public String toString() {
        return "OrbitalModule{" +
                "description='" + description + '\'' +
                '}';
    }
}

建造者(Builder)

Builder(AirShipBuilder)是为创建一个Product对象的各个部件指定的抽象接口,ConcreteBuilder(LowerAirShipBuilder/HigherAirShipBuilder)是具体的建造者,实现Builder接口, 构造和装配各个部件.

/**
 * @author jifang
 * @since 16/8/17 下午2:13.
 */
public interface AirShipBuilder {
    void builtEngine();
    void builtEscapeTower();
    void builtOrbitalModule();
    AirShip getResult();
}

生产低端飞船, 需要LowerAirShipBuilder; 生产高端飞船, 就需要HigherAirShipBuilder:

class LowerAirShipBuilder implements AirShipBuilder {
    private AirShip airShip = new AirShip();
    @Override
    public void builtEngine() {
        System.out.println("\t\t构造低端引擎");
        airShip.setEngine(new Engine("低端 - 引擎"));
    }
    @Override
    public void builtEscapeTower() {
        System.out.println("\t\t构造低端逃逸塔");
        airShip.setEscapeTower(new EscapeTower("低端 - 逃逸塔"));
    }
    @Override
    public void builtOrbitalModule() {
        System.out.println("\t\t构造低端轨道舱");
        airShip.setOrbitalModule(new OrbitalModule("低端 - 轨道舱"));
    }
    @Override
    public AirShip getResult() {
        return airShip;
    }
}
class HigherAirShipBuilder implements AirShipBuilder {
    private AirShip airShip = new AirShip();
    @Override
    public void builtEngine() {
        System.out.println("\t\t构造高端引擎");
        airShip.setEngine(new Engine("高端 - 引擎"));
    }
    @Override
    public void builtEscapeTower() {
        System.out.println("\t\t构造高端逃逸塔");
        airShip.setEscapeTower(new EscapeTower("高端 - 逃逸塔"));
    }
    @Override
    public void builtOrbitalModule() {
        System.out.println("\t\t构造高端轨道舱");
        airShip.setOrbitalModule(new OrbitalModule("高端 - 轨道舱"));
    }
    @Override
    public AirShip getResult() {
        return airShip;
    }
}

指挥者(Director)

使用Director(AirShipDirector)控制建造过程, 也用它来隔离用户与建造过程的关联:

/**
 * @author jifang
 * @since 16/8/17 下午2:15.
 */
public class AirShipDirector {
    /**
     * 确定一种稳定的构造过程
     *
     * @param builder
     */
    public static void construct(AirShipBuilder builder) {
        builder.builtEngine();
        builder.builtEscapeTower();
        builder.builtOrbitalModule();
    }
}

Client

完全不需知道具体的创建/装配过程, 只需指定Builder:

public class Client {
    @Test
    public void client() {
        AirShipBuilder lowBuilder = new LowerAirShipBuilder();
        // 构造低端飞船
        AirShipDirector.construct(lowBuilder);
        AirShip lowShip = lowBuilder.getResult();
        System.out.println(lowShip);
        AirShipBuilder highBuilder = new HigherAirShipBuilder();
        // 相同的构造过程, 不同的Builder, 可以构造出不同的飞船
        AirShipDirector.construct(highBuilder);
        AirShip highShip = highBuilder.getResult();
        System.out.println(highShip);
    }
}

实例-MyBatis中的建造者模式

MyBatis的SqlSessionFactoryBuilder是对SqlSessionFactory建造过程的简单封装,他对建造者模式做了简化处理(只有Builder而无Director),以减小编程复杂度:

/**
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }
  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }
  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

由于SqlSessionFactory创建的SqlSession需要支持很多操作(如selectOne()selectList()update()等), 因此SqlSessionFactory的构造过程是非常复杂的(可参考SqlSessionManagerDefaultSql SessionFactory实现),因此使用SqlSessionFactoryBuilder作为Builder简化其构造过程,并且为其设置配置文件(mybatis-configuration.xml)作为Director来指导Builder的构造过程:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mybatis/mapper/*DAO.xml"/>
    <property name="typeAliases" value="com.feiqing.domain.User"/>
    <property name="configLocation" value="classpath:mybatis/mybatis-configuration.xml"/>
</bean>

详细可参考博客: mybatis源码分析(1)——SqlSessionFactory实例的产生过程

小结


原文出处:适配器模式

适配器模式

结构型模式

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低;

结构型模式共有7个可供研究,它们分别是: 适配器模式, 代理模式, 桥接模式, 装饰者模式, 组合模式, 外观模式, 享元模式;

适配器模式

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

适配器模式中的角色:

实现

/**
 * Created by jifang on 15/12/10.
 */
public class Adaptee {
    public void specificRequest() {
        System.out.println("特殊请求");
    }
}

* 目标`接口(Target)`


public interface Target {
    void request();
}
public class Adapter implements Target {
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    @Override
    public void request() {
        adaptee.specificRequest();
    }
}
public class Client {
    @Test
    public void client() {
        Target target = new Adapter(new Adaptee());
        target.request();
    }
}

小结


原文出处:代理模式

代理模式

为其他对象提供一种代理以控制对这个对象的访问(可以详细控制访问某个对象的方法, 在调用这个方法[前/后]做[前/后]置处理,从而实现将统一流程放到代理类中处理).

静态代理

我们模拟请明星唱歌_这个过程,但大家都知道要请明星唱歌(比如周杰伦)是一件比较麻烦的事情, 比如唱歌前要_签约, 唱歌之后还有收款,而平时明星们都是比较忙的, 想签约, 收款这些事情一般都是由他的助手来代理完成的,而明星只负责唱歌就行了,像_签约_与_收款_这种事情就可以算作是明星的增强, 虽然这不是明星的主要目的, 但是这个流程是必须要有的.

/**
 * 定义真实对象和代理对象的公共接口
 * Created by jifang on 15/12/20.
 */
public interface Star {
    // 签约
    void signContract();
    // 唱歌
    void singSong();
    // 收款
    void collectMoney();
}
public class RealStar implements Star {
    /**
     * 由于这些事情都委托给代理来做了, 因此我们只是象征性实现就好了
     */
    @Override
    public void signContract() {
    }
    @Override
    public void collectMoney() {
    }
    /**
     * 但唱歌是要自己真唱的
     */
    @Override
    public void singSong() {
        System.out.println("周杰伦在唱歌");
    }
}
public class StaticProxy implements Star {
    private Star star;
    public StaticProxy(Star star) {
        this.star = star;
    }
    @Override
    public void signContract() {
        System.out.println("代理签约");
    }
    /**
     * 代理可以帮明星做任何事, 但唯独唱歌这件事必须由Star自己来完成
     */
    @Override
    public void singSong() {
        star.singSong();
    }
    @Override
    public void collectMoney() {
        System.out.println("代理收钱");
    }
}
public class Client {
    @Test
    public void client() {
        Star star = new StaticProxy(new RealStar());
        star.signContract();
        star.singSong();
        star.collectMoney();
    }
}

可以看出,客户实际想要调用的是RealStarsingSong方法,现在用StaticProxy来代理RealStar类,也可以达到同样的目的,同时还封装了其他方法(像singContract``collectMoney),可以处理一些其他流程上的问题.
如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性;但是实际的Java应用中,如果有_一批_真实对象, 而毎个代理对象只对应一个真实对象的话,会导致类的急剧膨胀;此外,如果我们事先并不知道真实角色,那么该如何使用编写代理类呢?这个问题可以通过java的动态代理机制来解决.

动态代理

所谓_动态代理_是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface.

JDK对动态代理提供了以下支持:

首先, Star接口可以精简一下, 只做他该做的事情:

/**
 * Star只负责唱歌就行了
 * Created by jifang on 15/12/20.
 */
public interface Star {
    // 唱歌
    void singSong();
}
public class RealStar implements Star {
    /**
     * 唱歌是要自己真唱的
     */
    @Override
    public void singSong() {
        System.out.println("周杰伦在唱歌");
    }
}

当执行动态代理对象里的方法时, 实际上会替换成调用InvocationHandler中的invoke方法.

/**
 * 相当于原先的代理需要执行的方法
 * Created by jifang on 15/12/20.
 */
public class ProxyHandler implements InvocationHandler {
    private Star star;
    public ProxyHandler(Star star) {
        this.star = star;
    }
    /**
     * 代理对象的实现的所有接口中的方法, 内容都是调用invoke方法
     *
     * @param proxy  代理对象(Proxy.newProxyInstance返回的对象)
     * @param method 当前被调的方法
     * @param args   执行当前方法的参数
     * @return 执行方法method的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("签约");
        Object result = null;
        if (method.getName().equals("singSong")) {
            result = method.invoke(star, args);
        }
        System.out.println("收款");
        return result;
    }
}
public class Client {
    @Test
    public void client() {
        /**
         * newProxyInstance方法会动态生成一个代理类, 他实现了Star接口, 然后创建该类的对象.
         *
         * 三个参数
         * 1. ClassLoader: 生成一个类, 这个类也需要加载到方法区中, 因此需要指定ClassLoader来加载该类
         * 2. Class[] interfaces: 要实现的接口
         * 3. InvocationHandler: 调用处理器
         */
        Star proxyStar = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, new ProxyHandler(new RealStar()));
        proxyStar.singSong();
    }
}

代理工厂实现动态代理

/**
 * Created by jifang on 15/12/21.
 */
public class ProxyFactory {
    private BeforeAdvice beforeAdvice;
    private Object targetObject;
    private AfterAdvice afterAdvice;
    public ProxyFactory() {
    }
    public ProxyFactory(BeforeAdvice beforeAdvice, Object targetObject, AfterAdvice afterAdvice) {
        this.beforeAdvice = beforeAdvice;
        this.targetObject = targetObject;
        this.afterAdvice = afterAdvice;
    }
    private InvocationHandler handler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (beforeAdvice != null) {
                beforeAdvice.before();
            }
            Object result = null;
            if (targetObject != null) {
                result = method.invoke(targetObject, args);
            }
            if (afterAdvice != null) {
                afterAdvice.after();
            }
            return result;
        }
    };
    public Object createProxy() {
        return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), targetObject.getClass().getInterfaces(), handler);
    }
}
/**
 * Created by jifang on 15/12/20.
 */
public class Client {
    @Test
    public void client() {
        Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
        star.singSong();
    }
    /**
     * BeforeAdvice实现可定制化
     */
    private static class StarBeforeAdvice implements BeforeAdvice {
        @Override
        public void before() {
            System.out.println("签合约");
        }
    }
    /**
     * AfterAdvice实现可定制化
     */
    private static class StarAfterAdvice implements AfterAdvice {
        @Override
        public void after() {
            System.out.println("收款");
        }
    }
}

现在, 我们的对明星要求比较高了, 他不光要会唱歌, 还要会跳舞.

public interface Star {
    // 唱歌
    void singSong();
    // 跳舞
    void dancing();
}


public class RealStar implements Star {
    //...
    @Override
    public void dancing() {
        System.out.println("周杰伦在跳舞...");
    }
}

此时, 我们的client什么都不需要改, 只是添加一个调用就可:

public class Client {
    @Test
    public void client() {
        Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
        star.singSong();
        star.dancing();
    }
    // ...
}

而且在实际开发中, 这些增强类还可以从配置文件中读取(像Spring).
这种代理在AOP(Aspect Orient Programming: 面向切面编程)中被成为AOP代理,AOP代理包含了目标对象的全部方法,但AOP代理中的方法与目标对象的方法存在差异: 比如可以在执行目标方法之前/后插入一些通用的处理(增强).

代理场景


原文出处:桥接模式

桥接模式

场景

在商城系统中商品是分类摆放的,以电脑为例我们有以下商品分类, 该如何良好的处理商品分类销售的问题:

直观上我们会认为该商品分类以继承来实现:电脑作为根类,台式机/笔记本/平板电脑作为其子类,联想台式机/…作为电脑的孙类.(其继承结构可以从图上直观的 看出),但是考虑以下需求:

  1. 如果我们要增加一个品牌三星?
  2. 如果我们要增加一个分类智能手机?

问题1的解决方案是在台式机 笔记本 平板电脑 下面都添加相应的子类, 三星台式机 三星笔记本 ….
问题2的解决方案是需在_电脑_下面添加子类_智能手机_并在_智能手机_下在添加三个品牌分类联想智能手机 戴尔只能手机 … 额,想想就够麻烦的,但是如果我们既要添加品牌又要添加分类呢? 要疯了…
这样下去导致的后果就是类的个数急速膨胀, 管理成本极高.

我们马上就意识到仅用继承的是不行的了:

对象的继承关系是在编译时就确定好了的,所以无法在运行时改变从父类继承的实现.子类的实现与他的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化.当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换.这种依赖关系限制了灵活性并最终限制了复用性.

这个问题仅用继承是不能解决的,因为这样其实是违反了单一职责原则:一个类联想笔记本,却有两个引起这个类变化的原因-类型维度品牌维度.我们把两个维度混合在一起考虑,必然会造成牵一发而动全身的效果.

类型维度有自己的一套继承结构,品牌维度也有自己的一套继承结构,然后中间有座桥(Bridge)把这两个类关联起来.这样在添加品牌时只需在类型维度做修改就好了, 不会影响到品牌维度;当在品牌维度搞活动时,类型维度也不受影响.桥一端的变化不会引起另一端的变化,这就是桥接模式:

桥接模式

桥接模式: 将抽象部分与它的实现部分分离, 使他们都可以独立地变化.
其实就是处理多层继承结构, 处理多维变化的场景, 将各个维度设计成独立地继承结构, 使得各个维度可以独立地扩展, 并在抽象层建立关联.

注意, 这个结构的关键是: Abstraction里面持有Implementor对象.
这个反映到我们卖电脑的场景的类图关系如下:

Computerbrand属性就是那座.

/**
 * @author jifang
 * @since 16/1/3下午6:25.
 */
public interface Brand {
    String brand();
}
class Lenovo implements Brand {
    @Override
    public String brand() {
        return "联想";
    }
}
class Dell implements Brand {
    @Override
    public String brand() {
        return "戴尔";
    }
}
class Hasee implements Brand {
    @Override
    public String brand() {
        return "神州";
    }
}
/**
 * @author jifang
 * @since 16/1/3下午6:33.
 */
public abstract class Computer {
    private Brand brand;
    public Computer(Brand brand) {
        this.brand = brand;
    }
    public abstract String type();
    public void sale() {
        System.out.println("我们卖的是<" + brand.brand() + this.type() + ">电脑");
    }
}
class Desktop extends Computer {
    public Desktop(Brand brand) {
        super(brand);
    }
    @Override
    public String type() {
        return "台式";
    }
}
class Laptop extends Computer {
    public Laptop(Brand brand) {
        super(brand);
    }
    @Override
    public String type() {
        return "笔记本";
    }
}
class Pad extends Computer {
    public Pad(Brand brand) {
        super(brand);
    }
    @Override
    public String type() {
        return "平板";
    }
}
public class Client {
    @Test
    public void test() {
        Computer computer = new Desktop(new Dell());
        computer.sale();
    }
}

现在我要新加一个_智能手机_类型, 那么只需在Computer下面添加一个Smartphone就行了, 品牌维度不需要做任何的改动:

class Smartphone extends Computer {
    public Smartphone(Brand brand) {
        super(brand);
    }
    @Override
    public String type() {
        return "智能手机";
    }
}
public class Client {
    @Test
    public void test() {
        Computer computer = new Smartphone(new Lenovo());
        computer.sale();
    }
}

小结

桥接模式在实际开发中应用场景:

其实只要发现需要从多个角度去分类实现对象, 而只用继承会造成大量类的增加,不能满足开放- 封闭原则时,就应该考虑使用桥接模式.


原文出处:装饰者模式

装饰者模式

装饰者模式(Decorator): 又称包装器(Wrapper), 可以动态地为一个对象添加一些额外的职责. 就增加功能来说,装饰者模式是一种用于替代继承的技术, 他无须通过增加子类继承就能扩展对象的已有功能, 而是使用对象的关联关系代替继承关系 , 更加灵活,同时还可避免类型体系的快速膨胀.

组件 描述 I/O示例

Component 抽象构件角色, 真实对象和装饰对象的共有接口. 这样,客户端就能以调用真实对象的相同方式装饰对象交互.

InputStream/OutputStream

ConcreteComponent 具体构件角色,真实对象

FileInputStream/FileOutputStream

Decorator装饰抽象类, 实现了Component, 并持有一个Component引用, 接受所有客户端请求,并将请求转发给真实对象,这样,就能在真实对象调用的前后增强新功能. 但对于Component来说, 是无需知道Decorator存在的.

FilterInputStream/FilterOutputStream

ConcreteDecorator 具体装饰角色,完成对Component的具体增强.

BufferedInputStream/BufferedOutputStream

是你还有你, 一切拜托你.(图片来源: 《JAVA与模式》之装饰模式)

实现

/**
 * @author jifang
 * @since 16/8/20 下午5:55.
 */
public interface Component {
    void operator();
}
class ConcreteComponent implements Component {
    @Override
    public void operator() {
        System.out.println("具体对象" + this.toString() + "的操作");
    }
}
public abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) {
        this.component = component;
    }
    @Override
    public abstract void operator();
}
class BeforeAdviceDecorator extends Decorator {
    public BeforeAdviceDecorator(Component component) {
        super(component);
    }
    @Override
    public void operator() {
        System.out.println(" -> 前置增强");
        this.component.operator();
    }
}
class AfterAdviceDecorator extends Decorator {
    public AfterAdviceDecorator(Component component) {
        super(component);
    }
    @Override
    public void operator() {
        this.component.operator();
        System.out.println("后置增强 -> ");
    }
}
public class Client {
    @Test
    public void client() {
        // 裸Component
        Component component = new ConcreteComponent();
        component.operator();
        // 前置增强
        component = new BeforeAdviceDecorator(component);
        component.operator();
        // + 后置增强
        component = new AfterAdviceDecorator(component);
        component.operator();
    }
}

注: 如果只有ConcreteComponent而没有抽象的Component,那么Decorator可直接继承ConcreteComponent. 同样, 如果只有一个ConcreteDecorator,那就没有必要建立一个独立的Decorator, 将DecoratorConcreteDecorator的职责合并.

小结

对象

被增强对象不能变

被增强对象可变

被增强对象可变

内容

增强内容不能变

增强内容不可变

增强内容可变


原文出处:中介者模式

中介者模式

面向对象设计鼓励将行为分布到各个对象中, 这种分布可能会导致对象间有许多连接. 在最坏的情况下, 每一个对象都需要知道其他所有对象.
虽然将一个系统分割成许多对象可增强可复用性, 但是对象间相互连接的激增又会降低其可复用性.大量的连接关系使得一个对象不可能在没有其他对象的协助下工作(系统表现为一个不可分割的整体),此时再对系统行为进行任何较大改动就十分困难. 因为行为被分布在许多对象中, 结果是不得不定义很多子类以定制系统的行为.由此我们引入了中介者对象Mediator:

通过中介者对象, 可以将网状结构的系统改造成以中介者为中心的星型结构, 每个具体对象不再与另一个对象直接发生关系,而是通过中介者对象从中调停.中介者对象的引入,也使得系统结构不会因新对象的引入造成大量的修改.

中介者模式

中介者模式: 又称调停者模式, 用一个中介者对象(Mediator)来封装一系列对象的交互, 使各对象不需再显示地相互引用,从而使耦合松散, 而且可以独立地改变他们之间的交互:

(图片来源: 设计模式: 可复用面向对象软件的基础)

模式实现

联合国转发各国声明, 调停各国关系: 
各国向联合国安理会发送和接收消息, 安理会在各国间'适当地'转发请求以实现协作行为:

Colleague

抽象同事类, 定义各同事的公有方法:

/**
 * @author jifang
 * @since 16/8/28 下午4:22.
 */
public abstract class Country {
    protected UnitedNations mediator;
    private String name;
    public Country(UnitedNations mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    public String getName() {
        return name;
    }
    protected abstract void declare(String msg);
    protected abstract void receive(String msg);
}

ConcreteColleague

具体同事类:

class USA extends Country {
    public USA(UnitedNations mediator, String name) {
        super(mediator, name);
    }
    @Override
    public void declare(String msg) {
        mediator.declare(this, msg);
    }
    @Override
    public void receive(String msg) {
        System.out.println("美国接收到: [" + msg + "]");
    }
}
class Iraq extends Country {
    public Iraq(UnitedNations mediator, String name) {
        super(mediator, name);
    }
    @Override
    public void declare(String msg) {
        mediator.declare(this, msg);
    }
    @Override
    public void receive(String msg) {
        System.out.println("伊拉克接收到: [" + msg + "]");
    }
}
class China extends Country {
    public China(UnitedNations mediator, String name) {
        super(mediator, name);
    }
    @Override
    public void declare(String msg) {
        mediator.declare(this, msg);
    }
    @Override
    public void receive(String msg) {
        System.out.println("中国接收到: [" + msg + "]");
    }
}

Mediator

抽象中介者: 定义一个接口用于与各同事对象通信:

public abstract class UnitedNations {
    protected List<Country> countries = new LinkedList<>();
    public void register(Country country) {
        countries.add(country);
    }
    public void remove(Country country) {
        countries.remove(country);
    }
    protected abstract void declare(Country country, String msg);
}

ConcreteMediator

具体中介者:

class UnitedNationsSecurityCouncil extends UnitedNations {
    /**
     * 安理会在中间作出调停
     *
     * @param country
     * @param msg
     */
    @Override
    protected void declare(Country country, String msg) {
        for (Country toCountry : countries) {
            if (!toCountry.equals(country)) {
                String name = country.getName();
                toCountry.receive(name + "平和的说: " + msg);
            }
        }
    }
}

如果不存在扩展情况, 那么Mediator可与ConcreteMediator合二为一.

public class Client {
    @Test
    public void client() {
        UnitedNations mediator = new UnitedNationsSecurityCouncil();
        Country usa = new USA(mediator, "美国");
        Country china = new China(mediator, "中国");
        Country iraq = new Iraq(mediator, "伊拉克");
        mediator.register(usa);
        mediator.register(china);
        mediator.register(iraq);
        usa.declare("我要打伊拉克, 谁管我跟谁急!!!");
        System.out.println("----------");
        china.declare("我们强烈谴责!!!");
        System.out.println("----------");
        iraq.declare("来呀, 来互相伤害呀!!!");
    }
}

小结

Mediator的出现减少了各Colleague之间的耦合,使得可以独立改变和复用各ColleagueMediator,由于把对象如何协作进行了抽象将中介作为一个独立的概念并将其封装在一个对象中,这样关注的焦点就从对象各自本身的行为转移到它们之间的交互上来, 从而可以站在一个更宏观的角度去看待系统.

中介者模式很容易在系统中应用, 也很容易在系统中误用. 当系统出现了“多对多”交互复杂的对象群时, 不要急于使用中介者,最好首先先反思系统的设计是否是合理. 由于ConcreteMediator控制了集中化,于是就把交互复杂性变成了中介者的复杂性, 使得中介者变得比任一个ConcreteColleague都复杂.

在下列情况下建议使用中介者模式:

* 一组对象以定义良好但复杂的方式进行通信. 产生的相互依赖关系结构混乱且难以理解.
* 一个对象引用其他很多对象并且直接与这些对象通信, 导致难以复用该对象.
* 想定制一个分布在多个类中的行为, 而又不想生成太多的子类.

原文出处:组合模式

组合模式

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

(图片来源: 设计模式: 可复用面向对象软件的基础)

模式实现

案例: 杀毒软件

使对文件(Image/Text/Video/…)杀毒与对文件夹(Folder)的杀毒暴露统一接口.

Component

/**
 * @author jifang
 * @since 16/8/24 上午10:19.
 */
public abstract class AbstractFileComponent {
    protected String name;
    protected AbstractFileComponent(String name) {
        this.name = name;
    }
    protected void printDepth(int depth) {
        for (int i = 0; i < depth; ++i) {
            System.out.print('-');
        }
    }
    protected abstract void add(AbstractFileComponent component);
    protected abstract void remove(AbstractFileComponent component);
    protected abstract void killVirus(int depth);
}

Leaf

class ImageFileLeaf extends AbstractFileComponent {
    public ImageFileLeaf(String name) {
        super(name);
    }
    @Override
    public void add(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void remove(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void killVirus(int depth) {
        printDepth(depth);
        System.out.println("图片文件 [" + name + "]杀毒");
    }
}
class TextFileLeaf extends AbstractFileComponent {
    public TextFileLeaf(String name) {
        super(name);
    }
    @Override
    public void add(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void remove(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void killVirus(int depth) {
        printDepth(depth);
        System.out.println("文本文件 [" + name + "]杀毒");
    }
}
class VideoFileLeaf extends AbstractFileComponent {
    public VideoFileLeaf(String name) {
        super(name);
    }
    @Override
    public void add(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void remove(AbstractFileComponent component) {
        throw new NotImplementedException(this.getClass() + " not implemented this method");
    }
    @Override
    public void killVirus(int depth) {
        printDepth(depth);
        System.out.println("视频文件 [" + name + "]杀毒");
    }
}

Composite

public class FolderFileComposite extends AbstractFileComponent {
    private List<AbstractFileComponent> components = new LinkedList<>();
    public FolderFileComposite(String name) {
        super(name);
    }
    @Override
    public void add(AbstractFileComponent component) {
        components.add(component);
    }
    @Override
    public void remove(AbstractFileComponent component) {
        components.remove(component);
    }
    @Override
    public void killVirus(int depth) {
        printDepth(depth);
        System.out.println("目录 [" + name + "]杀毒");
        for (AbstractFileComponent component : components) {
            component.killVirus(depth + 2);
        }
    }
}
public class Client {
    @Test
    public void client() {
        ImageFileLeaf image = new ImageFileLeaf("九寨沟.jpg");
        VideoFileLeaf video = new VideoFileLeaf("龙门飞甲.rmvb");
        TextFileLeaf text = new TextFileLeaf("解忧杂货店.txt");
        FolderFileComposite home = new FolderFileComposite("/home");
        home.add(image);
        home.add(video);
        home.add(text);
        FolderFileComposite root = new FolderFileComposite("/");
        root.add(home);
        root.add(new TextFileLeaf("/authorized_keys"));
        root.add(new FolderFileComposite("/etc"));
        root.killVirus(0);
    }
}

上面的实现方式是透明方式: 直接在Component中声明add()/remove(). 这样做的好处是叶节点和枝节点对于外界没有任何区别, 他们具有完全一致的行为接口. 但问题是对叶节点实现add()/remove()没有任何意义. 所以还有另一种实现方式安全方式, 也就是在Component中不去声明add()/remove(),而是在Composite声明所有用来管理子类对象的方法, 不过由于不够透明, 所以叶节点与枝节点将不具有相同接口, 客户端调用需要作出相应判断,带来了不便, 关于该问题的详细信息可参考:

组合模式(Composite)的安全模式与透明模式.

小结

总的来说: 组合模式让用户可以一致地使用组合结构单个对象.


原文出处:外观模式

外观模式

外观模式: 又称门面模式: 外观Facade为子系统的一组接口提供一个一致界面,使得这组子系统易于使用(通过引入一个新的外观角色降低原系统复杂度,同时降低客户类与子系统的耦合度).

图片来源: 设计模式: 可复用面向对象软件的基础.

实现

案例需求: 租房

有过自己找房租房经历的同学能够体会得到找房是件很痛苦的事, 不光要挨个小区跑而且还要跟(二)房东讨价还价. 于是后来学聪明了, 不再自己挨门挨户的磨嘴皮子,而是直接找像链家、_我爱我家_这样的房屋中介, 他们手上握有一定的房源, 我们只需付给他们一笔佣金, 他们便可以代我们跟房东讲价, 而且他们大都很专业,省时间又省钱. 此时房屋中介就是一个外观Facade, 而房屋的出租户就是子系统SubSystem:

外观类: 知道哪些子系统负责处理请求, 将客户的请求代理给适当的子系统对象:

public class MediumFacade {
    private CuiYuanApartment cuiyuan;
    private XiXiApartment xixi;
    private XiHuApartment xihu;
    public MediumFacade() {
        cuiyuan = new CuiYuanApartment("翠苑小区", 900, 1);
        xixi = new XiXiApartment("西溪花园", 1200, 1);
        xihu = new XiHuApartment("西湖小区", 2600, 1);
    }
    public void rentingHouse(double price) {
        // 价钱合适而且有房可组
        if (price >= cuiyuan.getPrice() && cuiyuan.getStatus() != 0) {
            System.out.println("预订" + cuiyuan.getLocation());
            cuiyuan.setStatus(0);
        } else if (price >= xixi.getPrice() && xixi.getStatus() != 0) {
            System.out.println("预订" + xixi.getLocation());
            xixi.setStatus(0);
        } else if (price >= xihu.getPrice() && xihu.getStatus() != 0) {
            System.out.println("预订" + xihu.getLocation());
            xihu.setStatus(0);
        } else {
            System.out.println("出价太低/没有房源 ...");
        }
    }
}

子系统集合: 实现子系统功能, 处理Facade对象指派的任务(注意子系统内没有任何Facade信息,即没有任何Facade对象引用):

/**
 * @author jifang
 * @since 16/8/23 上午10:12.
 */
public class XiHuApartment {
    private String location;
    private double price;
    private int status;
    public XiHuApartment(String location, double price, int status) {
        this.location = location;
        this.price = price;
        this.status = status;
    }
    public String getLocation() {
        return location;
    }
    public double getPrice() {
        return price;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
}
class XiXiApartment {
    private String location;
    private double price;
    private int status;
    public XiXiApartment(String location, double price, int status) {
        this.location = location;
        this.price = price;
        this.status = status;
    }
    public String getLocation() {
        return location;
    }
    public double getPrice() {
        return price;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
}
class CuiYuanApartment {
    private String location;
    private double price;
    private int status;
    public CuiYuanApartment(String location, double price, int status) {
        this.location = location;
        this.price = price;
        this.status = status;
    }
    public String getLocation() {
        return location;
    }
    public double getPrice() {
        return price;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
}

这样, Client只需跟一个房屋中介联系并给出我们的报价, 他们便会帮我们联系所有符合的房东:

public class Client {
    @Test
    public void client() {
        MediumFacade facade = new MediumFacade();
        facade.rentingHouse(800);
    }
}

小结

有过面向对象开发经验的同学 即使没有听说过外观模式, 也完全有可能使用过他, 因为他完美的体现了依赖倒转原则迪米特法则的思想,是非常常用的模式之一.

* 首先 在设计初期, 应该有意识的进行层次分离, 比如经典的三层架构, 层与层之间建立**Facade**, 这样可以为复杂的子系统提供一个简单的接口, 使耦合度大大降低.
* 其次 在开发阶段, 子系统往往因为不断的重构而变得越来越复杂, 增加**Facade**可以提供一个简单的接口, 减少模块间依赖.
* 第三 在维护一个遗留系统时, 可能这个系统已经非常难以维护和扩展了, 但因为它包含非常重要的功能, **新的需求必须依赖它**, 此时可以为新系统开发一个**Facade**, 为设计粗糙或高复杂度的遗留代码提供一个的比较**清晰简单**的接口, 让新系统与**Facade**交互, **Facade**与遗留代码交互所有繁杂的工作.

原文出处:观察者模式

观察者模式

观察者模式: 又称‘发布-订阅’模式, 定义一种对象间的一对多依赖关系(多个观察者Observer监听某一主题Subject).当主题状态发生改变时,所有依赖它的对象都得到通知并被自动更新.

核心: 触发联动(图片来源: 设计模式: 可复用面向对象软件的基础)

模式实现

以电商系统下单: 
用户购买某件商品下一个订单, 需要: 通知库存系统减少库存通知商家系统发货通知支付系统收钱甚至还会通知关系中心使当前用户关注该商家.

Subject

目标/主题/抽象通知者:

/**
 * @author jifang
 * @since 16/8/30 上午9:49.
 */
public abstract class Subject {
    protected List<Observer> observers = new LinkedList<>();
    public void attach(Observer observer) {
        observers.add(observer);
    }
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    protected abstract void notifyObservers();
    public abstract String getState();
    public abstract void setState(String state);
}

ConcreteSubject

具体目标/具体主题:

class OrderSubject extends Subject {
    private String state;
    /**
     * 采用拉模型, 将Subject自身发送给Observer
     */
    @Override
    protected void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
    @Override
    public String getState() {
        return state;
    }
    @Override
    public void setState(String state) {
        this.state = state;
        this.notifyObservers();
    }
}

Observer

抽象观察者: 为那些在目标状态发生改变时需获得通知的对象定义一个更新接口.

public interface Observer {
    void update(Subject subject);
}

ConcreteObserver

具体观察者:

class WareHouseObserver implements Observer {
    private String orderState;
    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        System.out.println("库存系统接收到消息 [" + orderState + "], 减少库存");
    }
}
class PayObserver implements Observer {
    private String orderState;
    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        System.out.println("支付系统接收到消息 [" + orderState + "], 正在收钱");
    }
}
class RelationObserver implements Observer {
    private String orderState;
    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        if (orderState.equals("已付款")) {
            System.out.println("关系系统接收到消息 [" + orderState + "], 当前用户已关注该店铺");
        } else if (orderState.equals("取消订单")) {
            System.out.println("关系系统接收到消息 [" + orderState + "], 当前用户取消关注该店铺");
        }
    }
}
public class Client {
    @Test
    public void client() {
        Subject subject = new OrderSubject();
        Observer payObserver = new PayObserver();
        Observer relationObserver = new RelationObserver();
        Observer wareHouseObserver = new WareHouseObserver();
        // 注册到Subject
        subject.attach(payObserver);
        subject.attach(relationObserver);
        subject.attach(wareHouseObserver);
        subject.setState("已付款");
        System.out.println("-------------");
        // 付钱、发货完成
        subject.detach(payObserver);
        subject.detach(wareHouseObserver);
        subject.setState("取消订单");
    }
}

通知方式

JDK支持

Java语言自身提供了对观察者模式的支持: java.util包下提供了Observable类与Observer接口.

下面我们就用Java的支持实现观察者模式的推模型:

public class OrderSubject extends Observable {
    private String state;
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
        this.setChanged();
        this.notifyObservers(state);
    }
    @Override
    public String toString() {
        String result = "OrderSubject{";
        try {
            Field obs = Observable.class.getDeclaredField("obs");
            obs.setAccessible(true);
            Vector vector = (Vector) obs.get(this);
            result += vector;
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
        return result +
                "state='" + state + '\'' +
                '}';
    }
}
public class WareHouseObserver implements Observer {
    private String orderState;
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("拉模式: " + o);
        orderState = (String) arg;
        System.out.println("推模式: 库存系统接收到消息 [" + orderState + "], 减少库存");
    }
}

Guava支持

Guava提供EventBus以取代发布者订阅者之间的显式注册, 取而代之的是使用注解@Subscribe,使组件间有更好的解耦.

Event

封装消息类
EventBusEvent继承: EventBus自动把事件分发给事件超类的监听者/观察者,并允许监听者声明监听接口类型泛型的通配符类型(如 ? super Xxx).

interface Event {
    String getState();
}

Subject

使用Guava之后, 如果要订阅消息, 就不用再实现指定的接口, 只需在指定的方法上加上@Subscribe注解即可, 但为了代码的易读性,我们还是推荐保留公共的接口:

public interface Observer {
    void update(Event event);
}

Producer

public class Producer {
        private static final Logger LOGGER = Logger.getLogger(Client.class.getName());
        @Test
        public void client() {
            EventBus bus = new EventBus("observer-pattern");
            bus.register(new Observer() {
                @Subscribe
                @Override
                public void update(Event event) {
                    System.out.println("库存系统接收到消息 [" + event.getState() + "], 减少库存");
                }
            });
            bus.register(new Observer() {
                @Subscribe
                @Override
                public void update(Event event) {
                    System.out.println("支付系统接收到消息 [" + event.getState() + "], 正在收钱");
                }
            });
            // 不用实现接口, 直接给出一个Object对象也可
            bus.register(new Object() {
                @Subscribe
                public void onEvent(Event event) {
                    System.out.println("关系系统接收到消息 [" + event.getState() + "], 当前用户关注店铺");
                }
                @Subscribe
                public void onEventFun(Event event) {
                    System.out.println("我就是来打酱油的o(╯□╰)o");
                }
            });
            // 注册DeadEvent
            bus.register(new Object() {
                @Subscribe
                public void onDead(DeadEvent dead) {
                    LOGGER.log(Level.WARNING, "没有消费者接收" + dead);
                }
            });
            // 发布消息
            bus.post(new Event() {
                @Override
                public String getState() {
                    return "付钱成功";
                }
            });
            bus.post("dead event O(∩_∩)O~");
        }
    }

注: 线程间通信框架Disruptor也是观察者模式的一种具体实现, 详细可参考博客: Java并发基础:Disruptor小结并发框架Disruptor译文.

小结

将系统分割成一系列相互协作的类有一定的副作用: 需要维护相关对象间的一致性, 我们不希望为了一致性而将各类紧密耦合,这样会给维护、扩展和重用都带来不便.
而观察者模式允许独立的改变目标和观察者. 你可以单独复用Subject而不用管Observer 反之亦然.它也使你可以在不改动Subject和其他Observer的前提下增加观察者.


原文出处:享元模式

享元模式

内存属于稀缺资源, 不能随便浪费. 如果有很多相同/相似的对象, 我们可以通过享元节省内存.

内部状态 vs. 外部状态

享元模式(Flyweight): 运用共享技术有效地重用大量细粒度的对象.

此处输入图片的描述

* 在享元对象内部并且**不会随环境改变而改变的共享部分**, 可称之为享元对象的内部状态.
* **随环境改变而改变的不可以共享的状态**是外部状态.

在设计开发中,有时需要生产大量细粒度对象来表征数据, 如果这些对象除个别参数外基本相同, 此时如果能把那些参数移到类实例外面,在方法调用时将其传入, 就可以通过共享大幅度减少类实例数目.

模式实现

案例: 围棋设计

有下棋经验的同学都知道一盘棋的棋子大小、材质、颜色(黑/白)往往都是确定的, 而围棋落子的位置却不一定(看水平高低了O(∩_∩)O!),因此我们可以将棋子位置从棋子对象中剥离, 然后让棋子对象共享大小、材质、颜色属性, 并在调用时将位置传入, 就可大大减少棋子对象的数量:

此处输入图片的描述

Flyweight

所有具体享元类的超类或接口, 通过该接口, Flyweight可以接受并作用于外部状态:

/**
 * @author jifang
 * @since 16/8/26 上午10:27.
 */
public interface Flyweight {
    void operation(Location location);
}

ConcreteFlyweight

实现Flyweight接口, 并为内部状态增加存储空间:

class GoFlyweight implements Flyweight {
    private String color;
    private double radius;
    private String material;
    public GoFlyweight(String color, double radius, String material) {
        this.color = color;
        this.radius = radius;
        this.material = material;
    }
    public String getColor() {
        return color;
    }
    public double getRadius() {
        return radius;
    }
    public String getMaterial() {
        return material;
    }
    @Override
    public void operation(Location location) {
        System.out.println("[" + color + "]棋 [" + material + "]材质 半径[" + radius + "]CM 落在" + location);
    }
}

UnsharedConcreteFlyweight

指不需要共享的Flyweight子类, 因为Flyweight接口共享成为可能, 但它并不强制共享.UnsharedConcreteFlyweight用于解决那些不需要共享对象的问题:

class Location {
    private int locX;
    private int locY;
    public Location() {
    }
    public Location(int locX, int locY) {
        this.locX = locX;
        this.locY = locY;
    }
    public int getLocX() {
        return locX;
    }
    public void setLocX(int locX) {
        this.locX = locX;
    }
    public int getLocY() {
        return locY;
    }
    public void setLocY(int locY) {
        this.locY = locY;
    }
    @Override
    public String toString() {
        return "{" +
                "locX=" + locX +
                ", locY=" + locY +
                '}';
    }
}
public class FlyweightFactory {
    private static Map<String, GoFlyweight> map = new ConcurrentHashMap<>();
    public static GoFlyweight getGoFlyweight(String color) {
        GoFlyweight flyweight = map.get(color);
        if (flyweight == null) {
            flyweight = new GoFlyweight(color, 1.1, "陶瓷");
            map.put(color, flyweight);
        }
        return flyweight;
    }
}

小结

享元模式可以极大减少内存中对象的数量: 相同/相似对象只保留一份, 节约资源, 提高性能. 且将外部状态剥离, 使外部状态相对独立,不影响内部状态. 但相比原先的设计, 增加了实现复杂度, 且读取外部状态使得运行时间变长(时间换空间).


原文出处:命令模式

命令模式

在对象的结构和创建问题都解决了之后,就剩下对象的行为问题了: 如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高.
行为型模式共有11个可供研究,它们分别是:命令模式解释器模式访问者模式模板方法模式观察者模式状态模式策略模式责任链模式中介者模式备忘录模式迭代器模式.

命令模式

命令模式: 又称动作Action模式, 将请求封装为对象, 从而使我们可用不同的请求对客户进行参数化;命令可用于将行为请求者行为实现者解耦, 以适应变化(如: 对请求排队、记录日志、支持可撤销操作等).

(图片来源: 设计模式: 可复用面向对象软件的基础)

模式实现

案例:以饭店点菜为例-点餐
客户不需要直接向大厨下达点菜命令, 而是通过给服务员书写菜单, 然后服务员再具体指挥大厨照单做菜, 菜单是一种'Command':

(案例来源: 大话设计模式)

Receiver

命令接收者: 提供很多方法调用, 负责执行与请求相关业务逻辑;

厨师: 只负责做各种各样的菜.

/**
 * @author jifang
 * @since 16/8/19 上午10:01.
 */
public class CookReceiver {
    public void bakeMutton() {
        System.out.println("厨师: 烤羊肉串");
    }
    public void backChickenWing() {
        System.out.println("厨师: 烤鸡翅");
    }
}

Command

抽象命令接口: 类中对需要执行的操作进行声明, 且包含一个Receiver, 并公布一个execute()方法用来调用Receiver执行命令:

/**
 * @author jifang
 * @since 16/8/19 上午10:08.
 */
public abstract class Command {
    protected CookReceiver receiver;
    public Command(CookReceiver receiver) {
        this.receiver = receiver;
    }
    public abstract void execute();
}

ConcreteCommand

具体命令类: 实现Command内的抽象方法(调用Receiver提供的方法).

/**
 * 烤肉命令
 */
class BackMuttonCommand extends Command {
    public BackMuttonCommand(CookReceiver receiver) {
        super(receiver);
    }
    @Override
    public void execute() {
        this.receiver.bakeMutton();
    }
}
/**
 * 烤鸡翅命令
 */
class BackChickenWingCommand extends Command {
    public BackChickenWingCommand(CookReceiver receiver) {
        super(receiver);
    }
    @Override
    public void execute() {
        this.receiver.backChickenWing();
    }
}

Invoker

请求的发起者: 内部包含Command聚集, 他通过命令对象来唤起Receiver执行请求. 一个调用者并不需要在设计时确定其接受者, 因此它只与抽象命令Command存在关联.通过调用Commandexecute()间接调用Receiver的相关操作:

public class WaiterInvoker {
    private Queue<Command> queue = new LinkedList<>();
    public void addCommand(Command command) {
        if (checkCommand(command)) {
            queue.add(command);
        }
    }
    public void cancelCommand(Command command) {
        // 如果命令已经执行过, 则不予撤销
        if (!queue.isEmpty()) {
            queue.remove(command);
        }
    }
    /**
     * 通知执行所有命令
     */
    public void notifyExecute() {
        while (!queue.isEmpty()) {
            Command command = queue.poll();
            command.execute();
        }
    }
    private boolean checkCommand(Command command) {
        // TODO 检查命令是否有效: 如当前原材料是否充足等
        return true;
    }
}
/**
 * Created by jifang on 15/12/3.
 */
public class Client {
    @Test
    public void client() {
        // 开业准备
        WaiterInvoker waiter = new WaiterInvoker();
        CookReceiver cook = new CookReceiver();
        Command backMuttonOrder = new BackMuttonCommand(cook);
        Command backChickenWingOrder = new BackChickenWingCommand(cook);
        // 接收订单
        waiter.addCommand(backMuttonOrder);
        waiter.addCommand(backChickenWingOrder);
        // 在厨师制作完成之前还可以撤销订单
        waiter.cancelCommand(backMuttonOrder);
        // 通知执行
        waiter.notifyExecute();
    }
}

小结:

命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分隔开.


原文出处:模板方法模式

模板方法模式

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

(图片来源: 设计模式:可复用面向对象软件的基础)

模式实现

ATM取款机办理业务, 都会经过插卡输密码处理业务取卡 等几个过程, 而且这几个过程一定是顺序执行的, 且除了 处理业务(如取款、改密、查账) 可能会有所不同之外, 其他的过程完全相同. 因此我们就可以参考模板方法模式插卡输密码取卡 3个过程放到父类中实现, 并定义一个流程骨架, 然后将 处理业务的具体逻辑 放到子类中:

/**
 * @author jifang
 * @since 16/8/21 上午10:35.
 */
public abstract class AbstractATMBusiness {
    public void run() {
        System.out.println("-> 插卡");
        System.out.println("-> 输入并校验密码");
        if (checkPassword()) {
            onBusiness();
        }
        System.out.println("-> 取卡");
    }
    // 具体业务处理延迟到子类实现
    protected abstract void onBusiness();
    private boolean checkPassword() {
        // TODO Encode Password, Select DB & Comparison
        return true;
    }
}

AbstractATMBusiness是一个模板方法, 它定义了ATM操作的一个主要步骤并确定他们的先后顺序,但允许子类改变这些具体步骤以满足各自的需求.

class CheckOutConcreteATMBusiness extends AbstractATMBusiness {
    @Override
    protected void onBusiness() {
        System.out.println(" ... 取款");
    }
}
class ChangePasswordConcreteATMBusiness extends AbstractATMBusiness {
    @Override
    protected void onBusiness() {
        System.out.println(" ... 修改密码");
    }
}
/**
 * Created by jifang on 15/12/3.
 */
public class Client {
    @Test
    public void client() {
        AbstractATMBusiness changePassword = new ChangePasswordConcreteATMBusiness();
        changePassword.run();
        AbstractATMBusiness checkOut = new CheckOutConcreteATMBusiness();
        checkOut.run();
    }
}

实例

Servlet

HttpServlet定义了service()方法固定下来HTTP请求的整体处理流程,使得开发Servlet只需继承HttpServlet并实现doGet()/doPost()等方法完成业务逻辑处理, 并不需要关心具体的HTTP响应流程:

/**
 * HttpServlet中的service方法
 */
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
        // servlet doesn't support if-modified-since, no reason
        // to go through further expensive logic
        doGet(req, resp);
        } else {
        long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
        if (ifModifiedSince < (lastModified / 1000 * 1000)) {
            // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
            maybeSetLastModified(resp, lastModified);
            doGet(req, resp);
        } else {
            resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);   
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

详见: Servlet - 基础.

统一定时调度

将这个示例放在此处可能有些不大合适, 但它也体现了一些模板方法的思想:

1. 实现
/**
 * @author jifang
 * @since 16/8/23 下午3:35.
 */
public class ScheduleTaskMonitor implements InitializingBean, DisposableBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskMonitor.class);
    private static final int _10S = 10_000;
    private List<ScheduleTask> tasks = new CopyOnWriteArrayList<>();
    private static final Timer timer = new Timer("ScheduleTaskMonitor");
    private void start() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                for (ScheduleTask task : tasks) {
                    task.scheduleTask();
                }
            }
        }, 0, _10S);
    }
    public void register(ScheduleTask task) {
        tasks.add(task);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        this.start();
        LOGGER.info("Start Monitor {}", this.getClass());
    }
    @Override
    public void destroy() throws Exception {
        timer.cancel();
        LOGGER.info("Stop Monitor {}", this.getClass());
    }
}
public interface ScheduleTask {
    void scheduleTask();
}
2. 使用

只需在Spring的配置文件中引入该Bean:

<bean id="monitor" class="com.template.ScheduleTaskMonitor"/>

需要统一定时的类实现ScheduleTask接口, 并将自己注册到monitor中:

/**
 * @author jifang
 * @since 16/3/16 上午9:59.
 */
@Controller
public class LoginController implements ScheduleTask, InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private ScheduleTaskMonitor monitor;
    @Override
    public void scheduleTask() {
        LOGGER.error("O(∩_∩)O 日志记录~");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        monitor.register(this);
    }
}

即可完成scheduleTask()方法的定时调度.

小结

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


原文出处:状态模式

状态模式

状态模式: 允许一个对象在其内部状态改变时改变其行为, 其对象看起来像是改变了其类.

(图片来源: 设计模式:可复用面向对象软件的基础)
其目的是: 解决系统中复杂对象的状态流转以及不同状态下的行为封装问题.

模式实现

案例: 问题跟踪(Bug状态流转):
有过KeludeJira使用经验的同学都知道一个Bug由测试同学提出, 一直到被开发同学解决会经过一系列状态的流转:
新建(New) -> 打开(Open) -> 解决(Fixed) -> 关闭(Closed)

且每种状态都会对应复杂业务的处理逻辑(如通知相应开发/测试人员、邮件/短信提醒、报表记录等等), 下面我们就以这个场景来讨论状态模式的实现:

State

抽象状态: 定义一个接口封装与 Context的一个特定状态 相关的行为:

/**
 * @author jifang
 * @since 16/8/28 下午6:06.
 */
public interface State {
    void handle(Context context);
}

ConcreteState

具体状态: 每一个子类实现一个与 Context的某一个特定状态相关的具体行为 :

class NewState implements State {
    static final NewState instance = new NewState();
    // 单例 or 享元
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            // 本状态下的核心业务处理
            System.out.println("测试: 发现了Bug, 开发同学赶紧处理");
            // 状态流转
            context.setCurrent(OpenState.instance());
        }
    }
}
class OpenState implements State {
    static final OpenState instance = new OpenState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("开发: Bug已经看到, 正在处理");
            context.setCurrent(FixedState.instance());
        }
    }
}
class FixedState implements State {
    static final FixedState instance = new FixedState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("开发: Bug已经修复, 测试同学看一下");
            context.setCurrent(ClosedState.instance());
        }
    }
}
class ClosedState implements State {
    static final ClosedState instance = new ClosedState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("测试: Bug验证通过, 已关闭");
            context.setCurrent(null);
        }
    }
}

Context

public class Context {
    private State current;
    public Context(State current) {
        this.current = current;
    }
    public State getCurrent() {
        return current;
    }
    public void setCurrent(State current) {
        this.current = current;
    }
    public void request() {
        if (current != null) {
            current.handle(this);
        }
    }
}
public class Client {
    @Test
    public void client() {
        Context context = new Context(NewState.instance());
        context.request();
        context.request();
        context.request();
        context.request();
        context.request();
    }
}

状态推动

前面介绍的状态流转需要由Client推动(Client调用Contextrequest()), 还有其他几种推动方式.如State自动流转: 每个State处理结束,自动进入下一状态的处理环节(在State内部调用Contextrequest()):

class NewState implements State {
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("测试: 发现了Bug, 开发同学赶紧处理");
            context.setCurrent(new OpenState());
        }
        context.request();
    }
}

另外还有一种基于表驱动的状态机实现, 实现细节参考 设计模式:可复用面向对象软件的基础 P204.

小结


原文出处:策略模式

策略模式

策略模式: 定义一系列的算法, 将其一个个封装起来, 并使它们可相互替换, 使得算法可独立于使用它的客户而变化.

(图片来源: 设计模式: 可复用面向对象软件的基础)

策略模式对应于解决某一问题的一个算法族, 允许用户从该算法族中任选一个算法解决该问题, 同时可以方便的更换算法或者增加新的算法. 并由客户端决定调用哪个算法(核心: 分离算法, 选择实现).

模式实现

案例: 商场打折 -策略可以简单分为: 原价购买满减返利三种策略:

Strategy

抽象策略: 定义算法族中所有算法的公共接口, Context使用这个接口来调用ConcreteStrategy定义的算法:

/**
 * @author jifang
 * @since 16/8/29 下午7:43.
 */
public interface Strategy {
    double acceptCash(double money);
}

ConcreteStrategy

具体策略: 以Strategy接口实现某具体算法或行为:

// 正常收费
class Normal implements Strategy {
    @Override
    public double acceptCash(double money) {
        return money;
    }
}
// 打折收费
class Discount implements Strategy {
    private double rate;
    public Discount(double rate) {
        if (rate > 1.0) {
            throw new RuntimeException("折扣力度怎么能大于1.0?");
        }
        this.rate = rate;
    }
    @Override
    public double acceptCash(double money) {
        return money * rate;
    }
}
// 返利收费
class Rebate implements Strategy {
    private double cashState;
    private double cashReturn;
    public Rebate(double cashState, double cashReturn) {
        this.cashState = cashState;
        this.cashReturn = cashReturn;
    }
    @Override
    public double acceptCash(double money) {
        if (money > cashState) {
            money -= Math.floor(money / cashState) * cashReturn;
        }
        return money;
    }
}

Context

注: 将客户端需要选择具体Strategy的任务交给Context完成:
基础策略模式中,选择所用具体Strategy实现的职责由Client承担, 并将其传递给Context,这种方案对Client的负担较重, 因此将Context简单工厂融合, 选择算法实现的工作改由Context负责.

public class Client {
    @Test
    public void client() {
        double money = 1000;
        Context context = new Context();
        context.setStrategy(Context.Type.NORMAL);
        System.out.println("原价: [" + context.getResult(money) + "]");
        context.setStrategy(Context.Type.REBATE, 100, 20);
        System.out.println("满100返20: [" + context.getResult(money) + "]");
        context.setStrategy(Context.Type.DISCOUNT, 0.8);
        System.out.println("6折优惠: [" + context.getResult(money) + "]");
    }
}

小结


原文出处:责任链模式

责任链模式

责任链模式: 将能够处理某一类请求的对象串成一条链, 请求沿链传递, 链上的对象逐个判断是否有能力处理该请求. 使多个对象都有机会处理请求, 从而避免请求发送者和接收者之间的耦合关系.

(图片来源: 设计模式: 可复用面向对象软件的基础)
优势: 发出请求的客户端并不知道链上的哪个对象最终处理该请求, 这使得系统可以在不影响客户端的前提下动态地重新组织和分配责任.

模式实现

案例: 雇员要求 (请假 & 涨薪), 要经过总监Director -> 经理Manager -> 总经理GeneralManager的层层审批.

Handler:

定义一个处理请求的接口, 内持继任者(可选):

public abstract class Leader {
    protected Leader superior;
    protected String name;
    protected Leader(Leader superior, String name) {
        this.superior = superior;
        this.name = name;
    }
    public abstract void handle(Request request);
}

ConcreteHandler

// 总监
class Director extends Leader {
    public Director(Leader superior, String name) {
        super(superior, name);
    }
    @Override
    public void handle(Request request) {
        if (request.getType().equals("请假") && request.getCount() <= 10) {
            System.out.println("[ " + request.getContent() + "] 请假 [" + request.getCount() + "]天, 总监 [" + name + "] 审批通过");
        } else {
            if (superior != null) {
                superior.handle(request);
            }
        }
    }
}
// 经理
class Manager extends Leader {
    public Manager(Leader superior, String name) {
        super(superior, name);
    }
    @Override
    public void handle(Request request) {
        if (request.getType().equals("请假") && request.getCount() <= 20) {
            System.out.println("[ " + request.getContent() + "] 请假 [" + request.getCount() + "]天, 经理 [" + name + "] 审批通过");
        } else if (request.getType().equals("涨薪") && request.getCount() <= 1000) {
            System.out.println("[ " + request.getContent() + "]  涨薪 [" + request.getCount() + "]RMB, 经理 [" + name + "] 审批通过");
        } else {
            if (superior != null) {
                superior.handle(request);
            }
        }
    }
}
// 总经理
class GeneralManager extends Leader {
    public GeneralManager(Leader superior, String name) {
        super(superior, name);
    }
    @Override
    public void handle(Request request) {
        if (request.getType().equals("请假")) {
            if (request.getCount() <= 30) {
                System.out.println("[ " + request.getContent() + "] 请假 [" + request.getCount() + "]天, 总经理 [" + name + "] 审批通过");
            } else {
                System.out.println("[ " + request.getContent() + "] 你干脆辞职算了");
            }
        } else if (request.getType().equals("涨薪")) {
            if (request.getCount() <= 10_000) {
                System.out.println("[ " + request.getContent() + "]  涨薪 [" + request.getCount() + "]RMB, 总经理 [" + name + "] 审批通过");
            } else {
                System.out.println("你咋不上天呢");
            }
        }
    }
}

Client

向链上的具体处理者对象提交请求:

public class Client {
    @Test
    public void client() {
        Leader generalManger = new GeneralManager(null, "刘备");
        Leader manager = new Manager(generalManger, "诸葛亮");
        Leader director = new Director(manager, "赵云");
        director.handle(new Request("请假", "翡青", 32));
        director.handle(new Request("涨薪", "zjf", 1500));
    }
}


public class Request {
    private String type;
    private String content;
    private int count;
    public Request() {
    }
    public Request(String type, String content, int count) {
        this.type = type;
        this.content = content;
        this.count = count;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}

注: 非链表实现责任链 - 还可通过集合数组等形式存储责任链, 很多项目中,每个具体的Handler并不是开发团队定义的, 而是项目上线后又外部单位追加的, 此时使用链表方式定义chain of responsibility就很困难, 此时可选择使用集合存储.

小结