zoukankan      html  css  js  c++  java
  • Java进阶专题(八) 设计模式之适配器模式、装饰者模式、观察者模式

    前言

    ​ 今天开始我们专题的第八课了。本章节将介绍:三个设计模式,适配器模式、装饰者模式和观察者模式。通过学习适配器模式,可以优雅的解决代码功能的兼容问题。另外有重构需求的人群一定需要掌握装饰者模式。本章节参考资料书籍《Spring 5核心原理》中的第一篇 Spring 内功心法(Spring中常用的设计模式)(没有电子档,都是我取其精华并结合自己的理解,一个字一个字手敲出来的,如果觉得本文对你有用,请点个推荐)。

    适配器模式

    适配器模式的应用场景

    ​ 适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。
    适配器适用于以下几种业务场景:
    ​ 1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。
    ​ 2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。

    在中国民用电都是 220V 交流电,但我们手机使用的锂电池使用的 5V 直流电。因此,我 们给手机充电时就需要使用电源适配器来进行转换。下面我们有代码来还原这个生活场 景,创建 AC220 类,表示 220V 交流电

    package com.study;
    
    /**
     * @author wangzhongyuan
     */
    public class AC220 {
        public int outputAC220V(){
            int output = 220;
            System.out.println("提供220V交流电");
            return output;
        }
    }
    
    

    创建5V电源的接口:

    package com.study;
    
    /**
     * @author wangzhongyuan
     */
    public interface DC5 {
        int output5V();
    }
    
    

    创建提供5V电源的适配器:

    package com.study;
    
    /**
     * @author wangzhongyuan
     */
    public class PowerAdapter implements DC5{
        AC220 ac220;
    
        public PowerAdapter(AC220 ac220) {
            this.ac220 = ac220;
        }
    
        @Override
        public int output5V() {
            int outputAC220V = ac220.outputAC220V();
            System.out.println("将220v转换成5v");
            return outputAC220V/44;
        }
    }
    
    

    测试代码:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/20 11:17 下午
     */
    public class DemoTest {
    
        public static void main(String[] args) {
            DC5 powerAdapter = new PowerAdapter(new AC220());
            powerAdapter.output5V();
        }
    }
    
    

    输出结果:

    从上面的代码样式可以看出,适配器就是通过增加一个适配器类持有原有提供者的对象,实现了二者的兼容。

    适配模式的优缺点

    优点:

    1、能提高类的透明性和复用,现有的类复用但不需要改变。

    2、目标类和适配器类解耦,提高程序的扩展性。

    3、在很多业务场景中符合开闭原则。

    缺点:

    1、适配器编写过程需要全面考虑,可能会增加系统的复杂性。

    2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

    装饰者模式

    装饰者模式的应用场景

    ​ 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对 象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。 装饰者模式在我们生活中应用也比较多如给煎饼加鸡蛋;给蛋糕加上一些水果;给房子 装修等,为对象扩展一些额外的职责。装饰者在代码程序中适用于以下场景:

    1、用于扩展一个类的功能或给一个类添加附加职责。

    2、动态的给一个对象添加功能,这些功能可以再动态的撤销。

    代码实现

    来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点,于是很多小伙伴为了多赖一会儿床都不吃早餐。那么,也有些小伙伴可能在上班路上碰 到卖煎饼的路边摊,都会顺带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼 加鸡蛋,也可以加香肠。

    创建煎饼类:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/20 11:57 下午
     */
    public class Battercake {
    
        public String getMsg(){
            return "煎饼";
        }
    
        public int getPrice(){
            return 5;
        }
    }
    
    

    给煎饼加个蛋:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/20 11:59 下午
     */
    public class BattercakeWithEgg extends Battercake{
        @Override
        public String getMsg() {
            return super.getMsg() + "加鸡蛋";
        }
    
        @Override
        public int getPrice() {
            return super.getPrice() + 1;
        }
    }
    
    

    再加个烤肠:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:00 上午
     */
    public class BatterCakeWithEggAndSausage extends BattercakeWithEgg{
        @Override
        public String getMsg() {
            return super.getMsg() +"加烤肠";
        }
    
        @Override
        public int getPrice() {
            return super.getPrice() + 3;
        }
    }
    
    

    测试代码:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:01 上午
     */
    public class DemoTest {
    
        public static void main(String[] args) {
            BatterCakeWithEggAndSausage batterCakeWithEggAndSausage = new BatterCakeWithEggAndSausage();
            String msg = batterCakeWithEggAndSausage.getMsg();
            int price = batterCakeWithEggAndSausage.getPrice();
            System.out.println("买了:"+msg+",价格为:"+price);
        }
    }
    
    

    输出结果:

    运行结果没有问题,但是如果用户需要一个加 2 个鸡蛋加 1 根香肠的煎饼,那么用我们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。 如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰者模式来解决上面 的问题。

    创建煎饼抽象类:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:18 上午
     */
    public abstract class AbstractBatterCake {
        protected abstract String getMsg();
        protected abstract int getPrice();
    }
    
    

    创建基础套餐煎饼:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:19 上午
     */
    public class BatterCakeWithBase extends AbstractBatterCake{
        @Override
        protected String getMsg() {
            return "煎饼";
        }
    
        @Override
        protected int getPrice() {
            return 5;
        }
    }
    
    

    创建额外套餐的抽象装饰类:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:21 上午
     */
    public abstract class BatterCakeDecorator extends AbstractBatterCake{
        AbstractBatterCake batterCake;
    
        public BatterCakeDecorator(AbstractBatterCake batterCake) {
            this.batterCake = batterCake;
        }
    
        protected abstract void doSomething();
    
        @Override
        protected String getMsg() {
            return this.batterCake.getMsg();
        }
    
        @Override
        protected int getPrice() {
            return this.batterCake.getPrice();
        }
    }
    
    

    创建加鸡蛋套餐:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/20 11:59 下午
     */
    public class BattercakeWithEgg extends BatterCakeDecorator{
    
    
        public BattercakeWithEgg(AbstractBatterCake batterCake) {
            super(batterCake);
        }
    
        @Override
        protected void doSomething() {
    
        }
    
        @Override
        public String getMsg() {
            return super.getMsg() + "加鸡蛋";
        }
    
        @Override
        public int getPrice() {
            return super.getPrice() + 1;
        }
    }
    
    

    创建加烤肠套餐:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:00 上午
     */
    public class BatterCakeWithSausage extends BatterCakeDecorator{
    
        public BatterCakeWithSausage(AbstractBatterCake batterCake) {
            super(batterCake);
        }
    
        @Override
        protected void doSomething() {
    
        }
    
        @Override
        public String getMsg() {
            return super.getMsg() +"加烤肠";
        }
    
        @Override
        public int getPrice() {
            return super.getPrice() + 3;
        }
    }
    
    

    测试代码:

    package com.study;
    
    /**
     * @author wang.zhongyuan
     * @version 1.0
     * @date 2020/7/21 12:01 上午
     */
    public class DemoTest {
    
        public static void main(String[] args) {
            AbstractBatterCake batterCake;
            batterCake = new BatterCakeWithBase();
            batterCake = new BattercakeWithEgg(batterCake);
            batterCake = new BatterCakeWithSausage(batterCake);
            //再加个鸡蛋
            batterCake = new BattercakeWithEgg(batterCake);
            System.out.println(batterCake.getMsg()+",价格:"+batterCake.getPrice());
        }
    }
    
    

    输出结果:

    装饰者模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这样两个案例,我们可以总结出来,其实抽象的装饰者是可有可无的,具体可以根据业务模型来选择。

    装饰者模式与适配器模式对比

    装饰者和适配器模式都是包装模式(Wrapper Pattern),装饰者也是一种特殊的代理模式。

    		装饰者模式 															适配器模式 
    形式 是一种非常特别的适配器模式 没有层级关系,		装饰器模式有层级关系 
    定义 装饰者和被装饰者都实现同一个接口,					适配器和被适配者没有必然的联系,通 常是采用继承或代理的形式进行包装 
    		主要目的是为了扩展之后依旧保 留 OOP 关系 
    关系 满足 is-a 的关系 												 满足 has-a 的关系 
    功能 注重覆盖、扩展 							 							注重兼容、转换 
    设计 前置考虑 									 							后置考虑
    

    装饰者模式的优缺点

    优点:

    1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。

    2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。

    3、装饰者完全遵守开闭原则。

    缺点:

    1、会出现更多的代码,更多的类,增加程序复杂性。

    2、动态装饰时,多层装饰时会更复杂。

    观察者模式

    观察者模式的应用场景

    ​ 观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同 时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通 知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用 于在关联行为之间建立一套触发机制的场景。

    代码实现

    ​ 小伙伴们肯定都刷过抖音,遇到喜欢的作品都会点个❥喜欢,我们通过模拟喜欢这个事件来实践下观察者模式。当你点击喜欢时,会触发两个事件,一个喜欢数量会增加+1,二是作者会受到消息。你可能会想到MQ,异步队列等,其实JDK本身就提供这样的API。

    创建事件模型类,用于区分什么样的事件,观察者可以根据不同事件类型做不同处理:

    package com.study.demo4;
    
    /**
     * 事件模型
     * @Author wangzhongyuan
     * @Date 2020/7/21 11:28
     * @Version 1.0
     **/
    public enum EventModel {
        LIKE_EVENT("喜欢事件",1,null),
        MCOMENT_ENVET("评论事件",2,null)
        ;
    
        private String message;
        private int type;
        private Object date;
    
        EventModel(String message, int type, Object date) {
            this.message = message;
            this.type = type;
            this.date = date;
        }}
    

    创建事件类,被观察者对象,调用方可以向该对象中传送事件:

    package com.study.demo4;
    
    import java.util.Observable;
    
    /**
     * 事件总线(被观察者)
     * @Author wangzhongyuan
     * @Date 2020/7/21 11:24
     * @Version 1.0
     **/
    public class EventBus extends Observable{
        /**
         * 被观察者方法
         * @param eventModel
         */
        public void postEvent(EventModel eventModel){
            System.out.println("推送事件");
            setChanged();
            //通知观察者
            notifyObservers(eventModel);
        }
    }
    

    创建不同业务的观察者:

    package com.study.demo4;
    
    import java.util.Observable;
    import java.util.Observer;
    
    /**
     * 观察者1
     *
     * @Author 19054253
     * @Date 2020/7/21 11:32
     * @Version 1.0
     **/
    public class OneObserver implements Observer {
    
        public void update(Observable o, Object arg) {
            System.out.println("观察1,监听到了事件,触发:给作者推送消息!");
        }
    }
    
    package com.study.demo4;
    
    import java.util.Observable;
    import java.util.Observer;
    
    /**
     * 观察者2
     *
     * @Author 19054253
     * @Date 2020/7/21 11:48
     * @Version 1.0
     **/
    public class TwoObserver implements Observer {
    
        public void update(Observable o, Object arg) {
            System.out.println("观察2,监听到了事件,触发:喜欢总数+1");
        }
    }
    

    调用测试:

    package com.study.demo4;
    
    /**
     * @Author wangzhongyuan
     * @Date 2020/7/21 11:35
     * @Version 1.0
     **/
    public class DemoTest {
        public static void main(String[] args) {
            //被观察者
            EventBus eventBus = new EventBus();
            //观察者
            OneObserver oneObserver = new OneObserver();
            TwoObserver twoObserver = new TwoObserver();
            //监听观察者
            eventBus.addObserver(oneObserver);
            eventBus.addObserver(twoObserver);
            //被观察者触发事件
            eventBus.postEvent(EventModel.LIKE_EVENT);
        }
    }
    

    输出结果:

    从上面代码可以看出来,观察者模式的本质就是,被观察者对象持有观察者对象的引用,由被观察者去通知了观察者去做了某件事。JDK源码中,观察者模式也应用非常多。例如java.awt.Event就是观察者模式的一种,只不过Java很少被用来写桌面程序。以上就是我模拟的事件机制。

    观察模式的优缺点

    优点:

    1、观察者和被观察者之间建立了一个抽象的耦合。
    2、观察者模式支持广播通信。

    缺点:

    1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。
    2、使用要得当,要避免循环调用。

  • 相关阅读:
    安卓:线性布局
    安卓文件的资源访问
    关于安卓的一些初步认识
    haayaGuardV0---3---手机与pc基于p2p通信
    【无中生有】---13---系统发布
    【无中生有】---12---运维与监控
    【无中生有】---11---服务器集群部署
    【无中生有】---7---分布式消息队列
    使用消息队列的 10 个理由
    【无中生有】---10---API接口日志存储设计与实现设计
  • 原文地址:https://www.cnblogs.com/whgk/p/13357954.html
Copyright © 2011-2022 走看看