0%

Java面向对象设计模式

单例模式

饿汉模式

线程安全的饿汉模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SingleTon {
//1、构造方法私有化
private SingleTon() {
}

//2、创建类的唯一实例
private static SingleTon instance = new SingleTon();

//提供一个获取唯一实例的方法
public static SingleTon getInstance() {
return instance;
}
}

饿汉模式在类加载(包括初始化)后便会实例化 SingleTon 对象,但是在类加载(不初始化),SingleTon 不会被实例化,这时相当于懒汉模式。关于类加载的过程,可参考: JVM类加载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Test {
public static void main(String[] args) throws Exception {
Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class});
m.setAccessible(true);
ClassLoader cl = ClassLoader.getSystemClassLoader();
Object test1 = m.invoke(cl, "SingleTon");
System.out.println(test1 != null);
System.out.println(SingleTon.class.getName());
Object test2 = m.invoke(cl, "SingleTon");
System.out.println(test2 != null);
SingleTon.getInstance();
Object test3 = m.invoke(cl, "SingleTon");
System.out.println(test3 != null);
}
}

class SingleTon {
private SingleTon() {
System.out.println("constructor");
}

private static SingleTon instance = new SingleTon();

public static SingleTon getInstance() {
return instance;
}
}

// 输出
false
SingleTon
true
constructor
true

可以看到 SingleTon 类已经被加载了,但是并没有执行初始化过程,也就没有实例化 SingleTon 对象。直到调用 getInstance 方法时(也可以是调用SingleTon中的静态变量)才会实例化。

懒汉模式

线程不安全的懒汉模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingleTon {
//1、构造方法私有化
private SingleTon() {
}

//2、创建类的唯一实例
private static SingleTon instance;

//提供一个获取唯一实例的方法
public static SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}

线程安全的懒汉模式,双重检查锁定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingleTon {
//1、构造方法私有化
private SingleTon() {
}

//2、创建类的唯一实例
private static volatile SingleTon instance;

//提供一个获取唯一实例的方法
public static SingleTon getInstance() {
if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}

这里之所以要加 volatile 标识,是因为 synchronized 无法禁止指令重排序,而 instance 的赋值过程可以拆分为三个步骤:

  1. 申请一个内存区域(空白内存);
  2. 调用构造方法等进行初始化(写内存);
  3. 将对象引用赋值给变量;

而 2 和 3 可能发生重排序,当线程 T1 拿到锁开始创建实例,如果发生了重排序,此时 instance 已经被赋值,但是对象没有初始化,如果此时 instance 被同步到了主内存,当线程 T2 调用 getInstance 方法时,发现 instance 不为空,则直接返回使用,进而出错。

线程安全的懒汉模式,静态内部类:

1
2
3
4
5
6
7
8
9
10
11
12
public class SingleTon {
private SingleTon() {
}

private static class LazyHolder {
private static SingleTon instance = new SingleTon();
}

public static SingleTon getInstance() {
return LazyHolder.instance;
}
}

枚举单例

enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。枚举会被编译成如下形式:

1
2
3
public final class T extends Enum{

}

其中,Enum是Java提供给编译器的一个用于继承的类。枚举量的实现其实是public static final T 类型的未初始化变量,之后,会在静态代码中对枚举量进行初始化。所以,如果用枚举去实现一个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作用。

对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。

因此,选择枚举作为Singleton的实现方式,相对于其他方式尤其是类似的饿汉模式主要有以下优点:

  1. 代码简单
  2. 自由序列化

单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SingleTon {
private SingleTon() { }

public static SingleTon getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}

private static enum SingletonEnum{
INSTANCE;

private SingleTon singleton;

//JVM会保证此方法绝对只调用一次
private SingletonEnum(){
singleton = new SingleTon();
}

public SingleTon getInstance(){
return singleton;
}
}
}

更简单:

1
2
3
public enum EasySingleton {
INSTANCE;
}

单例模板

