关于c++的封装性
在头文件中定义一个类, 比如说CAlpha。然后用mfc把整个类输出来(在DLL中),供别人调用这个类。
听起来,这个解决方案很不错,模块化,实现部分进行了封装。
但我们实际来看这个例子:
//my_header.h
class CAlpha
{
private:
int m_nDegree;
public:
int GetMyDegree( );
void SetMyDegree( int nNewDegree );
};
当然要加上一些修饰符,才能编译成输出的类。 这里省略。
而当调用者要调用这个类,他(她)需要一个头文件,还需要dll, lib文件。dll、lib文件我就不讨论了。
关键在于调用者使用的头文件。 通常,如果调用者要用 "CAlpha temp_alpha;" 或者"CAlpha* pNewAlpha = new CAlpha( )"的时候,他必须得到这样的头文件。
//my_header.h
class CAlpha
{
private:
int m_nDegree;
public:
int GetMyDegree( );
void SetMyDegree( int nNewDegree );
};
这好象很平常!
但这恰恰是不平常的地方!
调用者看到了private里面的具体实现的细节!——这极大破坏了封装性。破坏了一个基本的OO原则。
如果调用者用这样一个头文件来new CAlpha行不行呢?
class CAlpha
{
public:
int GetMyDegree( );
void SetMyDegree( int nNewDegree );
};
不行!因为这个类与原始的实现的类的内存布局不一样,这样new一下,必然导致错误。
我不是OO基本教义派,破坏原则本身并不是非常可怕的。 但除了原则以外,可怕之处在于其他的现实危害。
假设这个类的实现者重新改变了他的实现,比如他删除了m_nDegree,但接口未变,“GetMyDegree()” and "SetMyDegree"都没有改变。 这个时候,他需要向调用者重新发送一个新的头文件吗?
我的答案是:如果调用者可以用new 这个对象的话,他必须重新发送头文件。否则调用者的调用在内存方面几乎必然是出错的。 (调用者必须知道这个类的内存布局,否则他无法new)。
而如果类的实现者在保持接口不变而仅仅是实现方式变化的情况下,还要给调用者提供新的头文件。这个成本就太高了。这个时候,还不如把源代码发送给调用者,让他自己去联编就可以了。
这显然是不经济的。
这个时候,如果想要求不再发送新的头文件给调用者,那就只有一个办法:禁止调用者直接new 这个对象。这个时候,就必须使用类工厂的模式。
在头文件中,增加这样一个函数 CAlpha* CreateNewAlpha( ); 在这个函数中:
这样实现 CAlpha* CreateNewAlpha( )
{
return new CAlpha( );
}
把CreateNewAlpha输出给调用者,这样就可以了。这个时候,调用者所看到的头文件是:
class CAlpha
{
public:
int GetMyDegree( );
void SetMyDegree( int nNewDegree );
};
CAlpha* CreateNewAlpha( );
这样才行。
这种看起来古怪的解决方法,都是因为c++的封装性有问题才这样做的。
看来c++要做的事情还是很多的。
这次把上次没写完的东西写了一下。还没有好好修改。先传上去再说。