设计模式之适配器、外观模式

DesignPattern之Adapter和Facade

迪米特原则(”Least Knowledge”原则)

Adapter 适配器模式(主要目的:转化接口!)

1.定义


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

个人理解:适配器模式,就好比转换插。我的电脑的充电器是英标三脚的插头,无法插进欧标的二脚插座,要解决这个问题,只需要一个欧标转换插,电脑充电器插入这个转换插,然后这个转换插再插进欧标的插座就可以解决。而且不仅是电脑充电器,凡是英标三脚的插头的电器都能使用这个转换插(只要实现了接口,被适配者的任何子类,都可以搭配)。

  • 适配器模式可以通过创建适配器进行接口转换,让原来不兼容的接口变成兼容。这可以让客户从实现的接口解耦。如果在一段时间后想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。

  • 适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者(适配器这个类中持有被适配者的引用)–> 额外优点:被适配者的任何子类都可以搭配适配器使用。

  • 因为客户是和接口绑定起来(面向接口编程),而不是和实现绑定起来,所以可以使用数个适配器,每一个负责转换不同的后台类;或者也可以加上新的实现,==只要实现了目标接口即可!==

2.例子:


使用火鸡搭配适配器来冒充鸭子

鸭子接口:

public interface Duck {

    public void quack();
    public void fly();

}

绿头鸭实现了鸭子这个接口:

public class MallarDuck implements Duck {

    @Override
    public void quack() {
        System.out.println("Quack!");
    }

    @Override
    public void fly() {
        System.out.println("MallarDuck flying!");
    }

}

火鸡接口:


public interface Turkey {

    public void gobble();
    public void fly();

}

火鸡的实现类:

public class WildTurkey implements Turkey {

    @Override
    public void gobble() {
        System.out.println("Gobble gobble!");
    }

    @Override
    public void fly() {
        System.out.println("I am flying a short distance!");
    }

}

现在缺少鸭子对象,想用一些火鸡对象来冒充鸭子

–> 使用火鸡-->鸭子适配器!!!!

// 首先这个适配器类需要实现想转换成的类型的接口,也就是客户所期望看到的接口
// 就好像前面所说的转换插,如果这个转换插不实现期望的接口(不是二脚欧标插口),
// 那么这个转换插并没有什么卵用!
//
public class TurkeyAdapter implements Duck {

    // 需要取得适配的对象(被适配者)的引用
    private Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    //实现了期望的接口就必须实现接口的方法,这里相当于委托给被适配者来实现
    @Override
    public void quack() {
        turkey.gobble();
    }

    @Override
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }

}

客户类:

public class AdapterTest {

    public static void main(String[] args) {
        MallarDuck mDuck = new MallarDuck();

        WildTurkey turkey = new WildTurkey();

        // 将火鸡包装进一个火鸡适配器中,使他看起来像是一只鸭子
        // 注意这里的类型是鸭子接口,就是说在客户看来这是一只鸭子
        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println("\nThe Turkey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nDuck says...");
        testDuck(mDuck);

        System.out.println("\nThe turkeyAdapter says...");

        // 因为turkeyAdapter的类型是Duck,所以完全可以作为参数传进testDuck方法
        // 但Duck的外表下其实是一种火鸡。。。坑爹啊!!!
        testDuck(turkeyAdapter); 
    }

    private static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }

}

3.java中的适配器模式


java早期的集合类型(Vector,Stack, Hashtable等)都有一个名为elements()的方法,该方法的返回值类型为Enumeration, Enumeration里有两个方法hasMoreElements(), nextElement()

Sun推出更新后的集合类是,开始使用Iterator接口,与Enumeration类似,但还提供了remove()方法。

面对遗留代码,这些代码暴露出枚举类接口,但是我们想在新的代码中使用迭代器?
–> 使用一个适配器即可。

按照前面鸭子的例子:

Enumeration接口:

public interface Enumeration {
    public void hasMoreElements();
    public void nextElement();
}

Iterator接口:

public interface Iterator {
    public boolean hasNext();
    public Object next();
    public void remove();
}

重头戏:
适配器!

public class EnumerationAdapter implements Iterator {
    private Enumeration enum;

    public EumerationAdapter(Enumeration enum) {
        this.enum = enum;
    }

    @Override
    public boolean hasNext() {
        return this.enum.hasMoreElements();
    }

    @Override
    public void next() {
        this.enum.nextElement();
    }

    // 我们知道枚举不支持删除,因为枚举是一个只读的接口。
    // 适配器无法实现一个有实际功能的remove方法
    // 幸运的时, 迭代器接口的设计者事先料到了会有这样的需要
    // 所以将remove()方法定义成会抛出 UnsupportedOperationException

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

2. Facade 外观模式(主要目的:简化接口!)

定义

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

例子

假设家里有一套家庭影院

观赏电影(复杂的方式)

