03 适配器 代理 外观 装饰者

这 4 个模式很相近

适配器模式:包装另一个对象,并提供不同的接口
外观模式:包装许多对象,以简化他们的接口。
装饰者模式:包装另一个对象,并提供额外的行为。(额外的行为可以是灵活的多个装饰器(class)完成的, 也就是可以 n:1)
代理模式:包装另一个对象,并控制对它的访问. (只能 1:1 的代理一个对象)

区别

从它们的定义中可以总结出以下几点区别:

1)适配器模式强调的是转换接口。举例:JDBC是java定义的数据库操作规范,其已经定义好了操作数据库的接口,假设一个数据库厂商提供了一套操作数据库的接口,但是该接口并不符合JDBC的规范,也就是说JDBC规范规定的接口和数据库厂商提供的接口不一致。这个时候就可以使用适配器模式将数据库厂商的接口转换成JDBC规范要求的接口。其他三种模式均不提供接口转换的功能。

2)外观模式强调的是包装多个对象,以简化他们的接口。想象这样一种场景:你家的电器都是智能控制的,当你回家的时候你要打开空调、打开电视、打开热水器等等,这些都需要你自己一个个的去操作,这个时候你就会有这样一种需求,就是当你到家的时候只要进行一个操作,就能依次打开空调、电视、热水器等。这个时候就可以使用外观模式将空调、电视、热水器等的接口进行包装,只对外提供一个按钮。其他三种模式均强调的是对一个对象的包装。

3)装饰者模式强调的是为被装饰对象增加额外的行为。举例:java.io包。

4)代理模式强调的是对被代理对象进行控制。这些控制体现在很多方面,比如安全、权限控制等。

适配器模式和外观模式容易和其他模式进行区分。下面重点比较装饰者模式和代理模式。

5)装饰者模式和外观模式主要的区别就是,装饰者模式从来不创建被装饰的对象,它总是添加新功能到已经存在的对象上面;而代理模式在被代理对象不存在的时候会创建被代理对象。

6)装饰者模式可以通过嵌套装饰添加多重额外功能,而代理模式一般不推荐使用嵌套代理。

适配器模式

JAVA.IO 的各种流之间的转换, 实际上是使用的适配器.  

package com.leon.design;

import java.lang.annotation.Target;

public class ClientAdapter {

    public static void main(String[] args) {
        ClientAdapter clientAdapter = new ClientAdapter();    
        Adpatee adpatee = new Adpatee();        
        AdapterTarget target = new MyAdapter(adpatee);    
        // 客户本来的需求充电
        clientAdapter.charge(target);
    }
    public void charge(AdapterTarget t) {
        t.handleRequest();
    }
}

package com.leon.design;

public interface AdapterTarget {
    void handleRequest();
}

package com.leon.design;

/**
 * 适配器
 */
public class MyAdapter implements AdapterTarget {
    private Adpatee adpatee;
    @Override
    public void handleRequest() {
        adpatee.request();
    }    
    public MyAdapter(Adpatee adpatee) {
        this.adpatee = adpatee;
    }
}

package com.leon.design;

/**
 * 被适配的类
 *
 */
public class Adpatee {
    
    // 欧洲的具体充电方法
    public void request() {
        System.out.println("欧洲充电功能");
    }
}

代理模式

 

核心功能唱歌, 并没有改变, 代理帮助歌星做一些其他的地方, 面向切面编程AOP, 与装饰者模式的区别是, 装饰者是针对唱歌本身, 增加一些装饰, 为装饰对象增加一些额外的功能. 控制唱歌的方法, 要想让明星唱歌, 必须要先找代理谈价格.

静态代理: 我们自己定义代理类

动态代理: 有程序自动生成代理类

静态代理:

package com.leon.design;

public class ClientStaticProxy {

    public static void main(String[] args) {
        StarPlay singer = new SingerStar("周杰伦");
        StarPlay dancer = new DancerStar("王天一");
        
        ProxyStar proxySinger = new ProxyStar(singer);
        ProxyStar proxyDance = new ProxyStar(dancer);
        
        proxySinger.play();
        proxyDance.play();
    }

}

package com.leon.design;

public interface StarPlay {
    void play();
}


package com.leon.design;

public class ProxyStar implements StarPlay{
    StarPlay target;
    
    // 代理人需要帮助star 收取费用和收尾工作
    @Override
    public void play() {
        System.out.println("Proxy: Play 之前, 先收钱");
        target.play();
        System.out.println("Proxy: Play 之后, 再收尾");
    }

