4. Observer/Event Pattern(观察者模式)—C#/C++/JAVA中的实现与区别

观察者模式是为了实现类与类之间的松耦合,具体作用都是当一个事件发生时,调用所有响应方法。

感觉观察者模式在c#、c++、java语言中的适用情况与意义是不一样的。

一、在c#中:

当一个事情发生了,多个观察者将出现反应。如人物被攻击这件事情发生了,则人物要播放被攻击动画,边上要显示攻击值,被攻击的人物要减少血量等等。对于这样的情况,我们一般的做法是,当某件事情发生时,则生成这些类的对象,逐个调用这些对象中相应的方法,但这些方法需要接收的参数必须是一样的:

class Model{
	void animation(int blood) {
		//具体实现
	}
}

class TextShow{
	void textShow(int blood) {
		//具体实现
	}
}

class DateBase{
	void DeductBlood(int blood) {
		//具体实现
	}
}

public class Main : MonoBehaviour {
    ShowText showText = new ShowText();
    Model model = new Model();
    Data data = new Data();

    public void onClickButton()
    {
        int blood = 100;
        showText.showText(blood);
        model.Anim(blood);
        data.DeductBlood(blood);
    }
}

这样做的不足之处在于类与类之间是紧耦合的,而且一个函数是否需要被调用,由管理该函数的类确定,而不是由管理Event事件的类确定,这就像是订报纸,订不订由用户决定,不由报刊决定,报刊只负责出报纸。

那么我们需要将这些生成对象调用函数抽象成观察者的Action,当Event发生时,就调用Action(要求Action中所有函数的参数应该是一样的)。Event不需要知道Action中具体有哪些函数。

C#语言中,可以使用System.Action与event来实现:

Event维护一个值(报刊)与方法委托链(订阅的用户),当值发生改变时(有新报刊发布),调用委托链中的所有方法:

public class Event
{
    //System.Action<int>表示调用的函数参数为Int型,Actions就是一个委托链。
    public static event System.Action<int> Actions;
    //设定一个值,当更改其值时,会调用set函数,当调用该函数时,则调用Actions中的所有函数。
    //而这个值应该是所有函数的实参。
    private int blood;
    public int Blood
    {
        get { return blood; }
        set
        {
            if (0 == value) return;//如果减少的血为0时,则不需要调用函数
            blood = value;
            Debug.Log("减少的血为" + blood);
            if (Actions != null) Actions(blood);//event不知道Actions中有哪些方法
        }
    }

}

Observers在之前就确定当事件发生时,自己的哪些方法需要被调用,这里有三个观察者:

public class Model {
    public Model()
    {
        Event.Actions += Anim;
    }
    void Anim(int blood)
    {
        Debug.Log("被攻击动画" + blood);
    }
}

public class Data {
    public Data() {
        Event.Actions += DeductBlood;
    }
    void DeductBlood(int blood) {
        Debug.Log("生命值降低"+blood);
    }

}

public class ShowText  {
    public ShowText()
    {
        Event.Actions += showText;
    }
    void showText(int blood)
    {
        Debug.Log("显示数值效果" + blood);
    }
}

用一个测试类,来初始化所有观察者与主题,并通过交互事件来更改主题维护的值:

public class Main : MonoBehaviour {
    // Use this for initialization
    Event _event = new Event();
    ShowText showText = new ShowText();
    Model model = new Model();
    Data data = new Data();

    public void onClickButton()
    {
        //通过Blood来更改blood
        _event.Blood = 100;
    }
}

这时会发现所有观察者调用了自己的函数:

C#因为有委托的概念(System.Action是以委托链实现的),所以任何一对多的模式下都可以应用观察者模式。

二、在java与C++中,没有委托的概念。

在C++中之所以使用观察者模式,是为了抽象观察者的响应事件类型。比如下载一个文件的同时需要显示进度条,一般的做法是(这里用java替代C++写一下):

package pers.soultree.Observer;

public class ObserverPattern {	
	public static void main(String[] args) {
		OperateFiles operateFiles=new OperateFiles();
		operateFiles.downloadFiles();
	}
}

class OperateFiles {
	ProgressBar progressBar =new ProgressBar();
	
	public void downloadFiles() {
		//根据路径寻找到文件夹,获得文件个数,比如说文件个数是10,然后逐个复制
		int size=10;
		for(int i=0; i<size;i++) {
			float progressValue=(float)(i+1)/size;
			progressBar.update(progressValue);
		}		
	}
}

class ProgressBar {
	public void update(float progressValue) {
		System.out.println("更新进度条"+progressValue);
	}
}

