关于java:面试官老爱问适配器模式与外观模式看看4年开发是怎么回答的吧

41次阅读

共计 6855 个字符,预计需要花费 18 分钟才能阅读完成。

前言

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

适配器模式是将一个类的接口转换成客户心愿的另外一个接口,身边很多货色都是实用于适配器模式的,笔记本的电源(也叫电源适配器),是将 220V 的交流电转换为笔记本电脑所须要的 12V(电流先疏忽),笔记本电脑的各种接口,VGA 转 Hdml,USB-TypeA 转 USB-TypeC, 亦或者你在香港买了个手机,充电器是你生存中没见过的三孔插座通过一个转换头转换为国内罕用的插头,很多例子都能很形象的解释这个设计模式。适配器模式(有时候也称包装款式或者包装)将一个类的接口适配成用户所期待的。一个适配容许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类本人的接口包裹在一个已存在的类中。

定义适配器模式

适配器模式 将一个类的接口,转换成客户冀望的另一个接口。适配器让本来接口不兼容的类能够合作无间。

对象和类适配器的类图

实际上有两种适配模式,"对象" 适配器 "类" 适配器 ,在 Java 中类适配器不能实现,因为须要 多重继承 的反对。

  • “ 对象 ” 适配器

  • “ 类 ” 适配器

"对象" 适配器 是通过应用 对象的组合 实现的接口转换,"类" 适配器 则是同时 继承 被适配者和指标类实现的。

实现对象适配器

先来个简略的实现,当初我有一个鸭子类,它的子类都能够呱呱叫,我想让一个咯咯叫的火鸡也领有鸭子的行为。咱们能够应用 对象适配器 假装火鸡让它看起来像鸭子。

鸭子行为类:Duck 接口

public interface Duck {void quack(); // 呱呱叫
    void fly();}

绿头鸭是鸭子的子类:MallardDuck

// 绿头鸭
public class MallardDuck implements Duck {public void quack() { // 呱呱叫
        System.out.println("Quack...");
    }
    public void fly() {System.out.println("I'm flying 5 meters");
    }
}

火鸡行为类:Turkey 接口

public interface Turkey {void gobble(); // 咯咯叫
    void fly();}

野火鸡是火鸡的子类:WildTurkey

public class WildTurkey implements Turkey {public void gobble() { // 咯咯叫
        System.out.println("Gobble...");
    }
    public void fly() {System.out.println("I'm flying a 1 meter");
    }
}

接下来咱们须要让火鸡伪装成鸭子,写一个适配器,让它适配火鸡

火鸡适配器类:TurkeyAdapter

public class TurkeyAdapter implements Duck {
    Turkey turkey; // 组合
    // 获得要适配的对象援用
    public TurkeyAdapter(Turkey turkey) {this.turkey = turkey;}
    // 接口装换
    public void quack() { // 简略的转换
        turkey.gobble();}
    public void fly() { // 略微难一点的转换
        for(int i = 0; i < 5; i++)
            turkey.fly();}
}

测试适配器:DuckTestDriver

public class DuckTestDriver {public static void main(String[] args) {
        // 绿头鸭子
        Duck duck = new MallardDuck(); 
        // 野火鸡
        Turkey turkey = new WildTurkey();
        // 野火鸡应用适配器伪装成鸭子
        TurkeyAdapter turkeyAdapter = 
            new TurkeyAdapter(turkey);

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

        System.out.println("\nThe Duck says...");
        TestDuck(duck);

        System.out.println("\nThe TurkeyAdapter says...");
        TestDuck(turkeyAdapter);
    }
    static void TestDuck(Duck duck) {duck.quack();
        duck.fly();}
}

测试后果

The Turkey says...
Gobble...
I'm flying a 1 meter

The Duck says...
Quack...
I'm flying 5 meters

The TurkeyAdapter says...
Gobble...
I'm flying a 1 meter
I'm flying a 1 meter
I'm flying a 1 meter
I'm flying a 1 meter
I'm flying a 1 meter

可见,火鸡在适配器的帮忙下胜利的被认为是鸭子,然而它的实质不变它还是火鸡,只能咯咯叫。

真实世界的适配器

旧世界的迭代器  和  新世界的迭代器

  • 在 Java 晚期的汇合(Collection)类型(例如:VectorStackHashTable这些类当初都被遗弃了)都实现了一个名为 elements() 办法。它会返回一个 Enumeration 举,跟咱们当初的 Iterator 迭代器作用差不多,就是 遍历汇合 ,然而枚举是 ” 只读 “ 的,意味着它不能像迭代器那样 删除元素

