JAVA设计模式-观察者模式

开始了解观察者模式之前,我们先了解一下什么是设计模式、为什么我们要使用设计模式

  1. 什么是设计模式?
    一种针对特定问题的解决方案,这种解决方案被抽象化、模板化后就是设计模式。
  2. 为什么要使用设计模式?
    当我们使用第三方的库、框架的是很好,利用他们的API或者组件,放到合适的地方编译成我们自己的程序。但是库与框架并无法帮助我们将应用组织成易了解、易维护、具有弹性的架构,这个时候就需要设计模式。设计是一门艺术,通常有许多可以取舍的地方,如果我们利用这些经过深思熟虑,且经过时间考研的设计模式就可以稍微领先别人了。

观察者模式

该模式定义了对象之间的一对多依赖,当一个对象改变了状态,它的所有依赖者都会收到通知并自动更新。

观察者模式中,通常有一个主题和N个观察这,主题和观察者之前定义了一对多的关系。观察者依赖主题,只要主题的状态一旦发生了变化,观察者就会收到通知。观察者根据对应的通知做出响应的新值或更新。

 

 

在一些安全性要求比较高的系统中。通常我们修改了账户的密码,就会收到短信、邮件、公众号、APP推送的通知,“亲爱的用户 XXX:您好!  您于2021-03-08 11:29:20修改了您的密码,请谨记您的新密码。如非本人操作,则密码可能已经泄露,建议修改密码”,相信您一定收到过这种短信或者邮件。

我们此时用以下的类来模拟对用的通知发送类,分别对应了短信、邮件、公众号、APP推送,控制台打印了对应的文字,就表示通知发送出去了。实际情况中代码并没有这么简单。
 

package toolkit.net.cn;

/**
 * 短信通知
 * @author zhaox
 */
public class SMSDisplay {

    public void send(String username, String password) {
        System.out.println("短信-尊敬的用户" + username + ", 您的密码已经修改为" + password);
    }
}
package toolkit.net.cn;

/**
 * 邮箱通知
 * @author zhaox
 */
public class EMailDisplay {

    public void send(String username, String password) {
        System.out.println("邮件-尊敬的用户" + username + ", 您的密码已经修改为" + password);
    }
}
package toolkit.net.cn;

/**
 * 微信公众号通知
 * @author zhaox
 */
public class MPDisaplay {
    public void send(String username, String password) {
        System.out.println("公众号-尊敬的用户" + username + ", 您的密码已经修改为" + password);
    }
}
package toolkit.net.cn;

/**
 * APP端通知用户
 * @author zhaox
 */
public class APPDisplay {
    public void send(String username, String password) {
        System.out.println("APP-尊敬的用户" + username + ", 您的密码已经修改为" + password);
    }
}

新建一个AccountData类,来模拟修改账户密码后分别使用短信、邮件、公众号、APP通知用户的情况。通常没有使用任何模式的情况下,我们账户类的代码基本如下:

package toolkit.net.cn;

/**
 * @author zhaox
 */
public class AccountData {

    private SMSDisplay smsDisplay;

    private EMailDisplay eMailDisplay;

    private MPDisaplay mpDisaplay;

    private APPDisplay appDisplay;

    public AccountData(SMSDisplay smsDisplay, EMailDisplay eMailDisplay, MPDisaplay mpDisaplay,
                       APPDisplay appDisplay, String username, String password) {
        this.smsDisplay = smsDisplay;
        this.eMailDisplay = eMailDisplay;
        this.mpDisaplay = mpDisaplay;
        this.appDisplay = appDisplay;
        this.username = username;
        this.password = password;
    }

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    /**
     * 密码修改后,会调用该方法
     */
    private void passwordChanged() {
        smsDisplay.send(this.getUsername(), this.getPassword());
        eMailDisplay.send(this.getUsername(), this.getPassword());
        mpDisaplay.send(this.getUsername(), this.getPassword());
        appDisplay.send(this.getUsername(), this.getPassword());
    }

    /**
     * 模拟修改密码
     */
    public void changePassword(String newPassword) {
        this.password = newPassword;
        this.passwordChanged();
    }
}

初始化对应的推送服务,修改密码后,调用对应的推送服务来通知用户。我们通过main方法做一个模拟测试:

package toolkit.net.cn;

/**
 * @author zhaox
 */
public class Main {

