定义了对象之间的一对多依赖,因此当一个对象改变状态时,它的所有依赖项都会收到通知并自动更新。
OO设计原则为了交互对象之间的松耦合设计而努力。类图观察者模式类图
代码Subject.csnamespace DesignPatterns.Observer.Structural{ public abstract class Subject { List<Observer> observers = new List<Observer>(); public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } public void Notify() { foreach(Observer o in observers) { o.Update(); } } }}ConcreteSubject.cs
namespace DesignPatterns.Observer.Structural{ public class ConcreteSubject : Subject { private string? subjectState; public string? SubjectSate { get { return subjectState; } set { subjectState = value; Notify(); } } }}Observer.cs
namespace DesignPatterns.Observer.Structural{ public abstract class Observer { public abstract void Update(); }}ConcreteObserver.cs
namespace DesignPatterns.Observer.Structural{ public class ConcreteObserver : Observer { private string name; private string? observerState; private ConcreteSubject subject; public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this = name; } public override void Update() { observerState = subject.SubjectSate; Console.WriteLine("Observer {0}'s new state is {1}.", name, observerState); } }}Program.cs
using DesignPatterns.Observer.Structural;internal class Program{ private static void Main(string[] args) { ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver concreteObserverZ = new ConcreteObserver(subject, "Z"); subject.Attach(new ConcreteObserver(subject, "X")); subject.Attach(new ConcreteObserver(subject, "Y")); subject.Attach(concreteObserverZ); subject.SubjectSate = "ABC"; subject.Detach(concreteObserverZ); subject.SubjectSate = "EFG"; }}
输出结果
Observer X's new state is ABC.Observer Y's new state is ABC.Observer Z's new state is ABC.Observer X's new state is EFG.Observer Y's new state is EFG.
定义了对象之间的一对多依赖,因此当一个对象改变状态时,它的所有依赖项都会收到通知并自动更新。
OO设计原则为了交互对象之间的松耦合设计而努力。类图观察者模式类图
代码Subject.csnamespace DesignPatterns.Observer.Structural{ public abstract class Subject { List<Observer> observers = new List<Observer>(); public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } public void Notify() { foreach(Observer o in observers) { o.Update(); } } }}ConcreteSubject.cs
namespace DesignPatterns.Observer.Structural{ public class ConcreteSubject : Subject { private string? subjectState; public string? SubjectSate { get { return subjectState; } set { subjectState = value; Notify(); } } }}Observer.cs
namespace DesignPatterns.Observer.Structural{ public abstract class Observer { public abstract void Update(); }}ConcreteObserver.cs
namespace DesignPatterns.Observer.Structural{ public class ConcreteObserver : Observer { private string name; private string? observerState; private ConcreteSubject subject; public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this = name; } public override void Update() { observerState = subject.SubjectSate; Console.WriteLine("Observer {0}'s new state is {1}.", name, observerState); } }}Program.cs
using DesignPatterns.Observer.Structural;internal class Program{ private static void Main(string[] args) { ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver concreteObserverZ = new ConcreteObserver(subject, "Z"); subject.Attach(new ConcreteObserver(subject, "X")); subject.Attach(new ConcreteObserver(subject, "Y")); subject.Attach(concreteObserverZ); subject.SubjectSate = "ABC"; subject.Detach(concreteObserverZ); subject.SubjectSate = "EFG"; }}
输出结果
Observer X's new state is ABC.Observer Y's new state is ABC.Observer Z's new state is ABC.Observer X's new state is EFG.Observer Y's new state is EFG.
我的世界基岩版1.15.0.56发布了,此次修复了游戏过程中可能发生的几次崩溃,一起来看看吧!
\r【下载地址:我的世界1.15.0.56下载】
\r\r修正:
\r崩溃/性能
\r修复了游戏过程中可能发生的几次崩溃
\r修复了有时由于使用自定义块而导致的崩溃
\r修复了尝试使用自定义配方加载项时还会崩溃的问题,该加载项还会向玩家返回物品
\r修复了当玩家使用抗火药水或呼吸水后在水中或熔岩中加载已保存的游戏时有时会发生崩溃的问题
\r修复了在打开菜单的情况下退出游戏时有时可能导致游戏崩溃的问题
\r使用近战时使用的附魔来修复崩溃(MCPE-63517)
\r修复了加载关卡数据时可能发生的崩溃
\r一般
\r修复了将世界从Console Editions转换为Bedrock(MCPE-58480)时可能导致块被覆盖的问题
\r现在,地图将以正确的比例从Editions转换为Bedrock(MCPE-58796)
\r修复了两种第三人称视角中的各种手臂动画(MCPE-63088)
\r游戏玩法
\r修复了一个问题,该问题可能会导致块在损坏后重新出现(MCPE-48914)
\r修复了移动时观察者块无法供电的问题(MCPE-63785)
\r魔法武器现在可以再次正确地应用其效果(MCPE-63124)
\r更换装甲现在将更新并在视觉上显示正确的装甲(MCPE-63135)
\r剪羊毛现在可以正确更新绵羊(MCPE-63188)
\r缺少日志的云杉和巨型云杉树不再生成(MCPE-63658)
\r叶子不再阻止幼树长成树(MCPE-63153)
\r料斗现在将从上方放置的堆肥器中收集骨粉(MCPE-63809)
\r用户界面
\r现在,命令块的用户界面将正确地以4:3的比例适合屏幕内(MCPE-41730)
\r附加组件和脚本
\r蜜蜂产卵现在可以在内容包中进行编辑
\r修复了阻止自定义块在附加包中使用的问题(MCPE-63121)
\r修复了在某些情况下可能导致Minecraft:timer组件过早启动的加载项问题
\r修复了导致粒子动画反转的问题
\r在附加组件中使用minecraft:pushable组件将不再破坏较旧的内容
\r修复了阻止行为包中的动画控制器在专用服务器和领域上运行的问题(MCPE-59881)
\r▍特别推荐
\r【水域更新】我的世界水域更新物种大全 水域更新物种详细攻略
\r【1.2攻略汇总】我的世界1.2统一更新攻略汇总 手机版1.2内容详解
\r【MC环游记】【我的世界环游记】17:终结之章
\r【MC大事报】我的世界基岩版迎来1.4水域更新 MC大事报第60期
\r【游戏问答】我的世界水鬼怎么驯服 手机版水鬼驯服教程
\r【中国版】我的世界海洋版清凉上线 全新手游体验让你嗨翻一夏
\r【合成表】我的世界三叉戟合成表 手机版三叉戟怎么做
\r【周边趣闻】我的世界用MINECRAFT艺术支持慈善 多图欣赏
\r【攻略心得】我的世界水域更新攻略汇总 手机版水域更新UpdateAquatic内容详解
\r更多精彩内容,请关注我的世界专区
来源:好游快爆App-我的世界社区
作者 | 静幽水
责编 | Elle
问题背景话说程序员小强成功进入一家公司,并且老板也信守承诺给他分配了一个女朋友小美,老板这样做除了能让小强每天安心写代码之外,还有另外两个意图,第一就是小美是安插在小强身边的眼线,负责监督小强的工作,第二个也是最重要的目的是通过小美可以把公司重要的通知传递给小强。如下是过程示意图
以前我们是怎么用程序演示上面过程呢?(这里还没有使用观察者模式)为了简单,我不使用接口,直接使用老板,程序员,小美和客户端四个类。
老板类如下:
public class Boss{//用来定义今晚是否加班private boolean isnotifyProgrammer = false;public boolean getIsIsnotifyProgrammer {return isnotifyProgrammer;}//通知所有程序员加班public void notifyProgrammer {System.out.println(\"所有程序员今晚加班\");this.isnotifyProgrammer = true;}}
程序员类:
public class Programmer {private String name;//省去构造函数和set/get//接到加班通知,然后加班public void work_overtime {System.out.println(name+\"要加班\");}}
小美类,使用线程进行监控老板类,查看老板是否发出加班通知,如果发出就通知所有程序员加班
public class GrilFriend extends Thread{private Boss boss;private Programmer programmer;public GrilFriend(Boss _boss,Programmer _programmer){this.boss = _boss;thisgrammer = _programmer;}@Overridepublic void run{while (true){if(this.boss.getIsIsnotifyProgrammer){thisgrammer_overtime;}}}}
继承Thread类,重写run方法,然后start就可以启动一个线程了。
客户端
package Observer;public class Client {public static void main(String[] args) throws InterruptedException {Boss boss = new Boss;Programmer programmer = new Programmer(\"小强\");GrilFriend grilFriend = new GrilFriend(boss,programmer);grilFriend.start;boss.notifyProgrammer;Thread.sleep(1000);}}
将老板和程序员小强的实例传进小美类中,然后开启线程进行监听老板是否发出通知,只要这时老板发出通知,程序员就可以接收到并执行加班的命令。输出如下:
小强要加班小强要加班.....
有何问题上面这个程序,小美使用死循环来监听,导致CPU飙升,严重浪费了公司的资源,公司面临亏损,老板决定裁员来开源节流,就这样处在程序员和老板直接的小美就被裁掉了,没了小美之后,程序员就要上班时去老板办公室签到,下班时再去询问今晚是否加班。久而久之,程序员心生怨念,老板每天被问来问去也很烦,于是他们想到了一种新的解决方案。
解决方法于是老板买来几个小喇叭,放在程序员的办公室里,而按钮放在老板的办公室里,如果有新的通知,就按喇叭,程序员们听到之后就前往老板的办公室领取具体的通知内容,这样就不需要程序员每天去老板办公室询问是否有新的通知了,而且还节约了成本,所谓一举两得。
怎么用代码实现上面的过程呢
首先定义一个老板的接口类,里面主要是公共方法如添加程序员对象和删除程序员对象以及通知所有程序员的方法。以后还有其他的小老板,都继承自这个接口:
import java.util.ArrayList;import java.util.List;public class Boss{//定义一个列表,用来保存程序员对象private List<Programmer> programmers = new ArrayList<Programmer>;//添加一个程序员到列表中public void attach(Programmer programmer){programmers.add(programmer);}//从列表中删除某个程序员对象public void detach(Programmer programmer){programmers.remove(programmer);}//通知所有程序员加班public void notifyProgrammer(boolean isWorkOvertime) {//遍历程序员列表for(Programmer programmer:programmers)programmer_overtime(isWorkOvertime);}}
当前老板的类,当前老板设置一个状态,就是今晚程序员是否需要加班,当这个状态发生改变时,就调用上面的notifyProgrammer方法通知所有程序员:
//具体的老板-李老板public class Li_Boss extends Boss {//是否加班的通知private boolean isWorkOvertime;public boolean getIsWorkOvertime{return isWorkOvertime;}//当加班状态发生变化时,要通知程序员最新的加班状态public void setIsWorkOvertime(boolean isWorkOvertime){this.isWorkOvertime = isWorkOvertime;this.notifyProgrammer(isWorkOvertime);}}
程序员的接口,只有一个方法,被通知的是否加班方法:
public interface IProgrammer {//被通知的方法public void work_overtime(boolean isWorkOvertime);}
具体的程序员类,实现上面加班的方法,执行加班行为:
public class Programmer implements IProgrammer{private String name;public Programmer(String name) {this = name;}public String getName {return name;}public void setName(String name) {this = name;}//自己的加班状态private boolean WorkOvertimeState;//接到加班通知,然后更新自己的加班状态public void work_overtime(boolean isWorkOvertime) {WorkOvertimeState = isWorkOvertime;System.out.println(name+\"是否要加班:\"+WorkOvertimeState);}}
客户端,使用上面的类,先创建一个老板对象和两个程序员对象,并把两个程序员对象添加到老板对象中的程序员列表中(相当于上班前来打卡,老板知道今天有哪些程序员来上班了),然后老板发布今晚加班的通知,所有程序员就会自动接收到通知,并执行加班的行为:
public class Client {public static void main(String[] args) throws InterruptedException {Li_Boss boss = new Li_Boss;Programmer programmer1 = new Programmer(\"小强\");Programmer programmer2 = new Programmer(\"小华\");//将上面两个程序员添加到老板类里的程序员列表中boss.attach(programmer1);boss.attach(programmer2);//老板发布通知,今晚加班boss.setIsWorkOvertime(true);}}
打印结果如下:
小强是否要加班:true小华是否要加班:true
模式讲解以上的实现方式就是通过观察者模式实现的,观察者模式定义:
定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
上面老板和程序员的关系就是典型的 一对多关系,并且程序员是否加班的状态也是由老板决定的,程序员会根据老板的通知执行相对应的行为,而老板不需要知道具体有哪些程序员,只需要维护一个程序员的列表就可以了,通知时遍历这个列表。
老板相当于被观察者,也被称为目标:Subject,程序员是观察者,也就是Observer,一个目标可以有任意多个观察者对象,一旦目标的状态发生变化,所有注册的观察者都会得到通知,然后各个观察者会对通知做出相应的反应。
但是需要注意,观察者始终是处于一种被动的地位,也就是注册和删除都是由目标来决定的,观察者没有权限决定是否观察哪个目标。这叫做单向依赖,观察者依赖于目标,目标是不会依赖于观察者的。这一点也很好了解,就像是程序员没有权限决定听不听老板的通知一样,老板通知所有程序员加班,你不能决定你能不能接收到通知吧。
观察者也可以观察多个目标,但是应该为不同的观察者目标定义不同的回调函数用来区分,也就是每个通知对应一个响应函数。这一点也很容易理解,一家公司会有老板,还会有部门经理,主管等职位,程序员都要听他们的。
观察者模型结构示意图:
Subject目标对象,相当于上面的Boss类
Observer:观察者接口,提供目标通知时对应的更新方法,可以在这个方法中回调目标对象,以获取目标对象的数据,相当于上面的IProgrammer接口
ConcreteSubject:具体的目标实现类,相当于上面的Li_boss
ConcreteObserver:具体的观察者对象,用来接收通知并做出响应,相当于上面的Programmer类。
新的问题公司这样执行了一段时间,新的问题又出现了,因为老板并不只是通知加班这么简单,还会通知其他的事情,可能每个程序员想得到的通知不一样,比如小强去出差,小华去休假。这个时候该怎么通知他们呢。
这就涉及观察者的两种模型,拉模型和推模型。上面的代码就是推模型实现的。
推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据。如上面就是推送的是否加班的状态。
拉模型“目标对象在通知观察者的时候,只会传递少量信息,如果不知道观察者具体需要什么数据,就把目标对象自身传递给观察者,让观察者自己按需去取信息。
为了方便对比,接下来用拉模型实现通知加班的代码,首先看一下Boss类,只需要更改通知的方法:
import java.util.ArrayList;import java.util.List;public class Boss{//定义一个列表,用来保存程序员对象private List<Programmer> programmers = new ArrayList<Programmer>;//添加一个程序员到列表中public void attach(Programmer programmer){programmers.add(programmer);}//从列表中删除某个程序员对象public void detach(Programmer programmer){programmers.remove(programmer);}//通知所有程序员加班public void notifyProgrammer {//遍历程序员列表for(Programmer programmer:programmers)programmer_overtime(this);}}
notifyProgrammer方法去掉参数,调用 programmer_overtime时将自身this传递进去。具体的老板类Li_Boss只需要实现这个方法就可以了。
IProgrammer接口中被通知的方法接收参数boss对象:
public interface IProgrammer {//被通知的方法public void work_overtime(Boss boss);}
具体的programmer实现类,也只需要该接收通知的方法:
public class Programmer implements IProgrammer{......//自己的加班状态private boolean WorkOvertimeState;//接到加班通知,然后更新自己的加班状态public void work_overtime(Boss boss) {WorkOvertimeState = ((Li_Boss)boss).getIsWorkOvertime;System.out.println(name+\"是否要加班:\"+WorkOvertimeState);}}
客户端不需要更改,输出结果和上面一样,没有问题。当有多个具体的观察者(程序员)类时,不同的类需要不同的信息,在目标类中定义多种状态,当具体的观察者接到通知后通过目标对象实例按需去状态就可以了。
推拉模型的比较:
推模型是假定目标对象知道观察者需要什么数据,相当于精准推送。拉模型目标对象不知道观察者需要什么数据,把自身对象给观察者,让观察者自己去取。
推模型使观察者模型难以复用,拉模型可以复用。
相关扩展其实在java中已经有了观察者模式的实现,不需要自己从头写。在java.util包里面有一个类Observable,它实现了大部分我们需要的目标功能。接口Observer中定义了update方法。相比于上面自己的实现区别如下:
不需要定义观察者和目标的接口,JDK已经定义好了
在目标实现类中不需要维护观察者的注册信息,这个Observable类中帮忙实现好了。
触发方式需要先调用setChanged方法
具体的观察者实现中,update方法同时支持推模型和拉模型。
使用java中的Observable实现上面的程序:
目标对象 :
import java.util.Observable;//具体的老板-李老板public class Li_Boss extends Observable {//是否加班的通知private String isWorkOvertime;public String getIsWorkOvertime{return isWorkOvertime;}//当加班状态发生变化时,要通知程序员来领取新通知public void setIsWorkOvertime(String isWorkOvertime){this.isWorkOvertime = isWorkOvertime;//使用java中的Observable,这句话是必须的。this.setChanged;//拉模式this.notifyObservers;//推模式//this.notifyObservers(isWorkOvertime);}}
观察者对象:
package Observer;import java.util.Observable;import java.util.Observer;public class Programmer implements Observer {private String name;//构造函数和set方法省略//自己的加班状态private String WorkOvertimeState;//接到加班通知,然后更新自己的加班状态@Overridepublic void update(Observable observable, Object o) {//拉模式对应的更新方法WorkOvertimeState = ((Li_Boss)observable).getIsWorkOvertime;System.out.println(name+WorkOvertimeState);//推模式对应的更新方法// WorkOvertimeState = o.toString ;// System.out.println(name+WorkOvertimeState);}}
客户端:
public class Client {public static void main(String[] args){Li_Boss boss = new Li_Boss;Programmer programmer1 = new Programmer(\"小强\");Programmer programmer2 = new Programmer(\"小华\");//将上面两个程序员添加到老板类里的程序员列表中boss.addObserver(programmer1);boss.addObserver(programmer2);boss.setIsWorkOvertime(\"今晚加班\");}}
输出结果:
小华今晚加班小强今晚加班
这样看观察者模式是不是非常简单了。
观察者模式的优点:
实现观察者和目标之间的抽象耦合:只是在抽象层面耦合了。
观察者模式实现了动态联动:一个操作会引起其他相关操作。
观察者支持广播通信:需要注意避免死循环。
观察者模式的缺点:
广播通信会引起不必要的操作。
何时使用观察者模式:
当一个抽象模型有两个方面,一个方面的操作依赖于另一个方面的状态变化时。
更改一个对象时,需要同时联动更改其他对象,但却不知道应该有多少对象需要被改变时。
当一个对象必须通知其他对象,但是又希望他们之间是松散耦合的。
模式变形不久老板又陷入了苦恼,因为上面这种方式每次都会同时通知小华和小强,但是老板想只通知小强而不通知小华,想要区别对待观察者,例如当有用户反应公司的软件产品出现bug时,需要小华和小强去找bug并修复。但当出现不是特别紧急的bug时只需要小华一个人修改就可以了,如果出现了紧急的bug,需要马上修复的,就需要小华和小强同时去修复。该如何使用观察者实现上面的场景呢?
因为经过上面的学习,对于观察者已经了解了,代码不做太多的解释。
在这里可以把Bug当做目标,小强和小华都是观察者,首先定义观察者接口:
//观察者接口public interface BugObserver {//传入被观察的目标对象public void update(BugSubject subject);//观察人员职务public void setName(String name);public String getName;}
目标对象抽象接口:
import java.util.ArrayList;import java.util.List;//目标对象public abstract class BugSubject {//保存注册的观察者protected List<BugObserver> observers = new ArrayList<BugObserver>;public void attach(BugObserver observer){observers.add(observer);}public void detach(BugObserver observer){observers.remove(observer);}//通知具体的观察者程序员public abstract void notifyProgrammer;//获取当前bug级别public abstract int getBugLevel;}
具体的观察者实现
//具体的观察者实现public class Programmer implements BugObserver{private String name;@Overridepublic void setName(String name) {this = name;}@Overridepublic String getName {return this;}@Overridepublic void update(BugSubject subject) {System.out.println(name+\"获取到通知,当前bug级别为\"+subject.getBugLevel);}}
具体的目标实现:
//具体的Bug对象public class Bug extends BugSubject {private int bugLevel = 0;public int getBugLevel{return bugLevel;}public void setBugLevel(int bugLevel){this.bugLevel = bugLevel;this.notifyProgrammer;}public void notifyProgrammer{for(BugObserver programmer:observers){if(this.bugLevel>=1){if(\"小华\".equals(programmer.getName)){programmer.update(this);}}if(this.bugLevel>=2){if(\"小强\".equals(programmer.getName)){programmer.update(this);}}}}}
客户端测试一下:
public class Client {public static void main(String[] args){//创建目标对象Bug bug = new Bug;//创建观察者BugObserver bugObserver1 = new Programmer;bugObserver1.setName(\"小华\");BugObserver bugObserver2 = new Programmer;bugObserver2.setName(\"小强\");//注册观察者bug.attach(bugObserver1);bug.attach(bugObserver2);bug.setBugLevel(1);System.out.println(\"---------------------\");bug.setBugLevel(2);}}
输出结果如下:
小华获取到通知,当前bug级别为1---------------------小华获取到通知,当前bug级别为2小强获取到通知,当前bug级别为2
可以看出,当Bug的级别不同的时候通知的人是不一样的,一级时只通知小华,二级的时候会通知小华和小强。
好了,这就是观察者模式的全部内容了。
声明:本文为作者投稿,版权归作者个人所有。