设计模式

面向对象六大原则

  • 单一职责 Single Responsibility Principle

    单一职责的定义就是一个类应该只有一个引起它变化的原因,也就是一个类应该是一组相关性很高的函数、数据的封装。划分界限不是死的,大都依靠经验而定。

  • 开闭原则 Open Close Principle

    定义:软件中的对象(类、模板、函数等)应该对应扩展是开放的,对于修改是封闭的。主要通过继承和接口来实现。开闭原则可以使程序更加灵活和稳定。完全符合开闭原则是理想化的状态。

  • 里式替换 Liskov Substitution Principle

    第一定义:如果对每一个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有对象O1都代换成O2时,程序P的行为没有发生变化,那么S就是类型T的子类。咋一看有点不好理解。那看第二定义:所有引用基类的地方必须能透明的使用其子类的对象。也就是说,只要父类能出现的地方,子类就能出现,而且替换为子类也不会产生任何错误或异常。实现里式替换的核心原理是抽象。抽象的实现又依赖于继承。

    继承的优缺点都很明显:

    优点:

    ​ 代码重用,减少创建类的成本(少写些代码?),每个子类都拥有父类的方法和属性;

    ​ 子类与父类基本相似,但又与父类有所区别;

    ​ 提高代码的可扩展性。

    缺点:

    ​ 继承是侵入式的,只要继承就必须拥有父类的所有属性和方法;

    ​ 可能造成子类代码冗余、灵活性降低

  • 依赖倒置 Dependence Inversion Principle

    依赖倒置指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次模块的实现细节(?)。

    关键点:

    ​ 高层模板不应该依赖底层模板,应该依赖其抽象;

    ​ 抽象不应该依赖细节;

    ​ 细节应该依赖抽象。

    依赖倒置在Java中的表现:模板间的依赖通过抽象产生,实现类之间不发生直接的依赖关系。

  • 接口隔离 Interface Segregation Principle

    第一定义:客户端不应该依赖它不需要的接口。第二定义:类间的依赖关系应该建立在最小的接口上。

  • 迪米特原则 Law of Demeter

    也被称为最少知识原则,其定义为:一个对象应该对其他对象有最少的了解。这样可以降低耦合度,将当一个类发生改变的时候对其他类的影响降至最小。

备忘录模式

定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之处保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。

UMLofMemento

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
35
36
37
38
39
40
package memento;

public class Game {
private int mCheckpoint = 1;
private int mLifeValue = 100;
private String mWeapon = "Lightsaber";

void play() {
System.out.println("playing " + String.format("level%d", mCheckpoint));
mLifeValue -= 10;
System.out.println("next level");
mCheckpoint++;
System.out.println("reach " + String.format("level%d", mCheckpoint));
}

void quit() {
System.out.println("attribute:"+this.toString());
System.out.println("quit");
}

Memento createMemento() {
Memento memento = new Memento();
memento.mCheckpoint=mCheckpoint;
memento.mLifeValue = mLifeValue;
memento.mWeapon=mWeapon;
return memento;
}

void restore(Memento memento) {
this.mCheckpoint=memento.mCheckpoint;
this.mLifeValue =memento.mLifeValue;
this.mWeapon=memento.mWeapon;
System.out.println("restores attribute:"+this.toString());
}

@Override
public String toString() {
return "Checkpoint " + mCheckpoint + " LifeValue " + mLifeValue + " Weapon " + mWeapon;
}
}

单例模式

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

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
35
36
37
38
39
40
41
42
43
public class LogUtil {

private static LogUtil sLogUtil;
public final int DEGUB = 0;
public final int INFO = 1;
public final int ERROR = 2;
public final int NOTHING = 3;
public int level = DEGUB;

private LogUtil() {
}

// 双重锁定(DCL)(Double-Check Locking)
public static LogUtil getInstance() {
if (sLogUtil == null) {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
}
}
return sLogUtil;
}

public void debug(String msg) {
if (DEGUB >= level) {
System.out.println(msg);
}
}

public void info(String msg) {
if (INFO >= level) {
System.out.println(msg);
}
}

public void error(String msg) {
if (ERROR >= level) {
System.out.println(msg);
}
}

}

只有在sLogUtil还没被初始化的时候才会进入到第3行,然后加上同步锁。等sLogUtil一但初始化完成了,就再也走不到第3行了,这样执行getInstance()也不会再受到同步锁的影响,效率上会有一定的提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static volatile Singleton instance;

private Singleton() {}

// 双重锁定(Double-Check Locking)
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}

更多创建方法

第 1 种方式:

publci 静态成员是 final 域

1
2
3
4
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
}

风险是可以通过反射调用私有构造方法,要抵御可以在构造方法中判断,创建第二个实例时抛出异常。

第 2 种方式:

public 的是静态工厂方法

1
2
3
4
5
6
7
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public static Elvis getInstance() {
return INSTANCE;
}
}

有同样的风险。

同时这两种方法还有反序列化的问题。解决需要将所有实例域声明为 transient,并提供 readResolve 方法:

1
2
3
private Object readResolve() {
return INSTANCE;
}

上面两种又称为饿汉模式。

第 3 种,通过枚举实现单例:

1
2
3
4
5
6
public enum Elvis {
INSTANCE;
public void someMethod() {

}
}

没有反射和序列化风险。

第 4 种,懒汉模式

1
2
3
4
5
6
7
8
9
10
class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCW = new Singleton();
}
return INSTANCE;
}
}

线程不安全。

第 5 种,懒汉模式、线程安全

1
2
3
4
5
6
7
8
9
10
class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}

效率低。

第 6 种,双重检查 DCL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized(Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

如果单例已经创建,则不再进行同步。

存在 DCL 失效的问题。

INSTANCE = new Singleton();

可以分为三个步骤:

给 Singleton 实例分配内存空间;

调用构造方法,初始化成员变量;

将 INSTANCE 对象执行分配的内存空间(此时 INSTANCE 就非 null 了)。

因为 JVM 的指令重排序,可以 1 - 3 - 2 这样执行,就可能造成线程读取到的是一个还未初始化的实例,造成 DCL 失效。

解决是加上 volatile 关键字。

更多方式

观察者模式

代理模式

为其他对象提供一种代理以控制对这个对象的访问。[DP]

静态代理

提供一个代理对象,代理对象持有对真实对象的一个引用,在代理对象的方法中调用真实对象的方法实现代理。

动态代理

需要实现 InvocationHandler 接口。

动态代理可以实现 AOP,可以在不改动已有代码结构的情况下增强或控制对象的行为。

享元模式

运用共享技术有效地支持大量细粒度的对象。

Android 中的 Message。