    public static void main(String[] args) {

        // 短信
        SMSDisplay smsDisplay = new SMSDisplay();
        // email
        EMailDisplay eMailDisplay = new EMailDisplay();
        // mp
        MPDisaplay mpDisaplay = new MPDisaplay();
        // app
        APPDisplay appDisplay = new APPDisplay();

        AccountData accountData = new AccountData(smsDisplay, eMailDisplay, mpDisaplay, appDisplay,
                "guest", "mm123465");

        accountData.changePassword("mm000000");

    }
}

执行main方法,打印的结果如下:

这些模拟代码基本可以模拟没有使用任何设计模式情况下的代码。

 

这基本上算是一个很糟糕的示范:

  1. 通知发送都是针对集体的实现变成,会导致我们以后在增加或删除通知方式时必须修改AccountData类。
  2. 短信、邮件、公众号、APP这四个类的发送方法基本是一样的,包括参数。

接下来我们使用观察者模式,对上面代码做一个调整,观察者模式定义了对象之间的一对多依赖,当一个对象改变了状态,它的所有依赖者都会收到通知并自动更新。很显然这里AccountData就是观察者模式中的主题对象,对应的短信、邮件、公众号、APP通知类就是依赖者对象。实现观察者模式的方法有很多种,这里就使用包含Subject与Observer接口类的设计的做法,另外Java API内置了观察者模式,java.util包内的Observer接口与Observable类。

  1. 观察者接口,所有观察者必须实现该接口,这个接口只有update()一个方法,当主题发生改变的时候,该方法被调用。
    package toolkit.net.cn;
    
    /**
     * 观察者接口
     * @author zhaox
     */
    public interface Observer {
    
        /**
         * 主题发送状态改变,该方法就会被调用
         * @param username 
         * @param password
         */
        public void update(String username, String password);
    }

     

  2. 主题接口,对象使用此接口注册为观察者或把自己从观察者中删除。
    package toolkit.net.cn;
    
    /**
     * 主题接口
     * @author zhaox
     */
    public interface Subject {
    
        /**
         * 注册观察者
         * @param observer
         */
        public void registerObserver(Observer observer);
    
        /**
         * 删除观察者
         * @param observer
         */
        public void removeObserver(Observer observer);
    
        /**
         * 当主题状态发生改变这个方法被调用来通知素有的观察者
         */
        public void notifyObservers();
    }
    

     

  3. 通知接口,该接口只包含一个send()方法,当需要发送对应的通知时,调用此方法。
    package toolkit.net.cn;
    
    /**
     * 
     * @author zhaox
     */
    public interface DisplayMessage {
        /**
         * 发送通知
         */
        public void send();
    }
    

     

  4. 在AccountData中实际Subject接口,并增加一个ArrayList来纪录观察者。
    package toolkit.net.cn;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author zhaox
     */
    public class AccountData implements Subject {
    
        /**
         * 纪录观察者对象
         */
        private List<Observer> observers;
    
        /**
         * 用户名
         */
        private String username;
    
        /**
         * 密码
         */
        private String password;
    
        public String getUsername() {
            return username;
        }
    
        public String getPassword() {
            return password;
        }
    
        /**
         * 密码修改后,会调用该方法
         */
        private void passwordChanged() {
    
        }
    
    
        public AccountData(String username, String password) {
            this.username = username;
            this.password = password;
            observers = new ArrayList<>(16);
        }
    
        /**
         * 注册观察者
         * @param observer
         */
        @Override
        public void registerObserver(Observer observer) {
            observers.add(observer);
        }
    
        /**
         * 取消注册
         * @param observer
         */
        @Override
        public void removeObserver(Observer observer) {
            int index = observers.indexOf(observer);
            if (index >= 0) {
                observers.remove(index);
            }
        }
    
        @Override
        public void notifyObservers() {
            int size = observers.size();
            for (int i = 0; i < size; i++) {
                Observer observer = observers.get(i);
                observer.update(this.getUsername(), this.getPassword());
            }
        }
    
        /**
         * 模拟修改密码
         */
        public void changePassword(String newPassword) {
            this.password = newPassword;
            this.passwordChanged();
        }
    }
    

     

  5. 短信、邮件、公众号、APP对应的四个类,都实现Observer、DispalyMessage接口,修改如下
    package toolkit.net.cn;
    
    /**
     * 短信通知
     * @author zhaox
     */
    public class SMSDisplay implements Observer, DisplayMessage {
    
        private String username;
        private String password;
        private Subject accountData;
    
        public SMSDisplay(Subject accountData) {
            this.accountData = accountData;
            accountData.registerObserver(this);
        }
    
        @Override
        public void update(String username, String password) {
            this.username = username;
            this.password = password;
            this.send();
        }
    
        @Override
        public void send() {
            System.out.println("短信-尊敬的用户" + username + ", 您的密码已经修改为" + password);
        }
    
    }
    
    package toolkit.net.cn;
    
    /**
     * 邮箱通知
     * @author zhaox
     */
    public class EMailDisplay implements Observer, DisplayMessage {
    
        private String username;
        private String password;
        private Subject accountData;
    
        public EMailDisplay(Subject accountData) {
            this.accountData = accountData;
            accountData.registerObserver(this);
        }
    
        @Override
        public void update(String username, String password) {
            this.username = username;
            this.password = password;
            this.send();
        }
    
        @Override
        public void send() {
            System.out.println("邮件-尊敬的用户" + username + ", 您的密码已经修改为" + password);
        }
    }
    
    package toolkit.net.cn;
    
    /**
     * 微信公众号通知
     * @author zhaox
     */
    public class MPDisaplay implements Observer, DisplayMessage {
    
        private String username;
        private String password;
        private Subject accountData;
    
        public MPDisaplay(Subject accountData) {
            this.accountData = accountData;
            accountData.registerObserver(this);
        }
    
        @Override
        public void update(String username, String password) {
            this.username = username;
            this.password = password;
            this.send();
        }
    
        @Override
        public void send() {
            System.out.println("公众号-尊敬的用户" + username + ", 您的密码已经修改为" + password);
        }
    }
    
    package toolkit.net.cn;
    
    /**
     * APP端通知用户
     * @author zhaox
     */
    public class APPDisplay implements Observer, DisplayMessage {
    
        private String username;
        private String password;
        private Subject accountData;
    
        public APPDisplay(Subject accountData) {
            this.accountData = accountData;
            accountData.registerObserver(this);
        }
        @Override
        public void update(String username, String password) {
            this.username = username;
            this.password = password;
            this.send();
        }
    
        @Override
        public void send() {
            System.out.println("APP-尊敬的用户" + username + ", 您的密码已经修改为" + password);
        }
    }
    



     

