色谱工作站

最近在重构色谱工作站程序,发现有几个类有重复性的代码(为了方便说明问题,去掉了不相关的代码):

class CAnalysisSample
{
protected:
	CAnalysisSample(CAnalysisSystem* pSystem):m_pSystem(pSystem){}

public:
	inline CAnalysisSystem* GetSystem() { return m_pSystem; }
	inline const CAnalysisSystem* GetSystem() const { return m_pSystem; }

private:
	CAnalysisSystem* m_pSystem;
};
class CAnalysisMethod
{
protected:
	CAnalysisMethod(CAnalysisSystem* pSystem):m_pSystem(pSystem){}

public:
	inline CAnalysisSystem* GetSystem() { return m_pSystem; }
	inline const CAnalysisSystem* GetSystem() const { return m_pSystem; }

private:
	CAnalysisSystem* m_pSystem;
};
class CAnalysisTask
{
protected:
	CAnalysisTask(CAnalysisSystem* pSystem):m_pSystem(pSystem){}

public:
	inline CAnalysisSystem* GetSystem() { return m_pSystem; }
	inline const CAnalysisSystem* GetSystem() const { return m_pSystem; }

private:
	CAnalysisSystem* m_pSystem;
};
class CAnalysisInstrument
{
protected:
	CAnalysisInstrument(CAnalysisSystem* pSystem):m_pSystem(pSystem){}

public:
	inline CAnalysisSystem* GetSystem() { return m_pSystem; }
	inline const CAnalysisSystem* GetSystem() const { return m_pSystem; }

private:
	CAnalysisSystem* m_pSystem;
};


很容易可以看出,对成员变量m_pSystem的声明和初始化以及两个get操作,完全都是一样的;并且这种完全一样的代码,还出现在了四个地方。

重复是可耻的,要遵循DRY。自然而然地,就是考虑将这些操作提取出来,放到一个公共类中(注意,我暂且还没说是公共基类)。

首先,要为这个公共类取一个名字,叫CHasAAnalysisSystemPointer如何?听起来不错,但太明确了。你是有一个系统,但你能保证永远都是系统Pointer?为什么不能是系统ref?抽象一点,或者从业务上理解,叫CHasAAnalysisSystem已足够。那能不能再简单点,叫CHasASystem?这个名字太过简单和抽象了。啥,等等,你说这个名字太过简单和抽象了?那为什么在上面的四个类中,get函数的名称是GetSystem而不是GetAnalysisSystem?好吧,能够提出这个问题的话,说明你有心了。这个问题的解答很简单,想想实际的使用情况即可:是谁在调用GetSystem?是上面的四个类的对象。具体点,以CAnalysisSample为例,大概是这样:

	CAnalysisSample* pSample = ...;
	pSample->GetSystem()...

如果pSample的声明处距离GetSystem的调用处很近,我们就很容易看到pSample是CAnalysisSample类型,有这个上下文为基础,你还会下意识地觉得GetSystem会返回其它System吗?调用CAnalysisSample的GetSystem,却返回FileSystem或FinancialSample?哦,简直难以想象,除非CAnalysisSample也和FileSystem或FinancialSample有所关联。也只有在这种情况下,才有必要将GetSystem更名为GetAnalysisSystem。如果你觉得上面的解释仍然不具说服力,那我就再打个比方:假如你是一名老师,需要统计学生的数学成绩。然而不幸的是,数学成绩表丢了,你因此只好按着学号依次询问学生的成绩。你会怎么问呢?

XXX,你多少分?

XXX,你数学多少分?

这两种问法都没有错。但为节省口水,老师一般会问前者(甚至连“XXX,你”这几个字都省了)。编码当然不需要节省口水,但需要节省篇幅。如果一件事能用两个字说清楚,就不要用三个字,否则多少会加重阅读者的负担。记住,我们捣鼓的是程序代码,而不是写小说,按字收费。

如果pSample的声明处距离GetSystem的调用处不近,那可以考虑重构一下,让它近:

CAnalysisSample* pSample = ...;
...//一大堆代码
pSample->GetSystem()//有上面的一大堆代码相隔,看不到pSample的声明


重构为:

CAnalysisSample* pSample = ...;
CAnalysisSystem* pSystem = pSample->GetSystem();
...//一大堆代码
pSystem->...//虽有上面的一大堆代码相隔,但现在也没关系了

或者重构为:

CAnalysisSample* pSample = ...;
BigBlock(pSample);//提取一大堆代码到一个或几个函数中
pSample->GetSystem()->...//根据需要也可考虑提取到函数UsingAnalysisSystem(pSample->GetSystem())


