设计原则 S.O.L.I.D
1. 单一责任原则
The Single Responsibility Principle
修改一个类的原因应该只有一个。
换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
2. 开放封闭原则
The Open Closed Principle
类应该对扩展开放,对修改关闭。
扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。
符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
3. 里氏替换原则
The Liskov Substitution Principle
子类对象必须能够替换掉所有父类对象。
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
4. 接口分离原则
The Interface Segregation Principle
不应该强迫客户依赖于它们不用的方法。
因此使用多个专门的接口比使用单一的总接口要好。
5. 依赖倒置原则
The Dependency Inversion Principle
高层模块不应该依赖于低层模块,二者都应该依赖于抽象; 抽象不应该依赖于细节,细节应该依赖于抽象。
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
单例
确保一个类只有一个实例,并提供该实例的全局访问点。
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
懒汉式 - 非线程安全
复制 public class Singleton1 {
private static Singleton1 uniqueInstance;
private Singleton1() {}
public static Singleton1 getInstance() {
if (uniqueInstance == null)
uniqueInstance = new Singleton1();
return uniqueInstance;
}
}
懒汉式 - 线程安全
复制 public class Singleton2 {
private static Singleton2 uniqueInstance;
private Singleton2() {}
// 使用同步方法,避免多线程下创建多个实例
public static synchronized Singleton2 getInstance() {
if (uniqueInstance == null)
uniqueInstance = new Singleton2();
return uniqueInstance;
}
}
双重校验锁
复制 public class Singleton3 {
// volatile确保当uniqueInstance被实例化时,其他线程能感知到
private static volatile Singleton3 uniqueInstance;
private Singleton3() {}
// 双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁
public static Singleton3 getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton3.class) {
// 进行第二次校验
if (uniqueInstance == null)
uniqueInstance = new Singleton3();
}
}
return uniqueInstance;
}
}
静态内部类
当 Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
复制 public class Singleton4 {
private Singleton4() {}
private static class SingletonHolder {
private static final Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getInstance() {
return SingletonHolder.INSTANCE;
}
}
简单工厂
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
复制 // pizza接口
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
复制 // 各种Pizza实现类,以CheesePizza为例
public class CheesePizza implements Pizza {
public void prepare() { System.out.println("Preparing cheese pizza...") }
public void bake() { System.out.println("Baking cheese pizza...") }
public void cut() { System.out.println("Cutting cheese pizza...") }
public void box() { System.out.println("Boxing cheese pizza...") }
}
复制 // 简单pizza工厂,帮助客户创建pizza
public class SimplePizzaFactory {
// 所有的客户都使用这个方法来实例化新对象
public Pizza createPizza(String type) {
Pizza pizza = null;
// 创建不同类型的pizza
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
}
return null;
}
}
复制 // 客户类
public class PizzaStore {
// 保存一个对SimplePizzaFactory的引用
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
// 通过传入订单类型来使用工厂创建pizza
pizza = factory.createPizza(type);
return pizza;
}
}
工厂
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
在简单工厂中,是由一个对象 负责所有具体类的实例化;而在工厂中,是由一群子类 来负责实例化。
复制 // 抽象pizza工厂
public abstract class PizzaStore {
public void orderPizza(String type) {
Pizza pizza;
// PizzaStore的子类在createPizza()方法中处理对象的实例化
pizza = createPizza(type);
return pizza;
}
// 实例化pizza的责任被移到一个方法中,这个方法就如同是一个工厂
protected abstract Pizza createPizza(String type);
}
复制 // 负责创建pizza的具体子类,以NewYorkPizzaStore为例
public class NYPizzaStore extends PizzaStore {
public Pizza createPizza(String type) {
Pizza pizza;
// 创建不同类型的NYStylePizza
if (type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
} else if (type.equals("clam")) {
pizza = new NYStyleClamPizza();
}
return pizza;
}
}
抽象工厂
迭代器
观察者
EventListener、消息队列常用的订阅-发布模型
代理
扩展阅读:动态代理与静态代理
由代理对象组合基础对象,控制对基础对象的访问,扩展新的功能。
代理对象 (Proxy) 通过组合的方式持有基础对象 (RealSubject) 的引用,并且实现了Subject接口。从客户端的角度,Proxy代理对象可以作为Subject接口的替代品。
静态代理
复制 public interface Payment {
void pay();
}
复制 public class RealPayment implements Payment {
public void pay() {
System.out.println("Paying...");
}
}
复制 public class Proxy implements Payment {
private Payment payment;
// 保存被代理的类的引用
public Proxy(Payment p) {
payment = p;
}
public void pay() {
// 扩展功能
System.out.println("Before payment");
// 调用被代理的类的方法
payment.pay();
System.out.println("After payment")
}
}
Java动态代理
复制 // 定义代理对象调用方法时希望执行的动作
Object invoke(Object proxy, Method method, Object[] args)
// 构造实现指定接口的代理类的一个新实例,需要提供类加载器、接口和处理器
static Object new ProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler handler)
以下程序使用代理对象对二分查找进行跟踪。首先使用1 - 1000 整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数,最后打印出与之匹配的元素。
复制 public class ProxyTest {
public static void main(String[] args) {
Object[] elements = new Object[100];
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
// 创建一个代理类
Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
elements[i] = proxy;
}
// 生成一个随机数
Integer key = new Random().nextInt(elements.length) + 1;
// 在数组中寻找该随机数
int result = Arrays.binarySearch(elements, key);
System.out.println(result);
}
}
// 动态代理类,用于跟踪方法的执行流程
class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object t) {
target = t;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
System.out.print(target);
// 打印方法名
System.out.print("." + m.getName() + "(");
// 打印方法参数
if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i < args.length - 1) System.out.print(", ");
}
}
System.out.println(")");
// 执行方法
return m.invoke(target, args);
}
}
运行结果:
复制 50.compareTo(77)
75.compareTo(77)
88.compareTo(77)
81.compareTo(77)
78.compareTo(77)
76.compareTo(77)
77.compareTo(77)
76