《设计模式之禅》学习及源码示例---装饰模式\适配器模式\代理模式

一、装饰模式

定义:

动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更加灵活

组成角色:
Component:抽象被装饰者

一个接口或者是抽象类,通常是我们需要修饰的类的接口或者是继承的抽象类

ConcreteComponent:具体被装饰者

我们需要装饰的对象就是这个类,这个类一般会实现或者继承Component

Decorator:装饰抽象角色

这个接口是一个抽象类实现或继承Component,作为具体装饰角色和被装饰角色的中转对象,非常重要

ConcreteDecorator:具体的装饰角色

ConcreteDecoratorA\ConcreteDecoratorB\ConcreteDecoratorC.....作为具体的装饰实现,继承Decorator现象具体对ConcreteComponent的装饰

需求:现在我们要给一个毛坯房装修

代码演示:

public interface Component {
    void operate();
}
public class ConcreteComponent implements Component {
    @Override
    public void operate() {
        System.out.println("这是一间毛坯房");
    }
}
public abstract class Decorator implements Component{
    private Component component=null;
    //用于传递被装饰者
    public Decorator(Component component) {
        this.component = component;
    }

    //给具体装饰者执行,并在其之上进行添加前后置功能
    @Override
    public void operate() {
        this.component.operate();
    }
}
public class ConcreteDecoratorDoor extends Decorator {
    public ConcreteDecoratorDoor(Component component) {
        super(component);
    }
    private void method1(){
        System.out.println("装个门");
    }
    private void method2(){
        System.out.println("门上装个眼睛");
    }

    @Override
    public void operate() {
        super.operate();
        method1();
        method2();
    }
}
public class ConcreteDecoratorWindow extends Decorator {
    public ConcreteDecoratorWindow(Component component) {
        super(component);
    }

    private void method1(){
        System.out.println("装个窗户");
    }
    private void method2(){
        System.out.println("窗户换上防弹玻璃");
    }

    @Override
    public void operate() {
        super.operate();
        method1();
        method2();
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Component mopifang=new ConcreteComponent();
        mopifang=new ConcreteDecoratorDoor(mopifang);
        mopifang=new ConcreteDecoratorWindow(mopifang);
        mopifang.operate();
    }
}

源码使用示例:JDK的IO使用了装饰模式


InputStream作为Component,FilterInputStream是Decorator,BufferedInputStream通过继承FilterInputStream,给FileInputStream装饰拓展了功能

public class FilterInputStream extends InputStream{
    protected  InputStream in;
    
    protected  FilterInputStream(InputStream in){
        this.in = in;
    }   
    ...
}
public class BufferedInputStream extends FilterInputStream {
..
}

通过传入参数实现相关的装饰