不像C++,C++可以通过直接new T()实现泛型单例,但Java不可以.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SingleTonBase {
private static final ConcurrentMap<Class, Object> map = new ConcurrentHashMap<>();

public static <T> T getInstance(Class<T> type) {
Object ob = map.get(type);
try {
if (ob == null) {
synchronized (map) {
if (map.get(type) == null) {
ob = type.newInstance();
map.put(type, ob);
}
}
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return (T) ob;
}

public static <T> void remove(Class<T> type) {
map.remove(type);

}
}

反射攻击

反射可以获取到单例的私有构造函数,从而破坏单例。如果要抵御这种攻击,就要修改构造器,让他在被要求创建第二个实例的时候抛出异常。可以通过一个boolean类型的静态变量来标识。但是依然可以通过反射的方式来修改标识的值,从而破坏单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Main {
public static void main(String[] args) {
System.out.println("the program is ready to start...");

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

SingTo singTo = SingTo.getInstance();

Class<SingTo> cls = SingTo.class;
try {
Field flag = cls.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(null, false);
Constructor<SingTo> constructor = cls.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingTo singTo1 = constructor.newInstance();

System.out.println(singTo == singTo1);
} catch (IllegalAccessException | InstantiationException
| NoSuchMethodException | InvocationTargetException
| NoSuchFieldException e) {
e.printStackTrace();
}
}
}

枚举单例可以避免反射攻击,如下调用newInstance会报错:

1
2
3
4
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
enumSingleton.test();
Class<EnumSingleton> cls = EnumSingleton.class;
System.out.println(enumSingleton == cls.newInstance());

因为类的cls.newInstance()方法最后调用了Constructor的newInstance方法:

1
2
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");

序列化与单例

为了防止反射破坏单例,在私有构造方法里面加入了一个同步变量的判断,确保构造方法只调用一次。但是仍然无法阻止序列化破坏单例,而枚举类可以防止。

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。 也就是说,以下面枚举为例,序列化的时候只将 DATASOURCE 这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

在readObj底层实现上,是通过反射调用私有构造方法来返回实例的,但是在这段代码后面,还会检测该类是否有一个readResolve()方法,如果有,就返回这个方法返回值。这个方法必然是为了解决序列化破坏单例的,我们可以添加这个方法,让其返回单例。

对于枚举单例之外的其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。

所以在单例类中添加如下方法:

1
2
3
public Object readResolve(){
return instance();
}

工厂模式

概述

  1. 抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

可以使用反射方法替代多个具体工厂角色,避免新建过多的具体工厂类。

实例

抽象工厂接口:

1
2
3
public interface HairFactory {
Hair create();
}

具体工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
public class LeftHairFactory implements HairFactory {

public Hair create() {
return new LeftHair();
}
}

public class RightHairFactory implements HairFactory {
public Hair create() {
return new RightHair();
}
}

抽象产品接口:

1
2
3
public abstract class Hair {
public Hair(){}
}

具体产品类:

1
2
3
4
5
6
7
8
9
10
11
public class LeftHair extends Hair {
public LeftHair() {
System.out.println("LeftHair...");
}
}

public class RightHair extends Hair {
public RightHair() {
System.out.println("RightHair...");
}
}

使用反射的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HairFactoryByReflect {

public Hair getHairByClass(Class c){
try {
return (Hair) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}

观察者模式

概述

观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者模式所涉及的角色有:

  • 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
  • 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
  • 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
  • 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

实例

抽象主题角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Subject {
// 用来保存注册的观察者对象
private List<Observer> list = new ArrayList<Observer>();

// 注册观察者对象
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}

// 删除观察者对象
public void detach(Observer observer){
list.remove(observer);
}

// 通知所有注册的观察者对象
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}

具体主题角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConcreteSubject extends Subject{

private String state;

public String getState() {
return state;
}

public void change(String newState){
state = newState;
System.out.println("主题状态为:" + state);
// 状态发生改变,通知各个观察者
this.nodifyObservers(state);
}
}

抽象观察者角色类:

1
2
3
public interface Observer {
public void update(String state);
}

具体观察者角色类:

1
2
3
4
5
6
7
8
9
10
11
public class ConcreteObserver implements Observer {
// 观察者的状态
private String observerState;

@Override
public void update(String state) {
// 更新观察者的状态,使其与目标的状态保持一致
observerState = state;
System.out.println("状态为:" + observerState);
}
}

客户端类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

public static void main(String[] args) {
// 创建主题对象
ConcreteSubject subject = new ConcreteSubject();
// 创建观察者对象
Observer observer = new ConcreteObserver();
// 将观察者对象登记到主题对象上
subject.attach(observer);
// 改变主题对象的状态
subject.change("new state");
}
}

代理/委托模式

概述

当不能直接访问一个对象时,可以使用代理来间接访问,比如对象在另外一台机器上,或者对象被持久化了,对象是受保护的。代理模式侧重于控制对对象的访问,代理类可以对它的客户隐藏一个对象的具体信息。

静态代理模式的代理类只是实现了特定类的代理,如果代理类对象的方法越多,你就得写越多的重复的代码,使用动态代理可以比较好地解决这个问题,动态代理可以动态的生成代理类,实现对不同类下的不同方法的代理。

静态代理和动态代理的区别:

  1. 静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道;
  2. 静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类;

静态代理

概述

当使用静态代理模式的时候,我们常常在一个代理类中创建一个对象的实例。

参与者:

  • 被代理对象基类(Subject):定义一个接口。
  • 具体被代理对象类(ConcreteSubject):实现接口的方法。
  • 代理类(Proxy):内部有被代理对象的成员变量,且直接在内部实例化它,代理类也实现了被代理对象基类的方法。

实例

被代理对象基类:

1
2
3
public interface Subject {
void operate();
}

具体被代理对象类:

1
2
3
4
5
6
7
public class ConcreteSubject implements Subject {

@Override
public void operate() {
System.out.println("do something...");
}
}

代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Proxy implements Subject {

private Subject subject = new ConcreteSubject();

@Override
public void operate() {
// 调用目标之前可以做相关操作
System.out.println("before....");
subject.operate();
// 调用目标之后可以做相关操作
System.out.println("after....");
}
}

客户端类:

1
2
3
4
5
6
7
public class Client {

public static void main(String[] args) {
Subject subject = new Proxy();
subject.operate();
}
}

Jdk动态代理

概述

jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理。代理类必须实现InvocationHandler接口,并且,JDK动态代理只能代理实现了接口的类,没有实现接口的类是不能实现JDK动态代理。

使用JDK动态代理类基本步骤:

  1. 编写需要被代理的类和接口;
  2. 编写代理类,需要实现InvocationHandler接口,重写invoke方法;
  3. 使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。

实例

被代理对象基类:

1
2
3
public interface Subject {
void operate();
}

具体被代理对象类:

1
2
3
4
5
6
7
public class ConcreteSubject implements Subject {

@Override
public void operate() {
System.out.println("do something...");
}
}

代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Proxy implements InvocationHandler {

private Object target;

public Proxy(Object target) {
this.target = target;
}

@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("before....");
// 使用方法的反射
Object invoke = method.invoke(target, args);
System.out.println("after....");
return invoke;
}
}

客户端类:

1
2
3
4
5
6
7
8
9
10
public class Client {

public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Class<?> clazz = subject.getClass();
Proxy proxy = new Proxy(subject);
Subject subjectProxy = (Subject) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), proxy);
subjectProxy.operate();
}
}

