观察者模式,
观察者模式,
设计模式是对软件编程领域内方法和技巧的总结,能有效的提高代码的可阅读性,复用性,可扩展性和可维护性,是软件工程的基石。设计模式也是计算机专业应届生求职过程中最常见一个类题,单例,观察者模式又是最常见的题目。因此熟练掌握和使用设计模式是每一个软件开发人员必备的技能。
引入:
题目:请用面向对象的编程方式实现以下功能:猫叫了,老鼠被吓跑了,主人被吵醒。(PS:当年我面试时遇到该题不下3次)
当年我学习过C++和Java编程语言,但是没有项目经验,更没有学习过设计模式,我的实现代码是:
//猫类
<span >public class Cat {
private String name;
public Cat(){}
public Cat(String Name){
this.name = Name;
}
public String getName(){
return name;
}
public void setName(String Name){
name = Name;
}
public void Miao(){
System.out.println("Cat "+getName() + " is shout: Miao miao!");
}
}</span>
//老鼠类
<span >public class Mouce {
private String name;
public Mouce(){}
public Mouce(String Name){
this.name = Name;
}
public String getName(){
return this.name;
}
public void setName(String Name){
this.name = Name;
}
public void run(){
System.out.println("Mouce "+getName()+" is running!");
}
}</span>
//主人类
<span >public class Host {
public Host(){
}
public void awake(){
System.out.println("Host is awaking!");
}
}</span>
//测试执行类
<span >public class Main {
public static void main(String[] args) {
Cat cat = new Cat("Jerry");
Mouce mouce = new Mouce("Tom");
Host host = new Host();
cat.Miao();
mouce.run();
host.awake();
}
}</span>
有没有采用面向对象?有。有没有实现猫叫,老鼠跑,主人醒?有。但是仔细看看实体类,Cat只有一个Miao方法,Mouce只有一个run方法,Host只有一个awake方法,而测试执行类创建了三个类的实例再分别调用对应的方法,实际上是打着面向对象的旗帜,干着面向方法的事情。
猫一叫,老鼠被吓跑了,主人被惊醒了,整个过程应该由猫来触发,每次猫的Miao方法被调用后就应该触发老鼠的run方法以及主人的awake方法;猫又如何知道是哪只老鼠,哪个主人呢?如果猫持有老鼠和主人的引用,实现起来就非常简单了。(PS:还要修改测试执行类)
//猫类
<span >public class Cat {
private String name;
private Mouce mouce;
private Host host;
public Cat(){}
public Cat(String Name){
this.name = Name;
}
public String getName(){
return name;
}
public void setName(String Name){
name = Name;
}
public void setMouce(Mouce mouce){
this.mouce = mouce;
}
public void setHost(Host host){
this.host = host;
}
public void Miao(){
System.out.println("Cat "+getName() + " is shout: Miao miao!");
mouce.run();
host.awake();
}
}</span>
//测试执行类
<span >public class Main {
public static void main(String[] args) {
Mouce mouce = new Mouce("Tom");
Host host = new Host();
Cat cat = new Cat("Jerry");
cat.setMouce(mouce);
cat.setHost(host);
cat.Miao();
}
}</span>
测试输出:
如果又有新的要求:老鼠和主人的个数不确定,可以在程序运行中动态的调整,该如何实现?
很显然,直接在cat中定义老鼠和主人的实例引用无法满足程序运行中调整老鼠和主人的需求;但是我们知道集合的大小是可变的,因此我们可以在cat中定义一个集合类型的变量,该集合既可以容纳老鼠实例,既可以容纳主人实例。什么集合既可以容纳老鼠,又可以容纳主人呢,根据面向对象编程的特性之一多态(父类引用指向子类对象),可以很容易实现这一点,因此可以抽象出一个接口/基类Observer,让Mouce和Host都继承自Observer, cat中拥有List<Observer>的实例。
//Mouce,Host的父接口
<span >public interface Observer {
public void Update();
}</span>
//老鼠类
<span >public class Mouce implements Observer {
//其他部分和上面Mouce类一样
@Override
public void Update() {
run();
}
}</span>
//主人类
<span >public class Host implements Observer {
//其他部分和上面Host类一样
@Override
public void Update() {
awake();
}
}</span>
//猫类
<span >import java.util.*;
public class Cat {
private String name;
private List<Observer> Observers;
public Cat(){
this.Observers = new ArrayList<Observer>();
}
public Cat(String Name){
this.name = Name;
this.Observers = new ArrayList<Observer>();
}
public String getName(){
return name;
}
public void setName(String Name){
name = Name;
}
public void Register(Observer o){
if(!Observers.contains(o)){
Observers.add(o);
}
}
public void unRegister(Observer o){
int index =Observers.indexOf(o);
if(index >= 0){
Observers.remove(index);
}
}
public void Miao(){
System.out.println("Cat "+getName() + " is shout: Miao miao!");
for(int i=0;i<Observers.size(); i++){
Observer obs = Observers.get(i);
obs.Update();
}
}
}</span>
//测试执行类
<span >public class Main {
public static void main(String[] args) {
Mouce mouce = new Mouce("Tom");
Mouce mice = new Mouce("Mice");
Host host = new Host();
Cat cat = new Cat("Jerry");
//两只老鼠被吓跑,主人被吵
cat.Register(mouce);
cat.Register(mice);
cat.Register(host);
cat.Miao();
System.out.println("-------");
//猫第二次叫,只吓跑一只老鼠
cat.unRegister(host);
cat.unRegister(mice);
cat.Miao();
}
}</span>
测试执行结果:
到此为止,一个简单的观察者小实例就完成了,猫是一个被观察者,内部定义一个观察者集合实例,并提供观察者订阅和取消订阅观察的方法(cat类内部的register和unregister)。被观察者行为或状态发生改变时通知观察者(cat.Miao方法内步遍历集合,调用每个观察者的update方法)。
数据传递:
在很多情况下,观察者需要获取被观察者的数据变化,观察者取得数据有两种方式:推方式和拉方式,换句话说就是被动接收数据和主动提取数据。
推方式:在被观察者通知观察者时,将需要处理的数据按照一定的方式传递给观察者,观察者接收后在进行处理。
拉方式:在被观察者通知观察者时,将被观察者自身的引用传递给观察者,观察者通过被观察者暴露的方法提取和处理数据。
下面对上述类进行适当修改,在一个例子中将综合使用推方式和拉方式。
//Mouce,Host的父接口
<span >public interface Observer {
public void Update(Cat c, String name);
}</span>
//老鼠类
<span >public class Mouce implements Observer {
//其他部分和上面Mouce类一样
@Override
public void Update(Cat c, String name) {
<span >//这里使用推方式,接收被观察者传递来的数据name
</span> System.out.println(name);
run();
}
}</span>
//主人类
<span >public class Host implements Observer {
//其他部分和上面Host类一样
@Override
public void Update(Cat c, String name) {
<span >//这里使用拉方式,通过被观察者的引用来获取需要处理的数据
</span> System.out.println(c.getName());
awake();
}
}</span>
老鼠类使用了推方式来接收数据,主人类使用了拉方式来接收数据。虽然传递的数据是简单的String类型,但足以说明推方式和拉方式的区别。 在实际应用中,如果需要处理的数据比较复杂,可能会将要传递的数据进行封装,封装成一个新的业务对象来传递,也可能会结合使用推方式和拉方式。
PS:本文简单的介绍了观察者模式的结构:1)被观察者拥有观察者关心的内容;2)所有观察者拥有相同的父接口或继承自相同的父类;3)被观察者拥有一个集合类型的实例变量,用以保存注册的观察者; 4)被观察者提供注册和取消注册的方法,供外部类订立订阅或取消订阅;5)被观察者的行为或状态发生改变时能通知所有观察者; 6)被观察者通知观察者数据有两种方式:推方式和拉方式。
本文是自己对观察者模式学习的总结,方便自己回顾知识点,同时也希望能给广大初学者带来帮助。观察者模式的内容还有其他方面,如方法的异步调用,有兴趣的童鞋不妨自己深入研究。
评论暂时关闭