2009年5月14日星期四

模板类的定义和紧耦合的两个类如果解决声明问题

前天晚上看了下数据结构的C++描述版,然后就动手写了个链表的类,用到了类模板,我是这样定义的,类ChainNode和Chain分别表示链表 的节点和链表本身,两个类的声明放在名为chain.h的头文件中,而又加了一个名为chain.cpp的源文件来实现这两个类中的方法。因为是最基本的 链表操作,所以很快就写完了,然后在main函数里面加了几天测试代码,编译链接。这时候问题来了,两个类里面的方法都链接报错,说是找不到实现,这让我 很奇怪,因为我明明在chain.cpp里面都有实现,为什么找不到,一开始我考虑到是否是因为在我实现的时候语法不对,因为我知道使用模板类和普通类不 同,有很多要注意的地方,例如实现方法Search时,不能写成:

bool Chain::Search(int i, T& x) {}

而是要写成:

template

bool Chain::Search(int i, T& x) {}

于 是把C++ primer又翻出来,看了类模板,确保实现的时候语法是正确的,但是看来看去也没有发现什么问题,编译的时候总是报错找不到实现。于是我修改了 chain.cpp,故意在里面加入了明显错误的代码,但是即使这样,编译chain.cpp仍然不报错,于是我怀疑难道是chain.cpp没有被加入 到工程以至于没有被编译?又查了半天,排除了这个可能,然后我就郁闷了,到底是怎么回事呢,没道理啊。后来Google了一把,总算找到了原因,那篇文章 是这样解释的:

因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和 实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接 程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。

在C++编程思想里的第15章,模板与容器类,有这么一段解释:

甚至是在定义非内联函数时,模板的头文件中也会放置所有的声明和定义。这似乎违背了通常的头文件规则:“不要在分配存储空间前放置任何东西”,这条规则是为 了防止在连接时的多重定义错误。但模板定义很特殊。由template<>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处 于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板 声明和定义。
有时,也可能为了满足特殊的需要(例如,强制模板实例仅存在于一简单的 Windows DLL文件中)而要在一个独立的C P P文件中放置模板的定义。大多数编译器有一些机制允许这么做,那么我们就必须检查我们特定的编译器说明文档以便使用它。

看到这些解释,我就明白了,把Chain的定义放在chain.cpp里面是没用的,确实会导致链接的时候找不到定义。

今 天上午看C++编程思想,在第三章看到一段话,让我想起以前遇到的一个问题,有一次,碰到了这种情况,有两个类:ClassA和 ClassB,ClassA中有一个成员变量是ClassB,同样,ClassB中也有个成员变量是ClassB。ClassA和ClassB在同一头文 件中声明,这时问题就来了,如果先声明ClassA,那么就会报错,说ClassB找不到,而如果先声明ClassB,那么也会报错,说ClassA找不 到,情况如下:

class ClassA
{
public:
ClassA();
~ClassA();

private:
ClassB m_classb;
int m_A;
};

class ClassB
{
public:
ClassB();
~ClassB();

private:
ClassA m_classa;
int m_B;
};

这 种情况下,如何解决问题?在第三章的某处,先是说明了这么做出错的原因:因为如果试图传递整个对象,那么编译器就必须知道这个对象的全部完全的定义以确定 它的大小以及如何传递它,如果编译器找不到给对象的完整定义,那么就会失败。上面正是这种情况,因为如论是先声明ClassA或者ClassB或者两者两 者都声明,但是因为都没用完整的定义,所以编译器就报错了。那么知道了问题的原因,如何解决?可以稍微修改一下,把成员变量改成指针:ClassB* m_pClassb;和ClassA* m_pClassa; 然后在前面加上声明class ClassA;class ClassB;这样就可以了。为什么?因为编译器知道如何传递一个地址,地址的大小是固定的,而不用管地址里的保存的对象类型如何,它的大小有多大。

当然从设计的角度来讲,这ClassA和ClassB这种设计肯定是很不好的,因为耦合度太高了。

2009年5月10日星期日

UML的依赖,关联,聚合和组合