面对以前的 遗留代码 ,这些代码只暴露出 枚举器的接口 ,然而咱们心愿用应用 迭代器 ,那么咱们须要结构一个 适配器 让枚举器看起来像迭代器,进而应用它。

依据上例所理解的对象适配器咱们先设计出 类图,像这样:

编写一个 EnumerationIterator 适配器:

// 应用了 Iterator 假装的一个 Enumeration
public class EnumerationIterator implements Iterator<Object> {
    Enumeration<?> enumeration;
    public EnumerationIterator(Enumeration<?> enumeration) {this.enumeration = enumeration;}
    public boolean hasNext() {return enumeration.hasMoreElements();
    }
    public Object next() {return enumeration.nextElement();
    }
    public void remove() { // 未获反对的操作
        throw new UnsupportedOperationException();}
}

能够看见适配器适配得并不 完满 ,但这没有方法,毕竟Enumeration 不是一个 Iterator,只能抉择在remove() 办法间接抛出了一个 未获反对操作异样 UnsupportedOperationException,做出一个文档阐明,让客户在应用的时候小心就没有太大问题。

测试一下:EnumerationIteratorTestDriver

public class EnumerationIteratorTestDriver {public static void main(String[] args) {
        Vector<Integer> v = new Vector<Integer>(Arrays.asList(1, 2, 3, 4, 5));
        // 应用了 Iterator 假装的一个 Enumeration
        Iterator<?> iterator = new EnumerationIterator(v.elements());
        while(iterator.hasNext())
            System.out.print(iterator.next());
    }
}

测试后果:

12345

与外观和装璜器混同

简而言之:
适配器模式职责是 转换接口
外观模式职责是 简化接口
装璜器模式职责是 扩大对象的行为与责任

适配器的扩大利用

双向适配器,反对两边的接口,想要创立这样的适配器,必须实现波及的两个接口。

1.3 的例子中,如果来了一个嘎嘎叫的天鹅,我想让火鸡也伪装成它,能够实现一个 双向的适配器 去适配火鸡,让火鸡能够被看作是鸭子和鹅,像这样:

public interface Goose {gaggle(); // 嘎嘎叫
    gFly();}
public class TwoWayAdapter implements Duck, Goose {
    Turkey turkey; // 组合
    Random rand;
    // 获得要适配的对象援用
    public TwoWayAdapter(Turkey turkey) {
        this.turkey = turkey;
        rand = new Random();}
    // 接口装换
    public void quack() {turkey.gobble();
    }
    public void gaggle() {turkey.gobble();
    }
    public void fly() { // 模仿鸭子的航行间隔
        for(int i = 0; i < 5; i++)
            turkey.fly();}
    public void gFly() { // 鹅的航行间隔比火鸡短    
        for(rand.nextInt(5) == 0) // 五分之一
            turkey.fly();}
}

定义外观模式

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

外观模式类图

Cilent: 有了外观,客户只须要跟外观打交道,工作变得简略了,客户与子系统 解耦 了。
Facade:外观对立了 接口
ComplacatedSubsystem:简单的子系统

当初还是有点迷糊,通过接下来的例子更深一步意识外观模式

实现外观类

我组装了一个家庭游戏空间的零碎,内含 Xbox 游戏机,超大屏电视,盘绕立体声,空调,灯光。

看看这些组件的类图:

当我须要启动我的游戏空间零碎时,我会这么做,先开灯,而后关上电视,而后。。。

light.on(); // 开灯
light.dim(10); // 亮度升高 10%
airConditioner.on();// 开空调,默认 20 度
tv.on(); // 开电视    
tv.setVolume(11); // 设成最大音量 11
StereoSpeaker speaker = tv.getStereoSpeaker();
speaker.setSurround(); // 设为盘绕音
xbox.on(); // 关上 Xbox 游戏主机
xbox.setGame(game); // 抉择游戏

用完了我还得须要反向地执行敞开动作,尽管该零碎功能性很强然而应用十分麻烦,难道不能一键开启敞开吗?能!

咱们能够实现一个提供更正当的接口的 外观类 ,将这个简单的零碎变得 容易应用

上面是实现了 外观类 的家庭游戏空间的零碎:

外观类 FamilyPlayPlaceFacade 只暴露出几个简略的办法,它将家庭游戏空间多个组件视为一个 子系统 Subsystem,通过调用这个子系统来实现 playGames() 办法,而且并未将子系统的类阻隔起来,我还是随时能够应用原来的子系统。

