首先吐槽一下现在看的书,刚开始看的是《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组件都干了什么事情,一旦有动作发生,那么观察者就会知道,现在的消息队列框架中,应该也用到了。多理解设计模式的意
。