这样的话违反了依赖倒置原则(依赖:一般指编译时依赖,而不是运行时依赖,A依赖B表示B存在A才能存在。依赖倒置原则:一般要对接口编程,而不要对实现编程)

因为progressBar是一个实现细节,一般不依赖于实现细节,因为依赖细节是很容易变化的,它将来可能会变成一个数值显示,或是"......"等,所以这里应该将它抽象成一个功能类接口:

interface IProgressDisplay{
	public void update(float progressValue) ;
}

不同的实现细节类型是它的子类:

class ProgressBar implements IProgressDisplay{
	public void update(float progressValue) {
		System.out.println("更新进度条"+progressValue);
	}
}

class ProgressText implements IProgressDisplay{
	public void update(float progressValue) {
		System.out.println("更新文字"+progressValue);
	}
}

在操作文件的类中维护一个进度类的链,需要更新的时候,则调用该链中所有类的Update函数:

List<IProgressDisplay> progressDisplays=new LinkedList<>();
	
	public void downloadFiles() {
		//根据路径寻找到文件夹,获得文件个数,比如说文件个数是10,然后逐个复制
		int size=10;
		for(int i=0; i<size;i++) {
			float progressValue=(float)(i+1)/size;
			for (IProgressDisplay iProgressDisplay : progressDisplays) {
				iProgressDisplay.update(progressValue);
			}
		}		
	}

因为这个链由该类维护,所以还需要写两个方法,来增加与删除子项:

public void addProgress(IProgressDisplay progressDisplay) {
		progressDisplays.add(progressDisplay);
	}
	
	public void removeProgress(IProgressDisplay progressDisplay) {
		progressDisplays.remove(progressDisplay);
	}

最后在测试类中添加需要更新的UI控件:

public class ObserverPattern {	
	public static void main(String[] args) {
		OperateFiles operateFiles=new OperateFiles();
		ProgressBar progressBar=new ProgressBar();
		ProgressText progressText=new ProgressText();
		operateFiles.addProgress(progressBar);
		operateFiles.addProgress(progressText);
		operateFiles.downloadFiles();
	}
}

这样就是面向接口编程,没有违反依赖倒置原则。但是要注意,这里和C#中一个重要的不同是,观察者都实现了同一个功能抽象的接口,而且响应的函数都是重写接口的函数。也就是观察者做出反应的方法实现的功能是同一类的,方法名也是一样的。

三、而在java中,它内置了观察者模式,强调一对多的关系,没有强调功能一致不需要自己写add,remove函数,而且不需要通过List实现:

事件发布者继承Observable类,这个类中会通知所有观察者调用update函数,而且要提供一个getInfo函数,供观察者获取变化的信息:

class OperateFiles extends Observable{
	float progressValue;
	public void downloadFiles() {
		//根据路径寻找到文件夹,获得文件个数,比如说文件个数是10,然后逐个复制
		int size=10;
		for(int i=0; i<size;i++) {
			progressValue =(float)(i+1)/size;
			setChanged();//标记此类已经发生了改变
			notifyObservers();//如果此类发生了改变,则通知所有观察者
		}		
	}
	public float getInfo() {
		return progressValue;
	}
}

所有观察者需要实现Observer接口,需要在构造函数中指定事件(被观察者),而且需要实现接口中的Update方法,并在这个方法中获得被观察者中变化的值(没错,所有观察者只能调用重写父类的update函数,而且参数也是确定的,一般用不到,获取变化的值需要通过之前指定的事件中的方法获取),也就是说,观察者中有一个被观察者,当被观察者更改值时,会调用观察者的update方法,观察者在此方法中通过被观察者的函数获得更改值:

class ProgressBar implements Observer{

	private Observable ob;
	public ProgressBar(Observable o) {
		this.ob=o;
		ob.addObserver(this);//为事件添加自己这个监听者
	}
	
	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		System.out.println("更新进度条"+((OperateFiles) ob).getInfo());
	}
}

测试类:

public class ObserverPattern {	
	public static void main(String[] args) {
		OperateFiles operateFiles=new OperateFiles();
		ProgressBar progressBar=new ProgressBar(operateFiles);
		ProgressText progressText=new ProgressText(operateFiles);
		operateFiles.downloadFiles();
	}
}

这个内置的观察者模式,其实也不需要观察者实现的功能一致,因为都是调用update函数,在这个函数中可以实现不同的功能,而且各个观察者持有一个被观察者,可以通过函数获取被观察者的其他值,感觉更灵活了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值