设计模式:结构型

三名无业青年,本着 “替人排忧、替人解难、替人受过” 的宗旨,成立了一个 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#

马青在编程时,经常用到 MapCollection,其中的 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)

2019-2021 © lil-q