Cglib动态代理

概述

Cglib是针对类来实现代理的,类不用实现接口,Cglib会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用,因此不能代理声明为final类型的类和方法。代理子类需要实现MethodInterceptor接口。

Cglib实现的MethodInterceptor接口在spring-core包。

实例

被代理对象类:

1
2
3
4
5
6
public class Subject {

public void operate() {
System.out.println("do something...");
}
}

代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Proxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();

public Object getProxyObj(Class clazz) {
//设置父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
enhancer.setUseCache(false);
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before....");
// 代理类对象实例调用父类方法
methodProxy.invokeSuper(o, args);
System.out.println("after....");
return null;
}
}

客户端类:

1
2
3
4
5
6
7
8
public class Client {

public static void main(String[] args) {
Proxy proxy = new Proxy();
Subject subjectProxy = (Subject) proxy.getProxyObj(Subject.class);
subjectProxy.operate();
}
}

装饰模式

概述

装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。装饰器模式关注于在一个对象上动态的添加方法,因此,当使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。

参与者:

  • 被装饰对象基类(Subject):定义一个接口。
  • 具体被装饰对象类(ConcreteSubject):实现接口的方法。
  • 装饰者类(Decorator):内部有被装饰对象的成员变量,其实例由外部传入,装饰者类也实现了被装饰对象基类的方法。

