设计模式——观察者模式

首先吐槽一下现在看的书,刚开始看的是《JAVA设计模式》,很权威的一本书,细节描述很好,每一个模式所对应的意图等概念写得也很好,但是就是例子不太好,什么火箭发射什么的,说实话真的不太好懂。于是乎我开始看了另一本《Head First 设计模式》这是一本通俗易懂的书,一看你就知道,哦哦哦~是这么回事,原来是这样,可是看完之后你大概知道了这是个什么东西,描述的是什么,没有一个具体的概念,这时候再回头来看《JAVA设计模式》中的概念,会有更深一层的理解。

意图:在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,会通知依赖于它的对象,并根据新状态做出相应的反应。

理解:当我看完《Head First 设计模式》时,第一理解就是咱们微信中的公众号,主题——>消费者,一对多的关系,当我们订阅公众号后,每次公众号有新的信息,它会发送给我们,而且只有订阅的人才能收到信息,在观察者模式中,不光有的状态,还可以有的状态,就像我们给公众号发指令时,我们可以得到某篇文章或者资源。在这里公众号是主题,订阅者就是观察者
使用场景:公众号的订阅,微博的订阅等等,也可以说你需要一个监视者,去监视那个的变化,然后做出想对象的反应。

上代码

这里用微信公众号举例
题外话:写到这里我突然间意识到一个问题:为什么使用接口?在观察者模式,适配器模式等,都是用了很多接口,其实有的地方不是用接口也没有问题,我在想(可能是我比较菜所以菜想起这个问题),接口的最终目的是为了实现松耦合,为了简化系统,提供一套规范,而且还会将接口细化,还有一些我不知道怎么用语言表达但是我能体会到接口的意思,我想大家应该也能体会的到。

接口方面:1.主题接口Topic。2.订阅者接口Observer。3.显示数据接口Display。
在《Head First 设计模式》也是这样定义的三个接口,如果还有其他业务,其实还可以定义其他的接口。以我的理解在这里解释一下这三个接口,1和2定义接口不用说,主要是3接口,不管我们是用的方式还是用的方式从主题哪里获取数据,到达我们这里都需要显示数据的,再有众多订阅者的时候,这些操作都是重复的,定义一个接口可以免除这些重复操作。
Topic接口

public interface Topic {
    //注册为订阅者
    String register(Observer user);
    //移除订阅者
    String removeUser(Observer user);
    //当主题更新后通知所有订阅者
    void notifyObservers();
}

Observer接口

/**
 * @description: 观察者需要实现的接口,当主题有变化时,观察者也有对应的变化
 * @date: 2018年8月22日下午4:08:58
 *
 */
public interface Observer {
    void update(String name);
    //得到订阅者的名字
    String getUserName();
}

Display接口

public interface Display {
    void display();
}

接下来就是各种主题以及订阅者了:
运动主题:

public class SportSTopic implements Topic{

    private List<Observer> topicList;
    private String sport;

    public SportSTopic() {
        topicList = new ArrayList<>();
    }

    @Override
    public String register(Observer user) {
        topicList.add(user);
        return "注册为体育订阅者成功";
    }

    @Override
    public String removeUser(Observer user) {
        int indexOf = topicList.indexOf(user);
        if(indexOf >= 0){
            topicList.remove(indexOf);
            return user.getUserName() + "取消体育订阅成功";
        }
        return "发生了异常";
    }

    @Override
    public void notifyObservers() {
        for(int i = 0;i<topicList.size();i++){
            Observer observer =  (Observer)topicList.get(i);
            observer.update(sport);
        }

    }
    /**
     * @description:设置体育公众号的内容,这里简化了,由我们自己设置
     * 公众号内容,现实中都是由服务方设置的。
     * @date: 2018年8月22日下午4:58:29
     * @param:
     */
    public void setSportContent(String sportContent){
        this.sport = sportContent;
        notifyObservers();
    }
}

订阅者小李:

public class XiaoLiOberver implements Observer, Display {

    private Topic topic;
    private String articleName;
    private String name;

    /**
     * 注册一下主题
     * @param topic
     */
    public XiaoLiOberver(Topic topic) {
        this.name = "小李";
        this.topic = topic;
        topic.register(this);
    }

    @Override
    public void display() {
        System.out.println("小李订阅:C罗进球啦");
    }

    @Override
    public void update(String articleName) {
        this.articleName = articleName;
        display();
    }

    public void remove(){
        topic.removeUser(this);
    }
    //以下省略getter/setter
}

测试:

public class Test {

    public static void main(String[] args) {
        SportSTopic sportTopic = new SportSTopic();
        XiaoLiOberver observer = new XiaoLiOberver(sportTopic);
        XiaoWangOberver observer2 = new XiaoWangOberver(sportTopic);
        sportTopic.setSportContent("体育竞技");
        String remove = observer.remove();
        System.out.println(remove);
        sportTopic.setSportContent("中国足球比赛");
    }
}