    public ProxyStar(StarPlay target) {
        this.target = target;
    }
    
}

package com.leon.design;

public class SingerStar implements StarPlay{
    private String singerName;
    @Override
    public void play() {
        System.out.println("I am " + singerName + ", i can sing.");
        
    }
    public SingerStar(String singerName) {
        this.singerName = singerName;
    }

}

package com.leon.design;

public class DancerStar implements StarPlay{
    private String dancerName;
    @Override
    public void play() {
        System.out.println("I am "+ dancerName +", I can dance.");
        
    }
    
    public DancerStar(String dancerName) {
        this.dancerName = dancerName;
    }

}

动态代码:

代理对象不需要实现接口, 但是目标对象(RealSubject) 仍然需要实现接口.

代理对象动态生成,利用 JDK API 在内存中动态构建代理对象 

package com.leon.design;

public class ClientDynimicProxy {

    public static void main(String[] args) {
        // 目标对象, 也是被代理对象
        StarPlay target = new SingerStar("宋祖英");
        // 给目标对象创建代理, 可以强转为接口
        StarPlay proxyInstance = (StarPlay) new ProxyFactory(target).getProxyInstance();
        
        proxyInstance.play();
        
        // 输出是 proxyInstance: com.leon.design.SingerStar@6bc7c054
//        System.out.println("proxyInstance: " + proxyInstance);
        // proxyInstance's type: class com.sun.proxy.$Proxy0
//        System.out.println("proxyInstance's type: " + proxyInstance.getClass());
    }

}

package com.leon.design;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;;

// 这里的代码完全是可以直接利用的, 目标对象是 Object 类型的
public class ProxyFactory {
    private Object target;
    
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    // 给目标对象动态生成一个代理对象
    // 参数1 ClassLoader loader: 制定当前目标对象使用的类加载器
    // 参数2 Class<?>[] interface: 目标对象实现的接口类型, 使用泛型方式
    // 参数3 InvocationHandler h: 事件处理, 执行目标对象方法时, 会帮当前目标对象的方法作为参数传入
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            // 每次调用目标对象方法时, 都会调用这个invoke, 所以可以从这加代理内容
            // 如果有多个方法调用时, 每个方法加入什么内容, 从这个人理解可以增加 if,else 判断
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("JDK代理开始");
                // 通过反射, 调用目标对象的方法
                Object retureVal = method.invoke(target, args);
                System.out.println("JDK代理结束");
                return retureVal;
            }
        });
    }
}

package com.leon.design;

public interface StarPlay {
    void play();
}

package com.leon.design;

public class SingerStar implements StarPlay{
    private String singerName;
    @Override
    public void play() {
        System.out.println("I am " + singerName + ", i can sing.");
        
    }
    public SingerStar(String singerName) {
        this.singerName = singerName;
    }

}

package com.leon.design;

public class DancerStar implements StarPlay{
    private String dancerName;
    @Override
    public void play() {
        System.out.println("I am "+ dancerName +", I can dance.");
        
    }
    
    public DancerStar(String dancerName) {
        this.dancerName = dancerName;
    }

}

还有一种叫做 Cglib 代理, 不需要实现接口, 就能实现代理. Cglib 包的底层时通过使用字节码处理框架 ASM 来转换字节码生成的新类.

装饰者模式

动态为一个对象增加一个新的功能, 被装饰新功能的方法, 可以先通过接口设计出来, 然后让被装饰的类和装饰类都实现这个接口.

关键是可以灵活的添加装饰对象.

角色:

接口/抽象类: Component,定义对象的方法

ConcreteComponent(被装饰类)(具体对象实现接口方法)

Decorator: 装饰器, 装饰的是ConcreteComponent的方法, 它含有一个被装饰的对象

 举例: 星巴克 咖啡的问题: 有单品咖啡, 然后, 还有很多收费的调料, 比如加 牛奶, 加巧克力, 加草莓, 加冰块等等很多很多.

那么,如果有客户要加 2份巧克力+1份牛奶+1份草莓, 我们不可能根据客户的需求都创建一个新的类(排列组合,很多种可能), 那怎么办呢?

解决办法就是递归思维, 我们先用一种调料来“装饰”, 之后把装饰完之后的还继续当做是"单品咖啡",继续用milk 来装饰, 这样递归操作, 直到装饰完所有的调料.

举例中的对象:

abstract BasicCoffee (基础类)

  private String description

  private int cost

  description() 方法, 介绍这杯咖啡的情况, 例如, 单品咖啡,加糖咖啡等

  cost()  // 具体计算咖啡的价格

