zoukankan      html  css  js  c++  java
  • headfirst设计模式(8)—适配器模式与外观模式

    前言

    这一章主要讲2个模式,一个是,适配器模式(负责将一个类的接口适配成用户所期待的),另外一个是外观模式(为子系统提供一个共同的对外接口),看完的第一反应是,为什么要把它们两放在同一章,难道它们有什么不可告人的秘密?

    难道是因为他们俩都很简单吗?不会不会,毕竟是大名鼎鼎的headfirst,怎么可能这么草率,这我是万万不相信的!

    细想了一下,我和工作的点点滴滴,我发现,一般到项目的后期,好像都比较容易用上这两个东西...

    当然,项目的后期并不是说一个项目自己从头发开到尾的项目,而是在它生命周期的后半段,比如适配器,用来适配老的接口,比如外观模式,用来隐藏各个子系统,各个模块的协作细节。

     

    不过外观模式却不一定都是在后期才发力的:

    1,前期如果系统比较复杂,在系统规划的时候,就会有意识的对系统分层,为上层模块提供一些高级的api。

    2,在系统的中期呢,开发过程中,发现子系统越来越复杂,也可以提供类似的操作。

    3,在系统后期,模块越来越多,功能越来越复杂,还有历史原因,外观模式就更加有用了,毕竟,有一个简单易用的API,比手动调用各个系统的逻辑那简直是不要太舒服!

     

    为什么后期不重构?而是要做这些修修补补的工作呢?

    举个例子,房子上有棵树,你觉得这棵树很碍事,就把树给干掉了,因为你以为,是房子上面长的,结果呢?特么是树把房子吊着的!类似的坑实在是太多了,所以,重构需谨慎,且构且珍惜。

    当然不是说重构不好,而是要综合考量各方面的因素,而且,重构也用得上这些啊,毕竟,重构不是重写...(诶,重写好像也要用)

     

    适配器模式

    先说说它是干嘛的,用通俗一点的话来讲就是,VGA转DVI,2线插头转3线插头...废话不多说,上个图就知道了

    什么?大家很想看个例子?那么我就来一个例子吧,就举一个小火鸡变成小鸭子的故事吧

    先看看鸭子接口(对应Target)

    /**
     * 鸭子接口
     */
    public interface Duck {
        /**
         * 鸭叫
         */
        void quack();
    
        /**
         * 飞行
         */
        void fly();
    }

    然后看一下火鸡的接口和实现类(对应Adaptee)

    /**
     * 火鸡接口
     */
    public interface Turkey {
        /**
         * 火鸡叫
         */
        void gobble();
    
        /**
         * 飞行
         */
        void fly();
    }
    
    /**
     * 野火鸡
     */
    public class WildTurkey implements Turkey {
        public void gobble() {
            System.out.println("咯咯");
        }
     
        public void fly() {
            System.out.println("我在飞,虽然我飞的很近");
        }
    }

    首先可以看出,它们的之间有一些共同之处,都有叫声,都可以飞行,这个也是适配的前提,有共同点!

    如果没有共同点,是不是去隔壁的其他设计模式看看?

    OK,接下来开始适配操作

    火鸡适配器(Adapter)

    /**
     * 火鸡适配器
     */
    public class TurkeyAdapter implements Duck {
        Turkey turkey;//持有一个火鸡对象
     
        public TurkeyAdapter(Turkey turkey) {
            this.turkey = turkey;
        }
    
        /**
         * 鸭叫
         */
        public void quack() {
            turkey.gobble();
        }
    
        /**
         * 飞行
         */
        public void fly() {
            //适配的时候,这里模拟飞行5次
            for(int i= 0; i < 5; i++) {
                turkey.fly();
            }
        }
    }

    适配器的逻辑也很简单

    首先,实现Duck接口,要让Client能够调用,那么首先得长得和别人一样啊

    其次,持有一个真正的处理对象,然后再根据Duck接口来进行适配,比如这里,quack接口,就直接调用Turkey#gobble(),而fly()可能是因为某种神秘力量,需要火鸡飞行的距离和鸭子一样远,所以需要手动去适配,在这里添加了适配的代码

    最后,适配器的作用就是把一个类转换成另外一个类,转换的时候可能需要一些逻辑上的处理,让它能符合用户的期待

    测试下是不是成功的伪装了呢

    public class DuckClient {
        public static void main(String[] args) {
    
            //初始化一只火鸡
            WildTurkey turkey = new WildTurkey();
            //伪装成一只鸭子
            Duck duck = new TurkeyAdapter(turkey);
    
            System.out.println("鸣叫:");
            duck.quack();
    
            System.out.println("------------------");
    
            System.out.println("飞行:");
            duck.fly();
        }
    }

    结果:

    适配器模式模式确实很简单,但是确实也很实用,优点很明显,可以将目标类和适配者解耦,不需要改动原来的结构(新增了Adapter来封装了适配的逻辑),但是建议不要在系统设计阶段就盲目的使用它,增加系统的复杂度

    外观模式

    这个就更简单了,例子我可以举一堆,比如说,酒店前台的小姐姐,餐厅前台的小姐姐,医院的小姐姐...

    核心思想:为子系统们提供一套通用的对外接口(高级API)

    为什么会有这样的需求呢?

    各个子系统在设计过程中,或者在实际使用的过程中会发现,有一些通用的步骤,对于更加高的调用层来说,它们其实不需要知道底层是通过哪些步骤来实现的,更多的是,以一个统一的接口来调用。

    比如,在想在家里搞一个家庭影院,需要以下步骤:

    1,灯光不能太亮,亮度需要调低到10

    2,需要打开投影机,并且要调整到宽屏模式

    3,音响需要调整成环绕立体音,音量设置成5

    4,打开DVD开始播放

    代码如下:

    灯光:

    /**
     * 影院灯光
     */
    public class TheaterLights {
        String description;
    
        public TheaterLights(String description) {
            this.description = description;
        }
    
        public void on() {
            System.out.println(description + " 打开");
        }
    
        public void off() {
            System.out.println(description + " 关闭");
        }
    
        public void dim(int level) {
            System.out.println(description + " 亮度调节到:" + level  + "%");
        }
    
        public String toString() {
            return description;
        }
    }
    View Code

    投影仪:

    /**
     * 投影仪屏幕
     */
    public class Screen {
        String description;
    
        public Screen(String description) {
            this.description = description;
        }
    
        public void up() {
            System.out.println(description + " 上升");
        }
    
        public void down() {
            System.out.println(description + " 下降");
        }
    
    
        public String toString() {
            return description;
        }
    }
    /**
     * 投影仪
     */
    public class Projector {
        String description;
        DvdPlayer dvdPlayer;
        
        public Projector(String description, DvdPlayer dvdPlayer) {
            this.description = description;
            this.dvdPlayer = dvdPlayer;
        }
     
        public void on() {
            System.out.println(description + " 打开");
        }
     
        public void off() {
            System.out.println(description + " 关闭");
        }
    
        public void wideScreenMode() {
            System.out.println(description + " 调整到宽屏模式");
        }
    
        public void tvMode() {
            System.out.println(description + " 调整到tv模式");
        }
      
        public String toString() {
                return description;
        }
    }
    View Code

    音响:

    /**
     * 音响
     */
    public class Amplifier {
        String description;
    
        public Amplifier(String description) {
            this.description = description;
        }
     
        public void on() {
            System.out.println(description + " 打开");
        }
     
        public void off() {
            System.out.println(description + " 关闭");
        }
    
        //立体声
        public void setStereoSound() {
            System.out.println(description + " 立体声模式");
        }
    
        //环绕声
        public void setSurroundSound() {
            System.out.println(description + " 环绕声模式");
        }
     
        public void setVolume(int level) {
            System.out.println(description + " 调整音量到: " + level);
        }
    
        public String toString() {
            return description;
        }
    }
    View Code

    DVD播放器:

    /**
     * DVD播放器
     */
    public class DvdPlayer {
        String description;
        int currentTrack;
        Amplifier amplifier;
        String movie;
        
        public DvdPlayer(String description, Amplifier amplifier) {
            this.description = description;
            this.amplifier = amplifier;
        }
     
        public void on() {
            System.out.println(description + " 播放");
        }
     
        public void off() {
            System.out.println(description + " 关闭");
        }
    
        public void play(String movie) {
            this.movie = movie;
            currentTrack = 0;
            System.out.println(description + " 播放 "" + movie + """);
        }
    
        public String toString() {
            return description;
        }
    }
    View Code

    不重要的代码就折叠了,免得难得看,不使用外观模式,需要调用一堆代码:

    /**
     * 不使用外观模式
     */
    public class Client {
        public static void main(String[] args) {
            Amplifier amp = new Amplifier("Top-O-Line 扬声器");
            DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
            Projector projector = new Projector("Top-O-Line 投影仪", dvd);
            TheaterLights lights = new TheaterLights("客厅灯");
            Screen screen = new Screen("投影仪银幕");
    
            System.out.println("准备看电影...");
            lights.dim(10);
            screen.down();
            projector.on();
            projector.wideScreenMode();
            amp.on();
            amp.setSurroundSound();
            amp.setVolume(5);
            dvd.on();
            dvd.play("夺宝奇兵");
        }
    }

    使用外观模式,一行解决:

    /**
     * 使用外观模式后的测试类
     */
    public class FacadeClient {
    
        private static HomeTheaterFacade HOME_THEATER;
        static{
            Amplifier amp = new Amplifier("Top-O-Line 扬声器");
            DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
            Projector projector = new Projector("Top-O-Line 投影仪", dvd);
            TheaterLights lights = new TheaterLights("客厅灯");
            Screen screen = new Screen("投影仪银幕");
    
            HOME_THEATER = new HomeTheaterFacade(amp, dvd, projector, screen, lights);
        }
    
        public static void main(String[] args) {
            //看电影
            HOME_THEATER.watchMovie("夺宝奇兵");
        }
    }

    我擦?咋还是这么多行?

    static块里面的代码是初始化代码,一般使用spring,都是依赖注入的东西,其实调用就一行:

    HOME_THEATER.watchMovie("夺宝奇兵");

    一键解决就是爽啊,如果说对比的话,相当于,去网上买了个床,小哥送来的是一堆零件让你组装,和小哥送来就是一张组装好了的床啊!

    但是能够一键解决的,更多的是一些通用的操作,比如说,例子中,灯光不能太亮,你想把它调到5,不想用默认的10,,那么可能就只能自己写一遍外观模式封装的逻辑了。

    那么这里就有个问题了,能不能重载方法,让它支持可以自定义灯光亮度这个参数呢?对于这个我只能说,要看业务需求了,如果100个人里面只有1个人用,那么对于系统产生的复杂度可能比 产生的价值高,反过来,可能就需要去实现。

    但是,如果这种需求越来越多,系统变得越来越复杂,那外观模式还是一个简单可爱的小姐姐吗?如果不实现,就无法达到隐藏子系统复杂度的痛点,如果实现,就会产生新的API调用的复杂度,我终于知道为啥我特么还在学习设计模式了...

    说了这么多,说说它的优缺点吧

    优点:

    1,对客户屏蔽了子系统组件使用起来门槛更低。

    2,实现了子系统与客户之间的松耦合关系。

    3,虽然提供了访问子系统的统一入口,但是并不影响用户直接使用子系统类。

    缺点:

    1,通过外观类访问子系统时,减少了可变性和灵活性。

    2,在新的子系统加入,或者子系统接口变更时,可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

  • 相关阅读:
    如何保证access_token长期有效
    微信自定义菜单的创建
    是否同一棵二叉搜索树
    Tree Traversals Again(根据前序,中序,确定后序顺序)
    List Leaves 树的层序遍历
    leetcode-优美的排列
    leetcode-下一个排列
    leetcode-二进制手表
    leetcode-组合总数III(回溯)
    leetcode-累加数(C++)
  • 原文地址:https://www.cnblogs.com/skyseavae/p/10486438.html
Copyright © 2011-2022 走看看