扯远了,回到名字问题上来。

那为什么说CHasASystem太过简单和抽象了呢?这是因为它和GetSystem不同:GetSystem是一个成员函数名称,被调用时必然会有一个隐含的上下文;而类,在被使用时,隐含的上下文只有一个命名空间。如果CHasASystem和CAnalysisSample是处于一个命名空间,那......说明CAnalysisSample等需要重构(更名为CSample,并且命名空间应当为Analysis)了。如果都是在全局命名空间,那好吧,CHasASystem中的System到底是什么系统呢?有什么隐含的语境或线索吗?什么地方能够得知这个System是AnalysisSystem而不是操作系统、文件系统、财务系统、人事系统...?

好了,名字确定为CHasAAnalysisSystem,下面来实现它。这个是简单的,拷贝上面四个类中任意一个,稍作修改即可:

class CHasAAnalysisSystem
{
protected:
	CHasAAnalysisSystem(CAnalysisSystem* pSystem);

public:
	CAnalysisSystem* GetSystem() { return m_pSystem; }
	const CAnalysisSystem* GetSystem() const { return m_pSystem; }

private:
	CAnalysisSystem* m_pSystem;
};

现在,来考虑一下,是让上面的四个类继承CHasAAnalysisSystem,还是拥有一个CHasAAnalysisSystem对象?

有个设计原则,优先使用组合,其次才是继承。

我们先使用组合看看,更改后的代码大概如下(以CAnalysisSample为例):

class CAnalysisSample
{
protected:
	CAnalysisSample(CAnalysisSystem* pSystem):m_oSystem(pSystem){}

public:
	inline CHasAAnalysisSystem& GetSystem() { return m_oSystem; }
	inline const CHasAAnalysisSystem& GetSystem() const { return m_oSystem; }

private:
	CHasAAnalysisSystem m_oSystem;
};

…… …… …… …… …… ……

你的舌头是不是和上面的省略号一样,无语了?
在使用组合的情况下,无论代码怎么写,都逃不了原来的样式,并且代码量只会比原来多,而不是少,根本没起到重用的目的。

组合无用,就只能继承了。(如果还要向CHasAAnalysisSystem中添加其它的功能,这个时候就可以考虑使用组合了)

继承有三种,public、protected和private。我们需要让CHasAAnalysisSystem的派生类继承接口和实现(get函数)。我们似乎应当理所当然地选择public继承:

class CAnalysisSample : public CHasAAnalysisSystem
{
protected:
	CAnalysisSample(CAnalysisSystem* pSystem):CHasAAnalysisSystem(pSystem){}
};

代码明显少了,确实起到了简化和重用的目的,而且可读性也没有降低。似乎很完美了。
等等,等等。回忆一下继承的相关知识:public表示is-a;private:use-a(用...实现);protected不知道用途。还有一个原则:继承是为了多态,如果不是为了多态,请使用组合。还有句话:继承分为接口继承和实现继承。

我们干了什么?我们需要CHasAAnalysisSystem的接口,也需要CHasAAnalysisSystem的实现(注意,这个和实现继承是两回事),这没有问题(刚好可以用public继承解决,当然,也只能用public继承解决)。但是,如果说CAnalysisSample is a CHasAAnalysisSystem,这个我绝对不能接受(概念上完全不通)。我相信所有看到这篇文章的人也一样不能接受。怎么办?实现上必须使用public,概念上却又不能使用public......

静下心,让我们回到起始点。我们搞出CHasAAnalysisSystem类的目的,是为了简化四个类的代码,实现重用。为此,我们使用了public继承,而在设计概念上,public继承表示is-a关系。可以肯定的有两点:1.CAnalysisSample is not a CHasAAnalysisSystem!2. 我们需要使用CHasAAnalysisSystem简化代码,实现重用。

要解决这个问题,只有一种办法:不使用public继承,但仍然可以将CHasAAnalysisSystem的接口暴露为public。万幸的是,C++提供了这个功能:

class CAnalysisSample : private CHasAAnalysisSystem
{
protected:
    AnalysisSample(CAnalysisSystem* pSystem):CHasAAnalysisSystem(pSystem){}

public:
    using CHasAAnalysisSystem::GetSystem;
};

这个方法不大好。每当向CHasAAnalysisSystem中添加接口函数,也就要相应地向四个类中添加using声明,重用目的大打折扣。

如果广大读者有什么好主意,请不吝相告,再次先表示感谢!

你可能感兴趣的:(色谱工作站)