设计模式分类
- 创建性模型:对对象创建过程的各种问题和解决方法的总结
工厂模式(Factory,Abstract Factory)
单例模式(singleton)
构建器模式(Buider)
原型模式(Pototype)
- 结构型模式:是对软件设计结构的总结,关注与类、对象继承、组合方式的实践经验
桥接模式(Bridge)
适配器模式(Adapter)
装饰器模式(Decorator)
代理模式(Proxy)
组合模式(Composite)
外观模式(Facade)
享元模式(Flyweight)
- 行为型模式:从类与对象交互、职责划分等角度总结的模式
策略模式(strategy)
解释器模式(Interpeter)
命令模式(Command)
观察者模式(Observer)
迭代器模式(Iterator)
模板方法模式(Template Method)
访问者模式(Visitor)
创建应用场景
识别装饰器
1.案例
InputSteam是一个抽象类,标准库提供FileInputStream,ByteArrayInputStream等各种不同子类,分别从不同角度对InputStream进行扩展
2.识别依据
通过**识别类设计特征**判断,即类构造函数以**相同**的**抽象类或接口**作输入参数,本质保证同类型实例,对目标对象调用,会通过保证覆盖过的方法,以后调用被包装实例,
自然会实现增加额外逻辑的目的,即所谓“装饰”,实例如下:BufferInputStream经过包装,为输入流过程增加缓存,类似包装器还可多次嵌套,不断增加不同层次功能。
public BufferedInputStream(InputStream in)
- InputStream装饰器实践
构建器模式
1.案例
最新jdk版本中 HTTP/2 Client API ,下面这个场景HttpRequest工厂,即典型构建起模式,通常被实现成fluent风格的API,也叫方法链
HttpRequest request = HttpRequest.newBuilder(new URI(uri))
.header(headerAlice, valueAlice)
.headers(headerBob, value1Bob,
headerCarl, valueCarl,
headerBob, value2Bob)
.GET()
.build();
2.优点:优雅解决否则复杂对象、传入复杂参数的麻烦,"复杂"是指类似要输入参数组合多,如果用构造函数,往往要为每一种可能参数组合实现相应构造函数
一系列的复杂构造函数会让代码可读性和维护性变得很差。
3.小结:上一步反映创建型模式初衷,即对象创建过程单独抽象出来,从结构把对象使用逻辑和创建逻辑相互独立,隐藏对象实例的细节,进而为使用者实现更加规范、统一的逻辑。
单例模式
- 简单例子
1.饱死鬼模式:无论应用是否请求单例对象,都要创建。
缺点:但是不能保证额外的对象不被创建处理,别人完全使用默认空参构造器可以直接"new Singleton()"
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
- 改进后单例模式
结合ConcurrentHashMap用到的lazy-load机制,可以在对象需要进行实例化,再被应用线程请求获取,改善初始内存开销,单例同样适用。
缺点:单线程不存在问题,在并发时需要考虑线程安全。可以用double check。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 最终单例模式
使用volatile 类型修饰单例对象提供线程间可见性,并初始化为null(改善初始内存开销),已经保证getInstance返回的是**初始完全**的对象
在同步之前进行null检查,以尽量避免开销较大的同步块
直接在class基本进行同步,保证线程安全的类方法(类方法即静态方法)调用。
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) { // 尽量避免重复进入同步块
synchronized (Singleton.class) { // 同步.class,意味着对同步类方法调用
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
争论:争议较大的是volatile修饰类的私有静态变量,当Single通本身有多个成员变量是,需要保证初始化完成后,才能被get到。
现代Java中,内存排序模型(JMM)已经非常完成,通过volatile的write或read,能够保证happen-before,避免volatile变量前面的类成员变量被排序到后面,造成数据修改问题,即避免编译器的指令重拍。
换句话说,构造的对象store指令能够被保证一定在volatile read之前。
当然,也有推荐使用内部类持有静态对象的方式实现,理论是依据对象初始化过程的初始化锁。这种和前面double-check锁都能保证线程安全,但语法稍显羞涩,不具特别优势。如下:
public class Singleton {
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
private static class Holder {
private static Singleton singleton = new Singleton();
}
}
- java核心库单例
java.lang.Runtime并未实现double-check锁,而是静态实例别声明为final,被通常实践忽略,一定程度保证实例不被串改(反射之类可以绕过是私有访问限制)
也有限保证执行顺序语义,代码如下:
private static final Runtime currentRuntime = new Runtime();
private static Version version;
// …
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don’t let anyone else instantiate this class */
private Runtime() {}