  • 打开爆米花机
  • 开始爆米花
  • 将灯光调暗
  • 放下屏幕
  • 打开投影机
  • 将投影机的输入切换到DVD
  • 投影机设置成宽屏模式
  • 打开功放
  • 将功放的输入设置为DVD
  • 将功放设置为环绕立体声
  • 打开DVD播放器
  • 开始播放DVD

映射成代码:

popper.on();
popper.pop();

lights.dim(10);

screen.down();

projector.on();
projector.setInput(dvd);
projector.wideScreenMode();

amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);

dvd.on();
dvd.play();

妈蛋,如果软甲设计成这么麻烦,哪里会有客户会用这个软件!!!

所以要使用==外观模式==,通过实现一个体更合理的接口的外观类,将一个复杂的子系统变得易用。

在这里,可以把上面的代码封装进一个watchMovie()方法中,客户只需要调用这个方法,就可以完成一系列的准备工作(好比按下遥控器上的一个按钮,然后系统就自动准备好。)

public class HomeTheaterFacade {
    AMplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    cdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacad(AMplifier amp,
    Tuner tuner,
    DvdPlayer dvd,
    cdPlayer cd,
    Projector projector,
    TheaterLights lights,
    Screen screen,
    PopcornPopper popper) {
        this.amp = amp;
        this.tuner = tuner;
        this.dvd = dvd;
        this.cd = cd;
        this.projector = projector;
        this.screen = screen;
        this.lights = lights;
        this.popper = popper;
    }

    //外观模式!!!
    public void watchMovie(String movie) {
        popper.on();
        popper.pop();

        lights.dim(10);

        screen.down();

        projector.on();
        projector.setInput(dvd);
        projector.wideScreenMode();

        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);

        dvd.on();
        dvd.play();
    }
}

客户类:


public class Client {
    public static void main(String[] args) {
        HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, 
        tuner, dvd, cd, projector, screen, lights, popper);

        // 客户只需要调用watchMovie方法便可以看电影了,
        // 比没有使用外观模式之前方便了无数倍!!!!
        homeTheater.watchMovie("Matrix");
    }
}
注意:外观模式并没有“封装“子系统的类,只是提供了简化的接口。所以如果客户觉得有必要,仍然可以直接使用子系统的类。这也是外观模式的一个优点:==提供简化的接口的同时,依然将系统外争的功能暴露出来,以供需要的人使用。==

3.迪米特原则(”Least Knowledge”原则)

这个原则告诉我们要减少对象之间的交互,只留下几个”密友“,让朋友圈维持在最小状态!!!!

这是说:当你在设计一个系统的时候,不管是任何对象,你都要注意它所交互的类有哪些,并注意这些类是如何交互的。

==–>不要让太多的类耦合在一起(低耦合!!!), 免得修改系统中一部分,会影响到其他部分==

如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,而且极其复杂(想象一下一堆毛线缠绕在一起。。。)

如何做到这个原则?

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

  • 该对象本身的

  • 被当做方法的参数而传递进来的对象

  • 此方法所创建或实例化的对象

    前面三条告诉我们:如果某对象调用其他方法的方法,不要调用该对象的方法!!!!

  • 对象的任何组件 (该类的属性的对象(该类持有对方引用))

调用从另一个调用中返回的对象的方法,会有什么坏处?

如果这样做,相当于想另一个对象的子部分发送请求,从而增加了直接认识的对象数目 –> 该类依赖的类的数目增加,系统的稳定性下降!



例子1

不采用这个原则:

public float getTemp() {
    Thermometer thermometer = station.getThermometer();
    return thermometer.getTemperature();
}

这里的termometer对象时由station的方法返回而来,这样做的结果是:当前的类与Thermometer这个类产生了关联,相当于当前类要认识Thermometer和Station两个类。。。

==采用这个原则==

在Station中添加一个方法,用来向Thermometer取得温度,这样就可以从Station这个类返回温度, 当前类无需与Thermometer产生关联(降低耦合!)

public float getTemp() {
    return station.getTemperature();
}


外观模式和最少知识原则

回想一下前面的家庭影院的设计,如果不采用外观模式,那么客户这个类必须要与Tuner,Screen,CdPlayer等各种类打交道,类与类之间的联系十分复杂。但是采用外观模式之后,客户只需要与HomeTheaterFacade这个类打交道,调用watchMovie方法便可。类与类之间的关联、复杂度大大降低了!

试着让子系统也能遵守这个原则:如果子系统太复杂,有太多的朋友牵涉其中,那么我们可以增加更多的外观,将此子系统分成好几个层次。

装饰者、适配器、外观三种模式对比

模式目的
装饰者不改变借口,但加入新功能、新责任
适配器将一个接口转换成用户需要的接口
外观使接口更简单易用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值