public class Test {
    public static void main(String[] args) {
        try {
            InputStream in = new FileInputStream(new File("c:\\xxx\\xxxx"));
            BufferedInputStream in1 = new BufferedInputStream(in);
            LineNumberInputStream in2 = new LineNumberInputStream(in1);
                         ....
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、适配器模式

定义:

将一个类的接口变成客户端所期待的另一种接口,从而使原本因为接口不匹配而无法在一起工作的两个类能够在一起工作

组成角色:
Target目标接口

该接口就是我们的期待接口,其他接口向这个接口转换

Adaptee源角色

希望被转换的接口

Adapter适配器角色

适配器角色职责是把源角色转为目标接口

需求:现在我们需要将220V的电压适配为5V

代码演示:

public interface Target {
    Integer output5v();
}
public class Adaptee {
    private final static Integer output220V=220;
    public Integer output220V(){
        System.out.println("这是"+output220V+"V的电压");
        return output220V;
    }
}

适配后

public class Adapter extends Adaptee implements Target {
    @Override
    public Integer output5v() {
        Integer output220V = super.output220V()/44;
        System.out.println("转化后变成"+output220V+"V");
        return output220V;
    }
}
public class Test {
    public static void main(String[] args) {
        Target target=new Adapter();
        target.output5v();
    }
}

源码使用示例:JDK的IO使用了装饰模式

InputStreamReader和OutputStreamWriter分别继承了Read和Writer接口,创建这两个对象时要传入InputStream和OnputStream类型的实例对象,将字节流转换为字符流。InputStreamReader和OutputStreamWriter就充当了Adapter适配器角色,InputStream和OnputStream类型的实例对象就是Adaptee源角色,而Read和Writer接口就是Target目标接口

public OutputStreamWriter(OutputStream out) {
        super(out);
        try {
//适配过程是将byte转码为char的编码
            se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }
//将字节流转化为字符流
public static void main(String[] args) throws FileNotFoundException {
        OutputStream outputStream=new FileOutputStream("c:\\xx\\xxx");
        OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
}
image.png

三、代理模式

为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为被代理类预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,而完成这些流程不会对被代理类有代码侵入,代理类起到了中介的作用。
其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类,代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

静态代理

public interface IHello {
void sayHello(String str);
}
public class Hello implements IHello{
  @Override
   public void sayHello(String str) {
         System.out.println("hello "+str);
     }
}
public class ProxyHello implements IHello{    
    private IHello hello;    
    public ProxyHello(IHello hello) {
        super();
        this.hello = hello;
    }
//加强的方法
    @Override
    public void sayHello(String str) {
        Logger.start();
        hello.sayHello(str);
        Logger.end();
    }
}
public class Logger {
     public static void start(){
         System.out.println(new Date()+ " say hello start...");
     }
     public static void end(){
         System.out.println(new Date()+ " say hello end");
     }
 }
 public class Test {
     public static void main(String[] args) {
         IHello hello = new ProxyHello(new Hello());
         hello.sayHello("world");    
     }
 }

JDK动态代理

实例1:简单实现

代理工厂

public class ProxyFatory {
    //obj是被代理的对象
    public static Object getProxyInstance(Object obj){
        //对人
        MyProxy myProxy=new MyProxy();
        myProxy.bind(obj);
        //返回的是代理人myProxy的invoke方法所返回的对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),myProxy);
    }
}

代理人必须是InvocationHandler 方法的子类

public class MyProxy implements InvocationHandler {
    private Object obj;

    public void bind(Object obj){
        this.obj=obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        proxy=obj;
        return method.invoke(proxy,args);
    }
}

被代理的对象

public class IPhone implements Apple {
    private int id;
    private String name;
    public IPhone() {
    }
    @Override
    public String getIdAndName(int id, String name) {
        return name+id;
    }
}

测试

public class Test {
    @org.junit.Test
    public void test1(){
        Apple proxyInstance = (Apple) ProxyFatory.getProxyInstance(new IPhone());
        String iPhone11 = proxyInstance.getIdAndName(1, "IPhone1");
        System.out.println(iPhone11);
    }
}

实例2:方法加强

需要被加强的接口类

public interface OrderService {
    int insertData(Integer i);
}

需要被加强的接口实现类

public class OrderServiceImpl implements OrderService {
    @Override
    public int insertData(Integer i) {
        System.out.println("执行插入业务");
        return i;
    }
}

代理类实现:

public class OrderProxy implements InvocationHandler {
    private Object target;
    public OrderProxy(Object target) {
        this.target = target;
    }
    //前置增强
    private void before(Object o){
        if (o instanceof Integer) {
            System.out.println("OrderService的前置增强....");
        }
    }
    //后置增强
    private void after(Object o){
        if (o instanceof Integer) {
            System.out.println("OrderService后置增强......");
        }
    }
    public Object bind(){
        Class aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(),aClass.getInterfaces(),this);
    }
//proxy代理类(这个很少用的),method被代理对象需要增强的方法,被代理对象执行方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //增强方法的参数,0代表第一个参数
        Object arg = args[0];
        before(arg);
        //需要增强的方法
        Object invoke = method.invoke(target, args);
        after(arg);
        return invoke;
    }
}

测试

 @org.junit.Test
    public void test12() {
        OrderService orderService = (OrderService) new OrderProxy(new OrderServiceImpl()).bind();
        orderService.insertData(1);
    }

CGLIB

jdk的动态代理需要实现接口,CGLIB不需要实现接口
引入依赖

  <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

被代理对象

public class CustomerServiceImpl {
    public int insertData(Integer i) {
        System.out.println("执行插入业务");
        return i;
    }
}

代理类

public class CustomerProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Customer的前置增强....");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("Customer后置增强......");
        return o1;
    }
}

测试

@org.junit.Test
    public void test112() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CustomerServiceImpl.class);
        enhancer.setCallback(new CustomerProxy());
        CustomerServiceImpl customerService = (CustomerServiceImpl) enhancer.create();
        customerService.insertData(1);
    }

面试题:

Cglib和jdk动态代理的区别?
  • 1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
  • 2、Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
什么时候用cglib什么时候用jdk动态代理?
  • 1、目标对象生成了接口 默认用JDK动态代理
  • 2、如果目标对象使用了接口,可以强制使用cglib
  • 3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
JDK动态代理和cglib字节码生成的区别?
  • 1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • 2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的
Cglib比JDK快?
  • 1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
  • 2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
  • 3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
  • 4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
Spring如何选择是用JDK还是cglib?(Mybatis使用的是jdk的动态代理)
  • 1、当bean实现接口时,会用JDK代理模式
  • 2、当bean没有实现接口,用cglib实现
  • 3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)
SpringAOP日志管理实战

https://www.cnblogs.com/jianjianyang/p/4910851.html

装饰器和代理模式的区别

对装饰器模式来说,装饰者和被装饰者都实现一个接口。对代理模式来说,代理类和真实处理的类都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。

在装饰模式调用者只想要你把他给你的对象装饰一下。而代理模式使用的是代理对象在自己的构造方法里面new的一个被代理的对象,不是调用者传入的。调用者不知道你找了其他人,他也不关心这些事,只要你把事情做对了即可。

  • 1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

  • 2、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

  • 3、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容