实例

被装饰对象基类:

1
2
3
public interface Subject {
void operate();
}

具体被装饰对象类:

1
2
3
4
5
6
7
public class ConcreteSubject implements Subject {

@Override
public void operate() {
System.out.println("do something...");
}
}

装饰者类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Decorator implements Subject {

private Subject subject;

public Decorator(Subject subject) {
this.subject = subject;
}

@Override
public void operate() {
// 调用目标之前可以做相关操作
System.out.println("before....");
subject.operate();
// 调用目标之后可以做相关操作
System.out.println("after....");
}
}

客户端类:

1
2
3
4
5
6
7
8
public class Client {

public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Subject decorator = new Decorator(subject);
decorator.operate();
}
}

外观/门面(Facade)模式

概述

隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。

  1. 门面(Facede)角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
  2. 子系统角色:实现了子系统的功能。它对客户角色和Facade是未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
  3. 客户角色:通过调用Facede来完成要实现的功能。

实例

每个Computer都有CPU、Memory、Disk。在Computer开启和关闭的时候,相应的部件也会开启和关闭,所以,使用了该外观模式后,会使用户和部件之间解耦。

子系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CPU {
public void start() {
System.out.println("CPU start...");
}

public void shutdown() {
System.out.println("CPU shutdown...");
}
}

public class Memory {
public void start() {
System.out.println("Memory start...");
}

public void shutdown() {
System.out.println("Memory shutdown...");
}
}

门面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Computer {
private CPU cpu;
private Memory memory;

public Computer() {
cpu = new CPU();
memory = new Memory();
}

public void start() {
System.out.println("Computer start begin...");
cpu.start();
memory.start();
System.out.println("Computer start end...");
}

public void shutdown() {
System.out.println("Computer shutdown begin...");
cpu.shutdown();
memory.shutdown();
System.out.println("Computer shutdown end...");
}
}

客户:

1
2
3
4
5
6
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();
System.out.println("=========");
computer.shutdown();
}

命令模式

概述

命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:

  • 命令模式使新的命令很容易地被加入到系统里。
  • 允许接收请求的一方决定是否要否决请求。
  • 能较容易地设计一个命令队列。
  • 可以容易地实现对请求的撤销和恢复。
  • 在需要的情况下,可以较容易地将命令记入日志。

命令模式涉及到五个角色,它们分别是:

  • 客户端(Client)角色:创建一个具体命令(ConcreteCommand)对象并确定其接收者。
  • 命令(Command)角色:声明了一个给所有具体命令类的抽象接口。
  • 具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
  • 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
  • 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

实例

接收者角色类:

1
2
3
4
5
6
7
8
public class Receiver {
/**
* 真正执行命令相应的操作
*/
public void action(){
System.out.println("执行操作");
}
}

抽象命令角色类:

1
2
3
4
5
6
public interface Command {
/**
* 执行方法
*/
void execute();
}

具体命令角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteCommand implements Command {
// 持有相应的接收者对象
private Receiver receiver = null;

public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}

@Override
public void execute() {
// 通常会转调接收者对象的相应方法,让接收者来真正执行功能
receiver.action();
}
}

