一、概述
设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用。
拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。
二、创建型
1. 单例(Singleton)
意图
确保一个类只有一个实例,并提供该实例的全局访问点。
类图
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
实现
(一)懒汉式-线程不安全
以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null)
,并且此时 uniqueInstance 为 null,那么多个线程会执行 uniqueInstance = new Singleton();
语句,这将导致多次实例化 uniqueInstance。
1 | public class Singleton { |
(二)懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。
但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。
1 | public static synchronized Singleton getUniqueInstance() { |
(三)饿汉式-线程安全
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
1 | private static Singleton uniqueInstance = new Singleton(); |
(四)双重校验锁-线程安全
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
1 | public class Singleton { |
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton();
这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
1 | if (uniqueInstance == null) { |
uniqueInstance 采用 volatile 关键字修饰也是很有必要的。uniqueInstance = new Singleton();
这段代码其实是分为三步执行。
- 分配内存空间
- 初始化对象
- 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程下,有可能获得是一个还没有被初始化的实例,以致于程序出错。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
(五)枚举实现
这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。
1 | public enum Singleton { |
考虑以下单例模式的实现,该 Singleton 在每次序列化的时候都会创建一个新的实例,为了保证只创建一个实例,必须声明所有字段都是 transient,并且提供一个 readResolve() 方法。
1 | public class Singleton implements Serializable { |
如果不使用枚举来实现单例模式,会出现反射攻击,因为通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象。如果要防止这种攻击,需要在构造函数中添加防止实例化第二个对象的代码。
从上面的讨论可以看出,解决序列化和反射攻击很麻烦,而枚举实现不会出现这两种问题,所以说枚举实现单例模式是最佳实践。
使用场景
- Logger Classes
- Configuration Classes
- Accesing resources in shared mode
- Factories implemented as Singletons
JDK
2. 简单工厂(Simple Factory)
意图
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
类图
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
如果存在下面这种代码,就需要使用简单工厂将对象实例化的部分放到简单工厂中。
1 | public class Client { |
实现
1 | public interface Product { |
1 | public class ConcreteProduct implements Product { |
1 | public class ConcreteProduct1 implements Product { |
1 | public class ConcreteProduct2 implements Product { |
1 | public class SimpleFactory { |
1 | public class Client { |
3. 工厂方法(Factory Method)
意图
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。
类图
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
实现
1 | public abstract class Factory { |
1 | public class ConcreteFactory extends Factory { |
1 | public class ConcreteFactory1 extends Factory { |
1 | public class ConcreteFactory2 extends Factory { |
JDK
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
4. 抽象工厂(Abstract Factory)
意图
提供一个接口,用于创建 相关的对象家族 。
类图
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。
至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。
从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。
代码实现
1 | public class AbstractProductA { |
1 | public class AbstractProductB { |
1 | public class ProductA1 extends AbstractProductA { |
1 | public class ProductA2 extends AbstractProductA { |
1 | public class ProductB1 extends AbstractProductB { |
1 | public class ProductB2 extends AbstractProductB { |
1 | public abstract class AbstractFactory { |
1 | public class ConcreteFactory1 extends AbstractFactory { |
1 | public class ConcreteFactory2 extends AbstractFactory { |
1 | public class Client { |
JDK
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
5. 生成器(Builder)
意图
封装一个对象的构造过程,并允许按步骤构造。
类图
实现
以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。
1 | public class AbstractStringBuilder { |
1 | public class StringBuilder extends AbstractStringBuilder { |
1 | public class Client { |
1 | abcdefghijklmnopqrstuvwxyz |
JDK
- java.lang.StringBuilder
- java.nio.ByteBuffer
- java.lang.StringBuffer
- java.lang.Appendable
- Apache Camel builders
6. 原型模式(Prototype)
意图
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
类图
实现
1 | public abstract class Prototype { |
1 | public class ConcretePrototype extends Prototype { |
1 | public class Client { |
1 | abc |
JDK
三、行为型
1. 责任链(Chain Of Responsibility)
意图
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
类图
- Handler:定义处理请求的接口,并且实现后继链(successor)
实现
1 | public abstract class Handler { |
1 | public class ConcreteHandler1 extends Handler { |
1 | public class ConcreteHandler2 extends Handler{ |
1 | public class Request { |
1 | public enum RequestType { |
1 | public class Client { |
1 | request1 is handle by ConcreteHandler1 |
JDK
2. 命令(Command)
意图
将命令封装成对象中,以便使用命令来参数化其它对象,或者将命令对象放入队列中进行排队,或者将命令对象的操作记录到日志中,以及支持可撤销的操作。
类图
- Command:命令
- Receiver:命令接收者,也就是命令真正的执行者
- Invoker:通过它来调用命令
- Client:可以设置命令与命令的接收者
实现
设计一个遥控器,可以控制电灯开关。
1 | public interface Command { |
1 | public class LightOnCommand implements Command { |
1 | public class LightOffCommand implements Command { |
1 | public class Light { |
1 | /** |
1 | public class Client { |
JDK
3. 解释器(Interpreter)
意图
为语言创建解释器,通常由语言的语法和语法分析来定义。
类图
- TerminalExpression:终结符表达式,每个终结符都需要一个 TerminalExpression
- Context:上下文,包含解释器之外的一些全局信息
实现
以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。
例如一颗解析树为 D And (A Or (B C)),文本 “D A” 满足该解析树定义的规则。
这里的 Context 指的是 String。
1 | public abstract class Expression { |
1 | public class TerminalExpression extends Expression { |
1 | public class AndExpression extends Expression { |
1 | public class OrExpression extends Expression { |
1 | public class Client { |
1 | true |
JDK
- java.util.Pattern
- java.text.Normalizer
- All subclasses of java.text.Format
- javax.el.ELResolver
4. 迭代器(Iterator)
意图
提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
类图
- Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator;
- Iterator 主要定义了 hasNext() 和 next() 方法。
- Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。
实现
1 | public interface Aggregate { |
1 | public class ConcreteAggregate implements Aggregate { |
1 | public interface Iterator<Item> { |
1 | public class ConcreteIterator<Item> implements Iterator { |
1 | public class Client { |
JDK
5. 中介者(Mediator)
意图
集中相关对象之间复杂的沟通和控制方式。
类图
- Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。
- Colleague:同事,相关对象
实现
Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:
使用中介者模式可以将复杂的依赖结构变成星形结构:
1 | public abstract class Colleague { |
1 | public class Alarm extends Colleague { |
1 | public class CoffeePot extends Colleague { |
1 | public class Calender extends Colleague { |
1 | public class Sprinkler extends Colleague { |
1 | public abstract class Mediator { |
1 | public class ConcreteMediator extends Mediator { |
1 | public class Client { |
1 | doAlarm() |
JDK
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
6. 备忘录(Memento)
意图
在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
类图
- Originator:原始对象
- Caretaker:负责保存好备忘录
- Menento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。
实现
以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。
实现参考:Memento Pattern - Calculator Example - Java Sourcecode
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | 110 |
JDK
- java.io.Serializable
7. 观察者(Observer)
意图
定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。
主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。
类图
主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。
观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。
实现
天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。
1 | public interface Subject { |
1 | public class WeatherData implements Subject { |
1 | public interface Observer { |
1 | public class StatisticsDisplay implements Observer { |
1 | public class CurrentConditionsDisplay implements Observer { |
1 | public class WeatherStation { |
1 | CurrentConditionsDisplay.update: 0.0 0.0 0.0 |
JDK
8. 状态(State)
意图
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
类图
实现
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
1 | public interface State { |
1 | public class HasQuarterState implements State { |
1 | public class NoQuarterState implements State { |
1 | public class SoldOutState implements State { |
1 | public class SoldState implements State { |
1 | public class GumballMachine { |
1 | public class Client { |
1 | You insert a quarter |
9. 策略(Strategy)
意图
定义一系列算法,封装每个算法,并使它们可以互换。
策略模式可以让算法独立于使用它的客户端。
类图
- Strategy 接口定义了一个算法族,它们都具有 behavior() 方法。
- Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(in Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。
与状态模式的比较
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
实现
设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。
1 | public interface QuackBehavior { |
1 | public class Quack implements QuackBehavior { |
1 | public class Squeak implements QuackBehavior{ |
1 | public class Duck { |
1 | public class Client { |
1 | squeak! |
JDK
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
10. 模板方法(Template Method)
意图
定义算法框架,并将一些步骤的实现延迟到子类。
通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。
类图
实现
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
1 | public abstract class CaffeineBeverage { |
1 | public class Coffee extends CaffeineBeverage{ |
1 | public class Tea extends CaffeineBeverage{ |
1 | public class Client { |
1 | boilWater |
JDK
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
11. 访问者(Visitor)
意图
为一个对象结构(比如组合结构)增加新能力。
类图
- Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作
- ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
- ObjectStructure:对象结构,可以是组合结构,或者是一个集合。
实现
1 | public interface Element { |
1 | class CustomerGroup { |
1 | public class Customer implements Element { |
1 | public class Order implements Element { |
1 | public class Item implements Element { |
1 | public interface Visitor { |
1 | public class GeneralReport implements Visitor { |
1 | public class Client { |
1 | customer1 |
JDK
- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
12. 空对象(Null)
意图
使用什么都不做的空对象来替代 NULL。
一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。
类图
实现
1 | public abstract class AbstractOperation { |
1 | public class RealOperation extends AbstractOperation { |
1 | public class NullOperation extends AbstractOperation{ |
1 | public class Client { |
四、结构型
1. 适配器(Adapter)
意图
把一个类接口转换成另一个用户需要的接口。
类图
实现
鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。
要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!
1 | public interface Duck { |
1 | public interface Turkey { |
1 | public class WildTurkey implements Turkey { |
1 | public class TurkeyAdapter implements Duck { |
1 | public class Client { |
JDK
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- javax.xml.bind.annotation.adapters.XMLAdapter
2. 桥接(Bridge)
意图
将抽象与实现分离开来,使它们可以独立变化。
类图
- Abstraction:定义抽象类的接口
- Implementor:定义实现类接口
实现
RemoteControl 表示遥控器,指代 Abstraction。
TV 表示电视,指代 Implementor。
桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。
1 | public abstract class TV { |
1 | public class Sony extends TV{ |
1 | public class RCA extends TV{ |
1 | public abstract class RemoteControl { |
1 | public class ConcreteRemoteControl1 extends RemoteControl { |
1 | public class ConcreteRemoteControl2 extends RemoteControl { |
1 | public class Client { |
JDK
- AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
- JDBC
3. 组合(Composite)
意图
将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。
类图
组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。
组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
实现
1 | public abstract class Component { |
1 | import java.util.ArrayList; |
1 | public class Leaf extends Component { |
1 | public class Client { |
1 | Composite:root |
JDK
- javax.swing.JComponent#add(Component)
- java.awt.Container#add(Component)
- java.util.Map#putAll(Map)
- java.util.List#addAll(Collection)
- java.util.Set#addAll(Collection)
4. 装饰(Decorator)
意图
为对象动态添加功能。
类图
装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
实现
设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。
1 | public interface Beverage { |
1 | public class DarkRoast implements Beverage { |
1 | public class HouseBlend implements Beverage { |
1 | public abstract class CondimentDecorator implements Beverage { |
1 | public class Milk extends CondimentDecorator { |
1 | public class Mocha extends CondimentDecorator { |
1 | public class Client { |
1 | 3.0 |
设计原则
类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。饮料可以动态添加新的配料,而不需要去修改饮料的代码。
不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。
JDK
- java.io.BufferedInputStream(InputStream)
- java.io.DataInputStream(InputStream)
- java.io.BufferedOutputStream(OutputStream)
- java.util.zip.ZipOutputStream(OutputStream)
- java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
5. 外观(Facade)
意图
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
类图
实现
观看电影需要操作很多电器,使用外观模式可以实现一键看电影功能。
1 | public class SubSystem { |
1 | public class Facade { |
1 | public class Client { |
设计原则
最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
6. 享元(Flyweight)
意图
利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。
类图
- Flyweight:享元对象
- IntrinsicState:内部状态,相同的项元对象共享
- ExtrinsicState:外部状态
实现
1 | public interface Flyweight { |
1 | public class ConcreteFlyweight implements Flyweight { |
1 | import java.util.HashMap; |
1 | public class Client { |
1 | Object address: 1163157884 |
JDK
Java 利用缓存来加速大量小对象的访问时间。
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.Character#valueOf(char)
7. 代理(Proxy)
意图
控制对其它对象的访问。
类图
代理有以下四类:
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数,比如智能智能;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
实现
以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。
1 | public interface Image { |
1 | public class HighResolutionImage implements Image { |
1 | public class ImageProxy implements Image { |
1 | public class ImageViewer { |
JDK
- java.lang.reflect.Proxy
- RMI
参考资料
- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
- Gamma E. 设计模式: 可复用面向对象软件的基础 [M]. 机械工业出版社, 2007.
- Bloch J. Effective java[M]. Addison-Wesley Professional, 2017.
- Design Patterns
- Design patterns implemented in Java
- The breakdown of design patterns in JDK