结果:

小李订阅:C罗进球啦
小王订阅:C罗进球啦
小李取消体育订阅成功
小王订阅:C罗进球啦

这之上的代码是主题向订阅者的操作,为了方便显示,我多加了名字。每当主题有新的内容,订阅者就会自动收到信息,当小李取消订阅之后,那个他再也收不到信息,除非再次订阅。当然还有的方式,订阅者给主题发送某些关键字,主题收到关键字之后去搜索,然后在将信息推送给当前订阅者,这里就不简述了。

在当值变化的时候update()调用显示数据display()这一块按照书中说有更好的方法,利用MVC模式。MVC模式是指将对象(模型)从显示他的GUI元素中(视图/控制器)分离出去。《Java设计模式》中提到了侦听器,而《Head》中并没有,我想这里的更好的方法就是用mvc模式,添加一个侦听器,去监听主题是否有了新变化,有的话调用display()方法。
下面开始对代码改造,事先说明:可能不正确,等我看了后面MVC模式对这一块的讲解后再来更改,还望大佬指正,这一块大家可忽略,我只是写给我自己看。

添加一个事件类

public class TopicEvent {

    public void change(Class<?> topic,Class<?> oberver){
        //私有参数数组
        Field[] fields = topic.getDeclaredFields();
        Method method = null;
        try {
            //得到display()方法
            method = oberver.getMethod("display", null);
        } catch (NoSuchMethodException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (SecurityException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        for(Field field : fields){
            //Field 提供有关类或接口的单个字段的信息,设置为true后可通过反射访问私有变量
            field.setAccessible(true);
            //得到变量类型名 如:java.lang.List
            String name = field.getType().getName();
            //这里偷个懒,因为我只需要String类型字段,这个字段里面存放的是内容
            if(name.contains("String")){
                try {
                    //然后调用方法display()方法
                    method.invoke(oberver.newInstance(), null);
                } catch (IllegalArgumentException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                } catch (IllegalAccessException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

添加一个监听器接口

public interface TopicListener {
    void isChange(TopicEvent topicEvent);
}

再然后将sportTopic改造

public class SportSTopic implements Topic,TopicListener{

    private List<Observer> topicList;
    private String sport;

    public SportSTopic() {
        topicList = new ArrayList<>();
    }

    @Override
    public String register(Observer user) {
        topicList.add(user);
        return "注册为体育订阅者成功";
    }

    @Override
    public String removeUser(Observer user) {
        int indexOf = topicList.indexOf(user);
        if(indexOf >= 0){
            topicList.remove(indexOf);
            return user.getUserName() + "取消体育订阅成功";
        }
        return "发生了异常";
    }

    @Override
    public void notifyObservers() {
        for(int i = 0;i<topicList.size();i++){
            Observer observer =  (Observer)topicList.get(i);
            observer.update(sport);
        }

    }
    /**
     * @description:设置体育公众号的内容,这里简化了,由我们自己设置
     * 对公众号,都是由服务方设置的。
     * @date: 2018年8月22日下午4:58:29
     * @param:
     */
    public void setSportContent(String sportContent){
        this.sport = sportContent;
        notifyObservers();
    }

    @Override
    public void isChange(TopicEvent topicEvent) {
        for(int i = 0;i<topicList.size();i++){
            Observer className = (Observer) topicList.get(i);
            try {
                //因为上面强转成了接口类,但是className.getClass()得到的还是实现类,然后再new一个实例再反射
                topicEvent.change(SportSTopic.class,className.getClass().newInstance().getClass());
            } catch (InstantiationException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

测试类:

public class Test {

    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, SecurityException {
        SportSTopic sportTopic = new SportSTopic();
        XiaoLiOberver observer = new XiaoLiOberver(sportTopic);
        XiaoWangOberver observer2 = new XiaoWangOberver(sportTopic);
        sportTopic.setSportContent("体育竞技");
        //此处技术有限,就当模拟主题发生了变化或者有了新消息,你可以理解为
        //Swing中的Jbutton,我点击了了一下按钮,然后侦听器侦听到了,就触发了种种事件
        //这里是个压缩版- -相当于我点击了一下按钮
        sportTopic.isChange(new TopicEvent());

        String remove = observer.remove();
        System.out.println(remove);
        sportTopic.setSportContent("中国足球比赛");
        sportTopic.isChange(new TopicEvent());
    }
}

输出:

小李订阅:C罗进球啦
小王订阅:C罗进球啦
小李取消体育订阅成功
小王订阅:C罗进球啦

现在貌似是实现了一些功能,但是总感觉不对劲,这个问题先留着,或者大神指明,后期我再来解决。

总结:这个模式适合用在项目初期架构时,而且在java的Swing中,实现了ActionListener类观察者就可以知道Swing组件都干了什么事情,一旦有动作发生,那么观察者就会知道,现在的消息队列框架中,应该也用到了。多理解设计模式的意

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值