关于工作和读书的笔记
[原创文章欢迎转载,但请保留作者信息]Justin 于 2009-12-20
大师说了,C++的设计还是有缺陷的:它无法把接口(interface)的设计和实现(implementation)的设计完全划分开来。比如说在一个类的(接口)声明当中,总是或多或少的会泄漏一些实现上的细节,虽然这样做与接口的设计并没有太多联系。有同学说应该多放些代码一起炒冷饭,是个好主意,下面是书中的修改版本,大致是一样的。
这些实现上的细节往往需要引用其他头文件中相关对象的定义(比如说下面的代码),从而产生了对这些头文件的(在编译时的)依赖。因此每次这些文件中的某个有变化时,依赖它的所有文件都需要重新编译。
【注意】这里貌似逻辑不是很顺:就算没有那些私有成员的声明,接口函数的返回值如果是string或是BClass等类型,不还是一样需要依赖引用其他头文件吗?其实这是两种不一样的情况,实现和接口。前面说的实现细节的泄漏是会导致编译依赖的,因为编译器需要了解这些类型对象的大小进而为其分配内存空间;但是接口,比如说函数的返回值或是参数表中的参数,就不需要编译器去考虑分配内存的问题,因此也就没有所谓的编译依赖了。问题知道了,那么解决办法呢,大师提出“骨肉分离法”(嗯……其实是我的杜撰@#¥%):将声明(declaration)和定义(definition)分开。
呃……下面的比喻,最好吃完饭再继续。如果说接口是一个类的骨架,那么实现就是他的血肉;如果说声明让你摸到了骨头,那么定义应该就是血和肉生长的地方。根据骨肉分离法,对于一个AClass类,第一步先把血肉(定义/实现)剥离开,只留下骨架。然后找个盒子(新建一个类,比如说AClassImpl),把血肉放进去。接下来还有一步,在骨头盒子里(原AClass类)加一条绳子连着血肉盒子(一个指向AClassImpl的指针),这样才不至于让骨肉真正的分离,只要找到了骨头盒子,就一定能找到血肉盒子,然后对于这个“可怜”的AClass来说,它的全部“零件”都是完整的,啥也没丢,但是做到了骨肉分离。
也做到了没有编译依赖。因为对于AClass的用户来说,他们面对的将是一个没有定义的类,这个类的后继改动,只要不涉及接口的改动,都不会导致用户程序的重新编译。看到这里想想工作时看到的代码,原来前辈也有看过啊……对比前面的例程,给一个“骨肉分离”了的版本吧:
前面的文字是自己的理解,而大师的真言是这样的:
如果觉得骨肉分离太残忍,大师还有另外一个工具:工厂(factory)。第二种方法中,抽象类/接口类提供了所有接口的纯虚函数形式:会有该类的子类去实现这些接口。与此同时,在抽象类/接口类中还会有一个静态(static)的工厂函数(比如create()/produce()/factory()……),这个函数实际上起到了构造函数的作用,它“制造”出子类对象来完成真正的任务,同时返回这个对象的指针(通常是智能指针如shared_ptr)。凭借这个返回的指针就可以进行正常的操作,同时不会有编译依赖的担心。一个简陋的代码见下:
Copyright @ Justin.H Powered by: .Text and ASP.NET Theme by: .NET Monster