设计模式:结构型
三名无业青年,本着 “替人排忧、替人解难、替人受过” 的宗旨,成立了一个 3T 公司。
一、装饰器 - Decorator#
3T 公司提供了多种服务,比如聊天类与催债类:
interface Service {
// 每一种服务都要收取一定费用
int cost();
}
class CommunicationService implements Service {
@Override
public int cost() {
return 5;
}
}
class FinancialService implements Service {
@Override
public int cost() {
return 10;
}
}
不论聊天还是催债都可以提供额外的服务 T1(排忧),T2(解难) 和 T3(受过)。附加额外的服务就需要提高费用,而提高费用并不影响原先的费用计算。
abstract class TDecorator implements Service {
// 抽象类装饰器持有 Service 对象
protected Service service;
}
// 排忧
class T1 extends TDecorator {
public T1(Service service) {
this.service = service;
}
@Override
public int cost() {
return 1 + service.cost();
}
}
// 解难
class T2 extends TDecorator {
public T2(Service service) {
this.service = service;
}
@Override
public int cost() {
return 1 + service.cost();
}
}
// 受过
class T3 extends TDecorator {
public T3(Service service) {
this.service = service;
}
@Override
public int cost() {
return 1 + service.cost();
}
}
今天,3T 公司收到一个催债任务,雇主要求了全部三个额外服务,最后收费 13 元。
public class TTT {
public static void main(String[] args) {
Service s = new FinancialService();
s = new T1(s); // 排忧
s = new T2(s); // 解难
s = new T3(s); // 受过
System.out.println(s.cost()); // 收费 13
}
}
二、适配器 - Adapter#
一位男青年来到 3T 公司,他已经单身太久了,渴望找个女伴。可是 3T 公司全是男员工,秉持着 “解决一切问题” 的服务理念,于观打算男扮女装…
interface Boy {
String boyHookUp();
}
interface Girl {
String girlHookUp();
}
class BoyInLove implements Boy {
@Override
public String boyHookUp() {
return "I'm in love with you! beauty!";
}
}
客户需要一个女伴,可公司只有男人,为了满足客户的需求,公司需要完成服务的适配,男扮女装的过程就是适配,它是为了适应客户的需求而做出的改动,同时又不影响到原本的其他功能,适配器如下:
class BoyAdapter implements Girl {
// 适配器持有原对象
Boy boy;
public BoyAdapter(Boy boy) {
this.boy = boy;
}
@Override
public String girlHookUp() {
String hi = boy.boyHookUp();
// 改变原对象行为以适应需求
return hi.replace("beauty", "handsome");
}
}
就这样于观适配完成,前往赴约…
public class Date {
public static void main(String[] args) {
Boy boy = new BoyInLove();
Girl girl = new BoyAdapter(boy); // 男扮女装
System.out.println(girl.girlHookUp());
}
}
I'm in love with you! handsome!
三、代理 - Proxy#
3T 公司已经营业三个月了,杨重发现客户的大多数需求都是重复的,最多的就是跑腿。为了显得高级,这个业务被称作代理。一日,某客户想要买两张电影票,可他又不想特地跑去电影院,于是他找到 3T 公司,定了代理业务。电影当然是客户自己去看,不过买票和送客户女伴回家这些事就交由 3T 公司负责了。
interface TicketsRequired {
void enjoy(String name1, String name2);
}
class MovieFan implements TicketsRequired {
@Override
public void enjoy(String theaterName, String homeName) {
System.out.println("Client watching a movie...");
}
}
class MovieFanProxy implements TicketsRequired {
MovieFan mf;
MovieFanProxy(MovieFan mf) {
this.mf = mf;
}
@Override
public void enjoy(String theaterName, String homeName) {
System.out.println("Booking tickets for client from " + theaterName); // 前处理
mf.enjoy(theaterName, homeName); // 客户看电影
System.out.println("Sending the client's girlfriend to " + homeName); // 后处理
}
}
public class Movie {
public static void main(String[] args) {
MovieFan mf = new MovieFan();
TicketsRequired tr = new MovieFanProxy(mf);
tr.enjoy("--the first theater--", "--Boulevard of Broken Dreams--");
}
}
Booking tickets for client from --the first theater--
Client watching a movie...
Sending the client's girlfriend to --Boulevard of Broken Dreams--
电影结束后,杨重在门口等客户的女伴。昏暗的灯光下,那女孩走路有些失稳,杨重忙上前搀扶:“您这喝了不少吧?” 女孩没说话,只轻声一笑坐进了车里。
回去路上,杨重透过后视镜看着那女孩望着车窗外,秀发遮着侧脸,眼里闪着迷人的光。杨重搭话:“您男朋友可真是个阔少爷”。后座传来一阵诡异的笑声,杨重回头定睛一看,这女伴竟是于观…
3.1 动态代理#
“真有你的,于观,” 杨重拍了拍方向盘,笑道,“这小子真就落你手里了。”
“人傻钱多,” 于观摘下假发,伸手拍了拍杨重的肩,“刚才你看我的眼神不太对劲啊?”
“真有你的!”
两人聊起公司的业务,杨重告诉于观,客户都是有钱的主,他们的活动都得买票,3T 公司快成了买票公司了。3T 公司成立之初,为了服务的贴心周到,针对每一类客户都需要定制专属的代理方案(代理类)。可是杨重渐渐发现不管客户是看电影还是看球赛,公司的服务不外乎就是买票送客户进去和结束了送客户回家。对每一类客户都定制方案实在是费时费力。
回到公司,杨重和于观把他们的想法告诉了马青。马青学过计算机,一听这立马来了劲,“我们该升级我们的代理服务了,新的服务叫做动态代理!有了动态代理,就不需要为每一个客户都定制方案了,换句话说,所有客户都只有一套方案:到时候看着办!”
原本的代理类持有原对象,这就存在耦合,这种静态代理只能代理具体的类,有多少类就需要多少代理类。如果代理方式需要改变,每一种代理类也都需要改变,这是十分繁杂的。解耦的关键是独立出代理的步骤,InvocationHandler 实现了这个功能,3T 公司的 TTTHandler 设计如下:
class TTTHandler implements InvocationHandler {
Object original;
TTTHandler(Object original) {
this.original = original;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 找到具体方法的执行语句
if (method.getName().equals("enjoy")) {
System.out.println("Booking tickets for client from " + args[0]); // 前处理
method.invoke(original, args); // 通过反射来执行
System.out.println("Sending the client's girlfriend to " + args[1]); // 后处理
}
return null;
}
}
虽然 TTTHandler 同样持有原对象,但是它并不知道这个对象到底是什么。可是如果不知道原对象具体是什么,该如何代理呢?答案就是反射。在通过 Proxy.newProxyInstance()
获取代理对象时,需要传入三个变量:接口的类加载器、接口的 Class 类、handler。 这里的接口指的就是具体要代理的功能的接口,那么可以这样理解:动态代理不再代理具体的类,而是代理接口,即只代理需要代理的功能。
3T 公司很快就实行了动态代理的模式,杨重再次接到订单,同样是买票,这次他不需要再定计划了,因为他根本不关心买的是电影票还是球赛票,他只知道他要买票(只传入了接口)。这样带来的好处就是杨重一个人就可以处理所有的买票任务了。
可是事情没有这么简单,杨重还要负责提醒客户准时参加活动,由于事先并不知道客户的具体活动是什么,杨重只好 “到时候看着办” 了。所谓的 “到时候看着办” 就是在运行期通过反射中的 invoke()
来执行具体操作。
public class Movie {
public static void main(String[] args) {
TicketsRequired tr = new MovieFan();
tr = getProxy(tr);
tr.enjoy("--the first theater--", "--Boulevard of Broken Dreams--");
}
public static TicketsRequired getProxy(Object proxy) {
InvocationHandler handler = new TTTHandler(proxy);
return (TicketsRequired) Proxy.newProxyInstance(
TicketsRequired.class.getClassLoader(),
new Class[] { TicketsRequired.class },
handler);
}
}
Booking tickets for client from --the first theater--
Client watching a movie...
Sending the client's girlfriend to --Boulevard of Broken Dreams--
四、外观 - Facade#
于观带着他的 “男朋友” 走进了 3T 公司,“请给我们来一个 ‘欢乐一日’ 套餐。”
杨重忍着笑:“好嘞!这就给您安排。”
class SubSystem {
public void dinner() {
System.out.println("Dinner!");
}
public void film() {
System.out.println("Film!");
}
public void shopping() {
System.out.println("Shopping!");
}
}
public class Facade {
private SubSystem subSystem = new SubSystem();
public void dayOfFun() {
subSystem.dinner();
subSystem.film();
subSystem.shopping();
}
public static void main(String[] args) {
new Facade().dayOfFun(); // 一键完成三个服务
}
}
Dinner!
Film!
Shopping!
3T 公司在不管改革,近期推出的 “套餐” 就是几种不同的服务的组合。当客户需要多种不同服务时,就不需要逐一安排,只需要使用 “套餐” 就可以了。
五、享元 - Flyweight#
马青最近在学 Java 中的设计模式,他发现 Java 利用缓存来加速大量小对象的访问时间,这就是享元。
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.Character#valueOf(char)
六、组合 - Composite#
马青在编程时,经常用到 Map 和 Collection,其中的 addAll()
就是组合。
- 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)