代码改变世界

几种特殊的类型设计。

2009-07-06 20:11  Ivony...  阅读(2342)  评论(16编辑  收藏  举报

这是“九种不够面向对象的对象“的在实际项目中的合理运用 一文的补充,显然继续在无聊的问题上纠缠是一件没有意义的事情。所以不妨另外发起一个话题,作为延伸。

有人批评我胡乱发明概念,这一点我承认,例如“不饱满的对象”。不够饱满这个词是我从中文中信手拈来的,主要是用来代替“不够面向对象”的说法。

作为一个对象,它有些什么呢?我觉得一个对像归纳起来,主要是三方面:标识、数据、行为。也就是类型、属性、方法,或者说我是什么、我有什么、我能什么。如果一个对象缺失了其中的一方面,我把它叫做“不饱满”的对象(或者大家有什么更好的建议)。

 

1、缺失行为的对象。

缺失行为,换一种说法就是什么都不能的对象,如同我在上一篇文章中举的石头的例子一样,没有任何行为的对象是可以合理存在的。当然,string的那个例子可能不够好,因为string或者说int等等纯数据类型,不会出现在OOD中。但这种缺失行为的对象也并非罕见,常见于定义数据规范的对象:如ConnectionString、Uri、IPAddress,实际领域中,OA中的Document、Enterprise中的Order,用户体系中Account、IIdentity、IPricipal这样的对象,WebApplication中的Session。如果你仔细去找,这类型的对象其实非常多,在大多数领域中都有可能出现。

当然,上面局的例子的每一种,某些东西的狂热拥护者都能举出反面例子,就像说明string也有方法一样。但,方法不等于行为,正如同不会有人将属性视为行为一样,但属性是方法来实现的。

给connectionString加上OpenConnection的方法或是给Account加上Logout的方法,抑或者Order的Seal或Sign方法,都不能说是一个好的Design,在我看来就是画蛇添足。

 

2、缺失数据的对象。

缺失数据,换一种说法就是什么都没有的对象。缺失数据不一定说是没有成员变量,而是外部不可见,不可读也不可写。当然也不排除真的没有任何成员变量的情况,但这个不是问题的重点。正如同上一篇文章中某个回复说的一样:“如果我们真的只需要一把刀,那么就造一把刀就可以了”。这样的对象在OOD中也是存在的,比如说监听事件并主动记录的Logger,审核权限的Auditor,这些对象都不需要自己有什么数据,别人也不关心他们有什么,只需要知道它们能干什么就行了。

 

3、行为和数据都没有的对象。

换一种说法什么都没有,什么都不能的对象,这种情况比较极端,但只要类型有意义,即使行为和数据都没有,这个类型本身就能构成其存在的意义。除了标记接口,这样的对象极为罕见,举一个可能并不是很恰当的场景:

有三种货物:猪肉、鱼肉和鸡肉,然后他们都是一份份的,每一份猪肉、每一份鱼肉以及鸡肉都是一样的,但猪肉、鱼肉鸡肉之间并不相同。

我们有几个仓库,然后仓库有这么一个行为,把一份货物放进去,但每种货物放进去的时候都要引发不同的行为。

当然,我们可以写三个方法,但更好的做法是写同一个方法的三个重载。当然直到这里,必须设计成三种类型看起来仍然没什么必要,但是如果我们再增添种种操作,例如仓库之间可以调配货物,根据货物选择加工员,设计为三种类型就能利用静态联编等手段大大的简化代码。

更进一步,在以后可能会提出需求要跟踪货物在仓库之间调配的过程并计算中间的不合理损耗,这样的情形下,一开始设计成三种类型就会很有帮助,尽管在一开始看起来这是一件很傻的事情。换言之,这样的对象在设计中存在的意义可能更多的是为以后重构做准备。

 

在设计中,可能更常见的是不关心对象的行为和数据。例如容器对象不关心里面储存的容器项有什么行为和数据,序列化器不关心被序列化的对象有什么行为和数据。我们做一个模块的设计,这个模块就可能有不关心数据和行为的对象。

 

4、缺失标识。

缺乏标识,即类型无意义的类,不应出现在在设计中,在编程中可以灵活加以运用。

常见的有,Math、HttpUtility为代表的工具类(函数集),只包含常量的常量集类。或许你可以说HttpUtility是“关于http的工具箱”,但Math却很难去说这是什么东西,数学手册?关于数学的工具箱?还是计算器?

广义一点,用于辅助实现的类,也可以认为是类型没有意义的,如Wrapper、Builder、Adapter、Factory等,因为它们本身并不是一个什么东西,而是为了某个目的而创造出来的。或者说属于编程技巧而不是设计技巧。

行为、数据都没有,类型也没有意义的类可以认为不存在。

 

继续,谈谈几种看起来不合常理的设计:

5、派生类屏蔽基类方法。

应该说这种情况不会常见,派生类与基类的关系是抽象的关系,基类必须比派生类更抽象。一般而言,具体的对象应该比抽象的对象拥有更多的功能。但在某些特殊情况下,多出来的却是限制,如同瘫痪的猫的例子一样。因为特殊情况并不多见,遇到这种情况的时候,仔细思考抽象的基类被屏蔽的方法是不是一个普适的行为,例如所有宠物都会拿耗子显然是荒谬的。如果被屏蔽的方法并不是一个普适的行为(例如没有或者很少直接用基类对象来调用这个方法),考虑将其删除或者修改为protected。但在任何时候都应当记住,派生类不一定要比基类多一些功能,基类必须比派生类抽象是继承的原则,不能因为派生类功能少基类功能多而去颠倒继承方向,继承的目的不在于代码重用。具体的例子其实很多,比如说HttpWebRequest等。

 

6、很少,甚至没有代码的派生类。

这是一个很有意思的话题,如同刚刚说的没有数据和行为的对象一样,一个对象只要类型有意义在很多时候就是存在的充分条件。没有代码的派生类意味着不会给基类增加任何功能。因为派生类与基类的关系是具体和抽象的关系,只要类型携带的信息更具体了,即使不增加任何东西也是合理的。具体的例子,比如ASP.NET中的PlaceHolder。因为它所需要具备的功能(充当其他控件的容器)已经被基类Control完全的实现了。所以它就不需要任何代码了。但它比Control具体却是很显然的。

 

总结,OOD不是三言两语,几个原则,几个案例就能说清楚的事情,理解,以及实践加上漫长的过程才能从OOP的领域迈向OOD的境界。这篇以及上篇文章主要我所希望表达的,并不是想说OOD是什么玄之又玄,存乎一心的东西。其实想说的东西也很老套,任何一个规则都有其背景和场景,千万不要认为所有的规则都是普适的,搞清楚背后的故事很重要。

你有一个良好的编程习惯,懂得灵活运用各种设计模式,这只是说明你具有一定的OOP水平了而已。正如同上面所说的,一个类(对象)只要其类型是有意义的,其存在就有意义,OOD要求从类型而不是其成员入手。这是一种抽象的艺术,尽管我很希望能够找到一句简单的话让大家可以一下子茅塞顿开从OOP一下跳到OOD的层次。但很可惜,我没找到。。。。。