修改main方法测试:

package toolkit.net.cn;

/**
 * @author zhaox
 */
public class Main {

    public static void main(String[] args) {
        AccountData accountData = new AccountData("guest", "mm123456");

        SMSDisplay smsDisplay = new SMSDisplay(accountData);
        EMailDisplay eMailDisplay = new EMailDisplay(accountData);
        MPDisaplay mpDisaplay = new MPDisaplay(accountData);
        APPDisplay appDisplay = new APPDisplay(accountData);

        accountData.changePassword("mm000000");
        System.out.println("----------------------------");
        System.out.println("移除公众号通知");
        System.out.println("----------------------------");
        accountData.removeObserver(mpDisaplay);
        accountData.changePassword("mm888888");
    }
}

执行main方法,打印结构如下:

 

移除公众号的通知后,控制台打印中没有了公众号通知类的打印,结果正确。

 

 


上面的代码中主题是一个具有状态的对象,观察者使用这些状态,虽然这些状态并不属于他们。有许多观察者依赖主题来告诉他们状态何时改变了。这就产生了一个关系:一个主题对应多个观察者的关系。

这样做的好处是什么?

  1. 关于观察者对象,主题只知道观察者对象实现了某个接口(Observer接口),主题并不需要关注观察者的具体类是谁,怎么实现的。
  2. 任何时候都可以新增观察者,因为主题依赖的是一个实现了Observer接口的对象列表,所以我们可以随时增加一个观察者。我们可以在运行时通过代码用心的观察者取代现有的观察者,主题中的代码无需做任何修改,同样的也可以删除某些观察者。
  3. 改变主题或者观察者任何一方,并不会影响到另外一个方。因为两者是松耦合的,只要他们之间的接口任被遵守,我们就可以自由的修改。当两个对象之间松耦合,他们依然可以介乎,但是不太清楚彼此的细节。

松耦合的设计能够让我们建立有弹性的OO系统,对象之间的互相依赖降到了最低,能够更好的应对变化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值