结构一个家庭游戏空间的外观类:FamilyPlayPlaceFacade

// 应用外观封装特定的一组行为, 但不阻隔子系统
public class FamilyPlayPlaceFacade {
    // 应用对象组合
    Light light;
    AirConditioner airConditioner;
    TV tv;
    Xbox xbox;
    // 在结构器初始化对象
    public FamilyPlayPlaceFacade(
        Light light,
        AirConditioner airConditioner,
        TV tv, 
        Xbox xbox) {
        this.light = light;
        this.airConditioner = airConditioner;
        this.tv = tv;
        this.xbox = xbox;
    }
    // 设定特定一系列的操作
    public void playGames(String game) {System.out.println("Get ready to play a game...");
        light.on();
        light.dim(10); // 亮度升高 10%
        airConditioner.on(); // 默认 20 度
        tv.on(); // 先开电视    
        tv.setVolume(11); // 设成最大音量 11
        StereoSpeaker speaker = tv.getStereoSpeaker();
        speaker.setSurround(); // 设为盘绕音
        xbox.on();
        xbox.setGame(game); // 抉择游戏
        System.out.println();}
    // 负责敞开所有
    public void endGames() {System.out.println("Shutting game down...");
        xbox.off();
        tv.off();
        airConditioner.off();
        light.off();}
}

当然每个工作的细节都 委托 相应的组件解决,毕竟我只是个外观而已。

当初我终于能够 一步到位 了,来肝一把《怪物猎人:世界》,测试一下:

public class FamilyPlayPlaceDrive {public static void main(String[] args) {
        String location = "Living Room";
        // 实例化组件
        Light light = new Light(location);
        AirConditioner airConditioner = new AirConditioner(location);
        StereoSpeaker stereoSpeaker = new StereoSpeaker(location);
        TV tv = new TV(location, stereoSpeaker);
        Xbox xbox = new Xbox(location);
        // 实例化外观
        FamilyPlayPlaceFacade gameFacade = 
            new FamilyPlayPlaceFacade(light, airConditioner, tv, xbox);
        gameFacade.playGames("Monster Hunter:World");
        gameFacade.endGames();}
}

准则定义

莫忒耳法令(Law of Demeter)指的也是 起码常识准则

起码常识准则:只和你的密友谈话

不要让太多的类耦合到一起,省得批改零碎的一部分,会影响其余局部。如果许多类之间相互依赖,那么这个零碎就会变成一个易碎的零碎,它须要花许多老本保护,也会因为太简单而容易被别人理解。

指导方针

在该对象的办法内,咱们只调用属于以下范畴的办法:

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

当恪守该准则对你的程序百利而无一害时,那就尽量去恪守它。

上面是四条指导方针的 利用

public class Car {
    Engine engine; // 本类组件 engine
    // 其余组件
    public Car() {// 初始化发动机}
    // 被当做参数传进来的对象 key
    public void start(Key key) {
        // 办法内创立的对象 doors
        Doors doors = new Doors();
        // 被当做参数传进来的对象 key 的办法
        boolean authorized = key.turns();
        if(authorized) {
            // 本类组件 engine 的办法
            engine.start();
            // 该对象自身 Car 的办法
            updateDashboardDisplay();
            // 办法内创立的对象 door 的办法
            doors.lock();}
    }
    public void updateDashboardDisplay() {// 显示更新}
}

毛病

尽管 缩小 了对象之间的 依赖 缩小 了软件的 保护老本

然而也会导致 过多 的 ”包装 “ 类被制作进去,以解决和其余组件的沟通,这可能会导致 复杂度 开发工夫的减少 ,并 升高运行时的性能

要点

  • 当须要应用一个现有的类而其余接口不合乎你的需要时,就应用适配器
  • 当须要简化接口并对立一个很大的接口或者一群简单的接口时,应用外观
  • 适配器扭转接口以合乎客户的冀望
  • 外观将客户从一个简单的子系统中解耦
  • 实现一个适配器的工作量依据指标接口的大小与复杂度规定
  • 实现一个外观,须要将子系统组合进外观,而后将工作委托给子系统去执行
  • 适配器模式的两种模式:对象适配器 和 类适配器。类适配器须要用到多重继承
  • 能够为一个子系统实现一个以上的外观
  • 适配器将一个对象包装起来以扭转接口;装璜者将一个对象包装起来以减少新的行为和责任;而外观将一群对象 ” 包装 ” 起来以简化接口

最初

感激你看到这里,看完有什么的不懂的能够在评论区问我,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!

正文完
 0