ItalianCoffee(被装饰类)  单品意大利式咖啡, 继承 BasicCoffee

USACoffee(被装饰类)  单品美国咖啡, 继承 BasicCoffee

DecoratorCoffee(装饰器类) 用来装饰这个单品的, 持有单品的对象, 继承 BasicCoffee, 这个类可以定义为抽象类

MilkDecorator(牛奶作料) 给单品加牛奶, 继承 DecoratorCoffee, 这才是真正的具体装饰类

SugarDecorator(糖作料) 给单品加糖, DecoratorCoffee, 这才是真正的具体装饰类

ChocolatesDecorator(巧克力作料) 给单品加巧克力 DecoratorCoffee, 这才是真正的具体装饰类

现在需要2份巧克力 + 1份牛奶

package com.leon.design;

public interface BaseCoffee {
    void getCurrentDescription();
    int getCurrentCost();
}

package com.leon.design;

public class ItalianCoffee implements BaseCoffee{
    private String description = "+ ItalianCoffee 1 cup";
    private int cost = 20;
    
    @Override
    public int getCurrentCost() {
        return cost;
    }

    @Override
    public void getCurrentDescription() {
        System.out.print(this.description + ":price=" + cost + ". ");    
    }
}

package com.leon.design;

public class UASCoffee implements BaseCoffee{
    private String description = "+ USACoffee 1 cup";
    private int cost = 15;

    
    @Override
    public int getCurrentCost() {
        return cost;
    }

    @Override
    public void getCurrentDescription() {
        System.out.print(this.description + ":price=" + cost + ". ");
    }
}

package com.leon.design;

public abstract class DecoratorCoffee implements BaseCoffee{
    BaseCoffee basecoffee;
    
    public DecoratorCoffee(BaseCoffee basecoffee) {
        this.basecoffee = basecoffee;
    }

    @Override
    public abstract void getCurrentDescription(); 
    

    @Override
    public abstract int getCurrentCost(); 
}

package com.leon.design;

public class MilkDecorator extends DecoratorCoffee{
    private String description = "+ milk 1 time";
    private int cost = 5;
    
    public MilkDecorator(BaseCoffee basecoffee) {
        super(basecoffee);
    }
    
    @Override
    public void getCurrentDescription() {
        basecoffee.getCurrentDescription();
        System.out.print(this.description + ":price=" + this.getCurrentCost() + ". ");
    }

    @Override
    public int getCurrentCost() {
        return basecoffee.getCurrentCost() + this.cost;
    }
    
}

package com.leon.design;

public class ChocolateDecorator extends DecoratorCoffee{
    private String description = "+ Chocolate 1 time";
    private int cost = 10;
    
    public ChocolateDecorator(BaseCoffee basecoffee) {
        super(basecoffee);
    }
    
    @Override
    public void getCurrentDescription() {
        basecoffee.getCurrentDescription();
        System.out.print(this.description + ":price=" + this.getCurrentCost() + ". ");
    }

    @Override
    public int getCurrentCost() {
        return basecoffee.getCurrentCost() + this.cost;
    }
    
}

package com.leon.design;

public class DecoratorClient {

    public static void main(String[] args) {
        BaseCoffee coffee = new ItalianCoffee();
        
        // 放牛奶 1 次
        coffee = new MilkDecorator(coffee);

        // 放牛奶 1 次
        coffee = new MilkDecorator(coffee);
        // 放巧克力 1 次
        coffee = new ChocolateDecorator(coffee);
        coffee.getCurrentDescription();
    }

}

在 JDK 的 IO 流的 InputStream 是一个抽象类, 这里边的继承关系, 就是一种装饰类.

外观模式(过程模式)

对 sub system中的一组接口提供一个外部界面(外观). 一种封装的思想. 一般情况下, 都会潜移默化的使用它.

家庭影院项目: 我们可以做一个 Facade(面板)类, 封装这个类的方法, 从而提供一个统一的接口给用户(ready, play, pause, end)

DVD 播放器(开,关,播放, 暂停)

投影仪(准备:插电源, 放画布 播放: 启动, 暂停:没有, 结束:拔电源, 收画布)

立体声音响(准备:插电源, 调整音量 播放: 启动, 暂停:声音暂停, 结束:拔电源)

在一组接口(ready, play, pause, end) 中分别调用子系统的对应方法.

(代码简单,不敲了, 直接画图把)

原文地址:https://www.cnblogs.com/moveofgod/p/12508643.html