今天复习了下UML,因为周一和印度人谈话时可能要问这个。拿过老谭的UML面向对象建模与设计翻了翻,看到讲聚合和组合的章节,这个东东一直没搞清楚,于是上Google搜索了一下。在UML中,类之间的关系可以分为:依赖,关联,聚合和组合四类。这四类关系其实都可以看作是某种意义上的关联,从依赖到组合,它们的关联强度由弱到强。

依赖是类与类之间最弱的关联,现实中的例子是,类A的某个方法的参数中使用了类B,这样就可以说类A依赖类B。而关联比依赖更强,例如类A中有一个属性,或者说一个成员变量是类B,那么就说类A关联类B。(但网上有的帖子把这种情况不看作是关联,在这些帖子中关联和依赖的强度是一样的,后一种情况应该算是聚合或者组合。)

聚合,用空心菱形表示,指的是整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构。从而找出一些组成类,该整体类和组成类之间就形成了聚合关系。例如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。需求描述中“包含”、“组成”、“分为…部分”等词常意味着聚合关系。

组合,用实心菱形表示,
也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。部分对象与整体对象之间具有共生死的关系。

区别聚合和组合的关键就是整体和部分的生命周期,如果整体和部分是同生共死,一荣俱荣,一损俱损的,那么这就是组合,否则就是聚合。这可以通过下面的代码来对比:
//聚合
class A
{
public:
A(B
* pb)
{
m_pb
= pb;
}
...
private:
B
* m_b;
};

//组合
class A
{
public:
A()
{
}
...
private:
B m_b;
}


对于聚合来说,它不负责组成部分的创建与删除,也就是说A只使用B,但B的生死A不管。而组合就不同了,
A要全权负责B的生老病死。所以组合的强度比聚合要大。聚合关系是“has-a”关系,组合关系是“contains-a”关系;
聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生
存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就删除了
代表部分事物的对象。

2009年5月8日星期五

microkernel初步

在讲微核模式和反射模式前,POSA首先提到了adaptable system,我姑且把它翻译成可适配系统吧,这个系统的特点是变动大,经常需要在已有的功能或者模块上添加新的功能,或是应付操作系统改变,第三方库变 动等问题。为了实现这样的系统,提出了微核模式和反射模式。

微核模式将系统的核心功能与外部功能和用户要求的功能分离开,同时它也作为一个容器或者socket,包容那些外部的扩展,并让那些外部的扩展能实现协作。微核典型的应用场景是OS和大型IDE的设计。

微核模式定义了5种组件:internal servers,external servers,adapters,clients和microkernel。

microkernel提供系统核心的底层的功能,为各个组件提供通信机制,对系统的依赖进行封装(例如驱动,其它的组件需要使用硬件服务,这是硬件依赖。这一部分工作也可以丢到下面介绍的internal server里去做。),管理资源。

internal servers是对microkernel的功能扩展,所以也称为子系统,microkernel通过向其发送服务请求来调用其相应的功 能,internal server主要是封装一些硬件和软件的依赖,例如某类硬件的驱动。书中特别提到internal server只能被microkenel访问,我想这也是internal server名字的由来吧。

external servers实现什么功能,看了半天也没看懂,貌似external server是向外提供编程接口的,client可以用external server提供的接口来访问microkernel提供的功能。不过这一段没看懂,到底是不是这样还不知道。(从后面的内容可知,external server自己也是实现了某些功能的,而不是什么功能都没有,只是作为一个桥梁去调用microkernel的功能。)

client这一段也没看太明白,“A client is an application that is associated with exactly one external server.”, 这句话让我很费解,为什么只能跟一个特定的external server相关联呢?一个client就不能使用其它的external server吗?如果不行,那会不会导致client太多了。在提到client和external server时顺带提了adapter模式,这是可以理解的,因为使用adapter使得client和external server之间的耦合更松,更有利于应付变化。

下图是微核模式里各个组件类的类图,从图中可以看出各个类之间的关系,internal server是只能被microkernel访问的,而client是通过adapter和external server来访问使用microkernel的功能。