请求者角色类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Invoker {
// 持有命令对象
private Command command = null;

public Invoker(Command command){
this.command = command;
}

public void action(){
command.execute();
}
}

客户端角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

public static void main(String[] args) {
// 创建接收者
Receiver receiver = new Receiver();
// 创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
// 创建请求者,把命令对象设置进去
Invoker invoker = new Invoker(command);
// 执行方法
invoker.action();
}
}

责任链模式

概述

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

责任链模式涉及到的角色如下所示:

  • 抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。
  • 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。

实例

抽象处理者角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Handler {

// 持有后继的责任对象
protected Handler nextHandler;

public abstract void handleRequest();

public Handler getNextHandler() {
return nextHandler;
}

public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
}

具体处理者角色:

1
2
3
4
5
6
7
8
9
10
11
12
public class ConcreteHandler extends Handler {

@Override
public void handleRequest() {
if(getNextHandler() != null) {
System.out.println("放过请求");
getNextHandler().handleRequest();
} else {
System.out.println("处理请求");
}
}
}

客户端类:

1
2
3
4
5
6
7
8
9
10
11
public class Client {

public static void main(String[] args) {
// 组装责任链
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
// 提交请求
handler1.handleRequest();
}
}

享元(Flyweight)模式

概述

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象d结构模式。

Java中字符串常量,Android中的Message,线程池等都运用了享元模式,单纯享元模式所涉及到的角色如下:

  • 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
  • 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。
  • 享元工厂(FlyweightFactory)角色 :本角色负责创建和管理享元角色。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

实例

抽象享元角色类:

1
2
3
public interface Flyweight {
public void operation(String state);
}

具体享元角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;

public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}

@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}

享元工厂角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FlyweightFactory {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();

public Flyweight factory(Character state){
// 先从缓存中查找对象
Flyweight fly = files.get(state);
if (fly == null) {
// 如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
// 把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}

客户端类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {

public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.factory(new Character('a'));
fly.operation("First Call");

fly = factory.factory(new Character('b'));
fly.operation("Second Call");

fly = factory.factory(new Character('a'));
fly.operation("Third Call");
}
}

模板模式

概述

准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法的角色:

  • 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
  • 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。

实例

抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractImageLoader {

// 抽象类定义整个流程骨架
public final void downloadImage() {
// 先获取最终的数据源URL
String finalImageUrl = getUrl();
// 然后开始执行下载
// ...
}

//以下是不同子类根据自身特性完成的具体步骤
protected abstract String getUrl();
}

具体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class WebpImageLoader extends AbstractImageLoader {
@Override
protected String getUrl() {
return "...";
}
}

public class JpgImageLoader extends AbstractImageLoader {
@Override
protected String getUrl() {
return ".....";
}
}

客户端类:

1
2
3
4
5
6
7
8
9
public class Client {

public static void main(String[] args) {
AbstractImageLoader loader = new WebpImageLoader();
loader.downloadImage();
loader = new JpgImageLoader();
loader.downloadImage();
}
}

策略模式

概述

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换,策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。这个模式涉及到三个角色:

  • 环境(Context)角色:持有一个Strategy的引用。
  • 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

实例

环境角色类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Context {
// 持有一个具体策略的对象
private Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

public void contextInterface() {
strategy.strategyInterface();
}
}

抽象策略角色类:

1
2
3
public interface Strategy {
public void strategyInterface();
}

具体策略角色类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConcreteStrategyA implements Strategy {

@Override
public void strategyInterface() {
// 相关的业务
}
}

public class ConcreteStrategyB implements Strategy {

@Override
public void strategyInterface() {
// 相关的业务
}
}

public class ConcreteStrategyC implements Strategy {

@Override
public void strategyInterface() {
// 相关的业务
}
}

客户端类:

1
2
3
4
5
6
7
8
9
public class Client {

public static void main(String[] args) {
Strategy strategy = new ConcreteStrategyB();
Context context = new Context(strategy);
// 通过不同的Strategy得到不同的结果
context.contextInterface();
}
}