适配器模式和装饰模式

472 阅读5分钟

前言

适配器模式和装饰模式都是通过组合类或者对象产生更大结构以适应更高层次的逻辑需求。其中装饰模式是代理模式的一个特殊应用,侧重于对类的功能进行加强和减弱。适配器模式则是侧重于将源角色转换的过程。

目录

一、适配器模式

1、定义

结构型设计模式关注重点在于多个对象间的组合,而创建型设计模式关注重点在于一个对象内部结构。

适配器模式关键点在于转换,将源角色转换成目标角色。

具体定义如下:

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

2、模式原理分析

有三个关键角色

  • 目标类,适配器类即将要适配的抽象类或者接口,就是需要把其他类转换成什么接口

  • 适配器类,你想把谁转换成目标角色,经过适配器包装,就是一个全新的角色

  • 具体适配器类,可以实内部的类,也可以是外部的类

具体看代码,会很容易理解

//源角色
public class Adaptee{
    public void adapteeEat(){
        System.out.println("饭太贵,吃不起");
    }
}
//目标角色
public interface Target{
    public void targetBuyThings();
}
//目标角色的实现类
public class ConcreteTarget implements Target{
    @Override
    public void targetBugThings(){
        System.out.println("东西太贵,买不起");
    }
}
//适配器角色
public class Adapter extends Adaptee implements Target{
    @Override
    public void targetBugThings(){
    	super.adapteeEat();
        System.out.println("东西太贵,买不起");
    }
}

//场景类
public class Client{
    public static void main(String[] args){
        Target target = new Adapter();
        target.targetBuyThings();
    }
}

代码很简单,利用了接口,达成了多继承目的,实现了不同类之间的关联,可以理解为中间者将两个类封装到了一起。

那如果我们的Adaptee 信息来自不同的类呢?如何处理,如下在构造函数中处理或者 getset方法处理

public class Adapter implements Target{
    private Adaptee adaptee1;
    private Adaptee adaptee2;
    public Adapter(Adaptee adaptee1,Adaptee adaptee2){
        this.adaptee1 = adaptee1;
        this.adaptee2 = adaptee2;
    }
    @Override
    public void targetBugThings(){
    	super.adapteeEat();
        System.out.println("东西太贵,买不起");
    }
}

这里可能大家还听过对象适配器和类适配器,这两种类型适配器,有什么不同?

  • 类适配器是类间继承

  • 对象适配器是对象的合成关系

3、使用场景

场景比较多,但是只需要记住,你需要修改一个已经投产中的接口时,适配器模式可能是最适合的模式,例如系统扩展了,需要使用一个已经有的或者新建立的类,这是可以考虑适配器模式。或者是不同数据格式、协议需要转换的时候,比如,API 网关中经常需要对 iOS、安卓、H5 等不同的客户端进行数据和通信协议上的适配转换,这时网关就是一个是适配器。

适配器模式一般是用于解决服役项目问题,最好不要在详细阶段考虑它,毕竟主要场景还是在扩展应用方面。

4、优点

  • 让两个没有任务关系的类在一起运行

  • 增加了类的透明型,具体的适配者类中新增功能只影响适配者类,而对于使用目标类的客户端类来说是透明的(使用目标类接口)

  • 提高了类的复用度

  • 符合开闭原则

  • 满足里氏替换原则

5、缺点

  • 一次只能适配一个抽象类或者接口

  • 过度嵌套会导致接口臃肿

  • 目标接口依赖太多适配接口,修改目标接口会导致所有适配接口都得改,太多Adapter 依赖 Target, Target 修改了方法,所有Adapter 都需要改。对应之前提到的 Adaptee很多的情况

二、装饰模式

1、定义

动态地给一个对象添加一些额外地职责

2、模式分析

//抽象构件
public abstract class Component{
	//抽象方法
    public abstract void operate();
}
//具体构件
public class ConcreteComponent extends Component{
    @Override
    public void operate(){
        System.out.println("operate car");
    }
}

//抽象装饰者
public abstract class Decorator extends Component{
    private Component component = null;
    public Decorator(Component component){
        this.component = component;
    }
    //委托给被修饰者执行
    @Override
    public void operate(){
        this.component.operate();
    }
}
//具体装饰类
public class ConcreteDecorator1 extends Decorator{
    //定义被修饰者
    puiblic ConcreteDecorator1(Component component){
        super(component);
    }
    //定义自己的修饰方法
    private void method1(){
        System.out.println("method1修饰");
    }
    //重写父类
    public void operate(){
        this.method1();
        super.operate();
    }
}

最简单的理解就是,用一个类持有另一个类,新类里方法调用可以操作另一个类的方法,这里的方法要留意一下,都是operate方法,这种模式其实也可以说是继承关系的一个替代方案

原始方法和装饰方法的执行顺序在具体的装饰类是固定的,可以通过方法重载实现多种执行顺序

3、使用场景

  • 需要动态扩展一个类的功能,或者给一个类增加附加功能

  • 需要为一批兄弟类进行改装

  • 通过顺序组合包装的方式来附加扩张功能的场景

例如数据上报,埋点,新功能需要更加详细,可以考虑下装饰者模式

4、优点

  • 装饰类和被装饰类可以独立发展,不会相互耦合(看上面的具体代码,这两个类都实现了基础类接口)

  • 动态扩展功能

  • 可以在统一行为上组合几种行为

  • 满足单一职责原则

5、缺点

  • 在调用链中删除某个装饰器时需要修改代码,为什么?看下面代码

    Component component = new ConcreteComponent();
    //第一次修饰
    component = new ConcreteDecorator1(component);
    //第二次修饰
    component = new ConcreteDecorator2(component);
    //修饰后运行
    component.operate();
    
  • 如果修饰类嵌套过多,会增加代码的理解难度,后续维护和排查问题的难度

三、区别

适配器模式和装饰者模式都属于结构型模式,功能都类似,都是包装作用。

装饰模式强调的是 动态扩展功能,所有装饰类都有一个共同的父类,而适配器模式侧重于转换,通过继承和实现方式,将源角色转换成目标角色,