本节讨论下数据流转方式。
一般情况下,所谓对象,都有状态信息的,这些状态,其实就是对象的数据。如果一个对象完全不与其他对象发生关系,基本上是很少见的,一般的函数都需要参数。今天这里不讨论关于状态如何变化的问题。
那么两个对象之间如何发送关系,对象A需要对象B的数据,有哪些方法可以完成?这些都是在进行设计的时候,需要了解的。先看例子:
class BTDownloader
{
double rate,speed,downloaded;
....
};
一个BT下载者,内部保存了下载速率,下载速度,已下载的字节数。如何进行BT下载,就放一边了。现在,需要将BT下载的状态打印出来,该怎么做?
很显然的,一个对象的数据,被对象外的函数需要,有且仅有两个方法:主动推,被动拉,除此之外,别无它法。
主动推,就是当对象数据变化的时候,它需要向接受者发送。被动拉,则是由需要数据的函数发起要数据的动作。
一般的情况,都是被动拉是情况。如下:
class BTDownloader
{
double rate,speed,downloaded;
public:
double getRate(){ return rate;}
double getSpeed() { return speed;}
double getDownloaded() { return downloaded;}
....
};
要用到被动拉的情况,则需要数据的函数必须拥有该对象才能使用该对象的公共函数,如下:
void xxx()
{
//某处定义了 BTDownloader d
d->getRate();
}
也就说,被动拉的方式,对于对象来说,是最简单方式,对于BTDownloader类来说,它完全不依赖另外的类,它只需要专心致志的做好自己的工作就行,至于谁向它要数据,它不关心,何时要数据,它不关心,如何表现这些数据,它更不关心。这些,都是它的客户需要思考的。如果要将下载状态显示在窗口界面里,那需要用到一个定时器,定时的询问数据,然后显示出来。
这里,对象的客户工作量稍微大点,如何将对象进行传递,在哪里创建该对象,在哪里初始化对象,在哪里增加下载任务而又在哪里查询状态,这些事情都是需要考虑的。主要的是各部分需要对象的时候,怎么能得到对象的问题。
再说说另一个情况,主动推。
主动推,是在数据有变化的时候,向接受者推数据,这里,对象就要依赖一个接受者类了。接受者接口类到底该如何设计,其实也没有定法,简单的,如下:
struct CReciver
{
public:
void rate(double r)
{ printf("/nnew rate %f",r);}
void speed(double s)
{printf....}
void downlaoded(double d)
{ printf ....}
};
这个类怎么用?很简单:
BTDownlaoder::OnDownlaod(...)
{
//计算得到rate,speed,downloaded,现在需要把数据推出去
//必须有 CReciver* reciver的一个指针
reciver->rate( newrate);
reciver->speed( newspeed);
reciver->downloaded( newdownloaded);
}
如此一来,BTDownloader就要保存接受者的指针.为什么要保存接受者指针呢?不保存不可以吗?不保存,那数据变化的时候,怎么推出去呢?
既然要保存这些指针,那就要考虑这些指针是怎么来的。显然不是自己new出来的(为什么),而必须是对象的客户new出来后作为参数传递进来的。如此就有
class BTDownlaoder
{...
void add_reciver(CReciver* r)
{
m_reciver=r;
}
};
具体到底是用单独的函数添加,还是在构造函数里传递,都无关紧要了。考虑下add_reciver是怎么做的?
void xxxx(...)
{
//得到BTDownloader* d指针
//得到CReciver* c指针
d->add_reciver(c);
}
一般的做法,当然是把这两个指针当作类成员变量来处理了。不过这样设计合适不合适再说了。
类似这样的设计好不好?BTDownloader不仅依赖一个CReciver类,而且要暴露一个函数来添加reciver.BTDownloader的客户不仅要熟悉BTDownloader,还要熟悉CReciver,还要为两个对象的生存周期考虑,还要考虑在不同的地方如何得到这两个类实例,我觉得实在有点麻烦。
如果对下载的状态的表现形式有变化,比如,需要在一个列表窗口中显示出来变化情况,那CReciver类就要修改了。嗯,修改方式如下:
class CReciver
{
int method;
HWND listHwn;
public:
CReicer(int method);
void setHwnd(h){ listHwnd=h}
void rate(double r)
{
if(method==1)
printf("/nnew rate %f",r);
else if(method == 2)
{
postmessage(listHwnd,....);
}
}
void speed(double s)
{
if(method == 1)
printf...
else if ( method == 2)
{
postmessage(listHwnd,....);
}
}
};
OK,万事大吉,客户在创建CReciver的时候,会根据情况设置不同的method,如果method==2,再设置一个listbox的hwnd就可以了。新的修改又来了,这次状态信息不显示在listbox中,而显示在一个进度条对话框中。如何修改?
没关系,继续method=3,需要的参数继续类似listbox的hwnd一样去set一下就ok了。
显然不是一个好的设计,还记得前几节的描述吗?当时说用ifelse不好,现在看到了不好的结果了。怎么做?有现成的做法。
我想,有耐心看到这里的读者,都是很有修养的,说来说去,不就是想说观察者模式么?嗯,我也觉多太啰嗦了,不说了。