【结构型模式四】适配器模式

1. 前情提要

在状态模式中,我们实现了让计算器类根据自身状态的变化,动态地调整价格。且客户端无需关注状态的变化,和对应的价格设置行为的变化,直接调用设置价格的接口即可。客户端代码为:

    public static void setPriceByState(Calculator calculator) {
        int basePrice = 10;
        calculator.setPriceByState(basePrice);
    }

计算器类的代码为:

public class Calculator implements Cloneable {
    private Date productionDate;
    private int price;
    private State state;

    public Calculator() {
        state = new NewProductState();
    }

    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }

    public void setPriceByState(int basePrice) {
        state.setPrice(this, basePrice);
    }

    public Date getProductionDate() {
        return productionDate;
    }

    public void setProductionDate(Date productionDate) {
        this.productionDate = productionDate;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

}

计算器的具体状态类都继承了抽象类State,所以在设置计算器类的state时可以直接使用setState()方法,传入继承了State类的具体状态类来设置计算器的状态。
如果有一个现成的具体状态类SlightlyOldState,它实现的功能与NewProductState、OldProductState、DeadProductState类似,只是未继承抽象类State,函数的方法名、参数等也与State中定义的抽象方法不同。
SlightlyOldState类代码:

public class SlightlyOldState {
    public void changePrice(Calculator calculator, int basePrice) {
        Date productionDate = calculator.getProductionDate();
        if (isSlightlyOld(productionDate)) {
            calculator.setPrice(basePrice/2);
        } else {
            changeState(calculator,basePrice);
        }
    }

    private boolean isSlightlyOld(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.YEAR,2);
        Date dateCopy = calendar.getTime();
        return dateCopy.compareTo(new Date()) >= 0;
    }

    protected void changeState(Calculator calculator,int basePrice) {
        calculator.setState(new OldProductState());
        calculator.setPriceByState(basePrice);
    }
}

但我们又不能更改这个类的代码,又想像使用NewProductState等具体类一样使用它,就可以使用适配器模式。

即当要使用两个或多个已经存在的功能类似的类,但它们的接口不统一,你想直接使用统一的接口调用它们时,就可以使用适配器模式。

2. 适配器模式

适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的类可以一起工作。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

适配器模式的角色

使用适配器模式实现根据状态改变计算器价格的类图:
适配器模式类图

Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。例如State类。

Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。例如SlightlyOldStateAdapter类。

Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。例如SlightlyOldState类。

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

3. 适配器类

对象适配器的适配类实现了目标接口或继承目标类(State抽象类),里面封装了一个被适配类的对象(SlightlyOldState),然后将被适配类的方法(changePrice)都包装成目标接口的方法(setPrice)。

public class SlightlyOldStateAdapter extends State {
    private SlightlyOldState slightlyOldState = new SlightlyOldState();
    @Override
    public void setPrice(Calculator calculator, int basePrice) {
        slightlyOldState.changePrice(calculator,basePrice);
    }

    @Override
    protected void changeState(Calculator calculator,int basePrice) {
        slightlyOldState.changeState(calculator,basePrice);
    }
}

这样就可以像使用NewProductState、OldProductState、DeadProductState类一样使用SlightlyOldStateAdapter类了。

4. 在NewProductState中更改下一状态

public class NewProductState extends State {
    @Override
    public void setPrice(Calculator calculator,int basePrice) {
        Date productionDate = calculator.getProductionDate();
        if (isNew(productionDate)) {
            calculator.setPrice(basePrice);
        } else {
            changeState(calculator,basePrice);
        }
    }

    private boolean isNew(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.YEAR,1);
        Date dateCopy = calendar.getTime();
        return dateCopy.compareTo(new Date()) >= 0;
    }
    @Override
    protected void changeState(Calculator calculator,int basePrice) {
        calculator.setState(new SlightlyOldStateAdapter());
        calculator.setPriceByState(basePrice);
    }
}

这里可以发现状态模式的代码还不够好,每个具体状态类之间有耦合。由于在具体状态类中引用了下一状态的对象,在状态的改变流程中有状态要增加或删除时,都会影响引用它的那个状态类。要想让各个状态类各司其职,可以将某个状态类的下一状态的设置交给客户端,让每个状态类无需知道其他状态类的存在,可使用职责链模式实现。

但状态的增加不影响客户端代码,客户端代码不变。

5. 适配器模式的适用场景

适配器模式应是能不用则不用。如果能事先预防接口不统一,就不会有不匹配问题发生。当有接口不匹配的小问题发生时,应及时重构。只有在碰到原代码无法修改时,才考虑使用适配器模式。

适配器模式的应用有:

  • spring AOP中使用 Advice(通知) 来增强被代理类的功能。

Advice的类型有:MethodBeforeAdviceAfterReturningAdviceThrowsAdvice

在每个类型 Advice 都有对应的拦截器:MethodBeforeAdviceInterceptorAfterReturningAdviceInterceptorThrowsAdviceInterceptor

Spring需要将每个 Advice 都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换

  • spring JPA(Java持久层接口)中的适配器模式
    在Spring的ORM包中,对于JPA的支持也是采用了适配器模式,首先定义了一个接口的 JpaVendorAdapter,然后不同的持久层框架都实现此接口。
  • spring MVC中的适配器模式
    Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。

在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值