Head First 设计模式(七)适配器模式和外观模式

适配器模式

定义

适配器模式将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间

场景+代码

场景

定义一般都是让人看不懂的,我们结合实例来理解。

先用现实生活中的例子:

如图,美国人到欧洲旅游时都会遇到一个问题,美国的插头和欧洲的插座并不兼容。怎么办呢?通过一个交流电适配器就可以了。

再看软件开发中的例子:

假设现在有一个软件系统,想要使用新的厂商提供的类库。

再具体点,假设现有系统是一个交易所系统,它从前都是调用的Trade接口的inMoney()方法来跟银行通信进账。而现在新厂商提供了一个新接口NewTradetransferMoneyIn()方法来进账。怎么办?

交易所系统里面的所有进账功能都是调用的Trade接口的方法,而新厂商的新接口NewTrade并没有继承Trade,所以不能直接使用。在不修改双方代码的前提下,我们就可以使用适配器模式来完成这个需求:

代码

下面,我们用代码来详细说明上面的场景:

系统旧的交易类支持广发银行、招商银行等:

/**
 * 旧的交易类,支持广发银行、招商银行等等
 */
public interface Trade {
    /**
     *  入账方法
     */
    public void inMoney(double money);
}

/**
 * 与广发银行进行出入金的交易类
 */
public class TradeFromCGB implements Trade{

    @Override
    public void inMoney(double money) {
        System.out.println("从广发银行入账"+money+"元");
    }

}

现在我们和新的厂商以及银行合作,新厂商扩展提供了支持浦发银行、工商银行等的新的交易类:

/**
 * 新厂商提供的交易类
 */
public interface NewTrade{
    /**
     * 入账方法
     */
    public void transferMoneyIn(double money);
}

/**
 * 与工商银行进行出入金的交易类
 */
public class NewTradeFromICBC implements NewTrade{
    @Override
    public void transferMoneyIn(double money) {
        System.out.println("从工商银行入账"+money+"元");
    }

}

现在出现问题了,在原来的系统里面,所有跟交易相关的方法都是调用的旧交易类接口进行的操作。

在不修改系统的前提下,我们无法调用新接口与工商银行、浦发银行进行交易。

/**
 * 交易系统
 */
public class TradeSystem {

    /**
     * 往交易系统的银行账户上入账
     * 形参都是Trade接口,新厂商提供的NewTrade无法适配
     */
    public void addMoneyToSystemAccount(Trade trade,double money) {
        trade.inMoney(money);
    }
}

怎么办,修改系统的代价太大,所有交易相关的方法都要改,而联系厂商改动接口也不现实,资金困难的我们要多付钱。现在只有自力更生了,我们想到了设计一个适配器类来伪装新接口:

/**
 * 新交易适配器类
 */
public class NewTradeAdapter implements Trade{
    NewTrade newTrade;

    public NewTradeAdapter(NewTrade newTrade) {
        this.newTrade = newTrade;
    }
    @Override
    public void inMoney(double money) {
        newTrade.transferMoneyIn(money);
    }

}

通过实现旧接口,以及组合新接口,在旧接口的方法中调用新接口的对应方法,我们伪装成功!

测试:

public class Main {

    public static void main(String[] args) {
        TradeSystem tradeSystem = new TradeSystem();
        Trade trade = new TradeFromCGB();
        tradeSystem.addMoneyToSystemAccount(trade, 1800000.5);

        NewTrade newTrade = new NewTradeFromICBC();
        NewTradeAdapter newTradeAdapter = new NewTradeAdapter(newTrade);
        tradeSystem.addMoneyToSystemAccount(newTradeAdapter, 1800000.5);
    }
}/**output:
从广发银行入账1800000.5元
从工商银行入账1800000.5元
*/

现在,我们可以顺利的调用“新接口”从工商银行里面入账了。

外观模式

定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。

外观模式其实就是对复杂接口的一种简化,当你觉得使用的相关子接口又多又复杂时,可以在它们上面设计出一个外观类,进行统一。

场景+代码

场景

现在我们有一个复杂的家庭影院系统,它设计到了许多组件:爆米花机、投影仪、屏幕、灯光……

每次放映一次电影,需要许多组件的合作:

//打开爆米花机
popper.on();
//开始准备爆米花
popper.pop();
//调暗光线
lights.dim();
//屏幕放下
screen.down();
//打开投影仪
projector.on();
//放入DVD,开始看电影
dvd.on(movie);

除了放映电影,当我们看完要关闭仪器时,又得调用所有的组件反操作一次,实在是太麻烦啦!

我们思考,难道不可以把所有的相关类组合进一个类中,设计出一个更加合理更简洁的外观类吗?

代码

组合了所有相关类后的家庭影院“外观类”:

/**
 * 家庭电影院
 */
public class HomeTheaterFacade {
    Popper popper;
    Lights lights;
    Screen screen;
    Projector projector;
    Dvd dvd;

    public HomeTheaterFacade(Popper popper,Lights lights,Screen screen,Projector projector,Dvd dvd) {
        this.popper = popper;
        this.lights = lights;
        this.screen = screen;
        this.projector = projector;
        this.dvd = dvd;
    }


    /**
     * 观看电影
     */
    public void watchMovie(){
        //打开爆米花机
        popper.on();
        //开始准备爆米花
        popper.pop();
        //调暗光线
        lights.dim();
        //屏幕放下
        screen.down();
        //打开投影仪
        projector.on();
        //放入DVD,开始看电影
        dvd.on(movie);
    }

    /**
     * 停止看电影
     */
    public void endMovie() {
        popper.off();
        lights.off();
        screen.up();
        projector.off();
        dvd.off();
    }
}

通过外观模式,我们简化了复杂的子系统接口,也将客户从组件的子系统中解耦。

另外,虽然外观模式简化了子系统,但依然可以直接使用子系统的类,系统的完整功能依旧暴露在外,供需要的人使用。

外观模式还涉及到了一个新的设计原则:

最小知识原则:减少对象之间的交互,只留下几个“密友”。

这个原则具体点说就是,我们在设计系统时,需要注意不让太多的类耦合在一起。
如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它将需要花许多成本维护,也会因为太复杂而不容易被其他人了解。

这个原则提供了一些方针

就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件

举个常见的反例,这句代码耦合了两个类:

System.out.println();

适配器模式、外观模式以及装饰者模式

这三个模式看起来的比较像,都是通过组合包装实现。

区别在于:

  • 适配器模式是将一个对象包装起来以改变其接口。
  • 装饰者模式是将一个对象包装起来以增加新的行为和责任。
  • 外观模式是将一群对象包装起来以简化其接口。

本文总结自:
《Head First 设计模式》第七章:适配器模式与外观模式

猜你喜欢

转载自blog.csdn.net/z55887/article/details/70161546
今日推荐