2009年5月7日星期四

PAC初步和Eclipse的visual editor插件

PAC模式感到很陌生,以前从没接触过,所以先在网上搜索了一番,资料也比较少,看了下Wikipedia里面的介绍,虽然说是和MVC比较相似,而且POSA也是将其和MVC放在一起介绍的,但是我觉得他是层模式和MVC模式的混合更合适。PAC从整体来看,它也是一个层结构,具体说应该是一个树状的层结构,树中的每一个节点,称为一个agent。这个层次树一般分为上层agent,中层agent和低层agent,如图一所示:

从图一可以清晰地看出树状的层结构,顶层agent提供系统的核心功能,中层和低层的agent都依赖它。低层agent和用户打交道,接收用户的输入和操作。中层agent联接上层和低层agent(这三层的功能和关系,我没看太懂,这个看以后会有什么感悟吧)。上下层相应的agent是父子关系,每个 agent都依赖它所有的上层agent。每个agent实现特定的功能,而且又由三个组件构成:Presentation,Abstraction和 Control。从这个意义上说,它和MVC确实有相似之处。presentation组件提供agent的可视行为,相当于 view,abstraction维护agent的数据,并提供对数据的操作接口。control组件联接presentation和 abstraction,并与其它的agent通信。

上午还捣鼓了一下Eclipse的GUI开发,因为王强问了一下,以前装了个visaul editor插件,一直没有用过,上午就趁机想试试,用Visual Class创建了一个类后,代码和frame都显示出来了,但是当我想向frame里添加控件时捣鼓了半天都找不到控件的窗口,郁闷,google了一把,找到一个帖子,终于把控件窗口弄出来了,为了避免忘记,现引用如下:
这个“控件面板”或“部件窗口”或“拖板”(没有统一称呼)在VE里称为 Palette 。如下打开:
window -> customize perspective -> shortcut标签 -> submenus选择 show view -> 单击General在右边 palette 打勾。
确定后 回到 window -> show view -> palette 。

2009年5月1日星期五

终于可以自由地在opera上用tor了

今天下午在网上无所事事,突然想起了洋葱头,我一直是用Firefox和Tor搭配使用的,因为FF有专门针对洋葱头的插件,这方面opera就差一些了。但是我很喜欢opera,所以在公司里一般的网上浏览都是用opera,而使用Google的服务,例如google calendar,docs,blog等等,就要用FF了,因为公司把Google的服务屏蔽了。

今天上网无所事事就又想到这个问题,上网搜了一下,opera是可以使用Tor的,打开Tor后,再设置opera的代理为:127.0.0.1,端口是8118,设置好后就可以用Tor了。不过这样还是有点不方便,因为要用的时候得到preference去设置代理,不用了还是要再设置一遍。

后来想能不能在opera的菜单或者工具栏里面添加按钮,实现enable和disable代理那就方便了。秉承知之为知之,不知google知的原则,搜索一把,网上有些帖子说明怎么在opera里添加菜单和按钮,考虑了一下,加菜单太麻烦,还有修改opera默认目录里的ini文件,搞不定啊。后来终于发现个好帖子,说有个网站:http://nontroppo.org/tools/buttonmaker,可以用来制作opera使用的按钮,填写几项就好了,比较重要的项是:Choose the button's action,就是点击这个按钮要执行的动作,我从commbo里面找了一下,有两个对得上号的操作:Enable proxy servers和Disable proxy servers。将相应的action选好,然后呢再设置一下button's title,这一项不是必须的,可填可不填。还有个选项是设置按钮的icon,我选了两个:一个是“Reload”,一个“stop”。这样设置完后,大功告成了,点击create button就会有相应的按钮被创建了,创建后会将你创建的按钮显示出来,点击这个按钮,opera就会把这个按钮添加到浏览器里了,在菜单工具->外观->按钮->我的按钮里面找到。

到了这一步基本就搞定了,然后在工具栏上点击右键,选择自定义,然后在我的按钮里找到我们加入的按钮,将按钮拖到工具栏上就行了。