面向对象六大原则
单一职责 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
也被称为最少知识原则,其定义为:一个对象应该对其他对象有最少的了解。这样可以降低耦合度,将当一个类发生改变的时候对其他类的影响降至最小。
备忘录模式
定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之处保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。

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() { } 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() {}
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。