通过面向对象的思路,我们可以把任何事物都看成一个对象然后单独处理,从理想的角度,任何一个微小的单元都可以以一个对象的形式表示。比如我们可以用如下代码表示一个人以及它的姓名:
1.不分离姓名
class 人 {
public string 姓名;
}
但是这个世界是很复杂的,姓名本身是由姓和名组成的。如果我们需要需要单独处理姓和名时,要怎么办?于是我们可以这么拆分:
2.直接分离姓名
class 人 {
public string 姓;
public string 名;
}
但我们会碰到这个问题:姓和名本身是一个整体,处理姓名的逻辑不应该放在人这个类里面,而应该单独提取出来。于是代码改为:
3. 提取姓名为单独一个类,然后单独处理
class 人 {
public 姓名 姓名;
}
class 姓名 {
public string 姓;
public string 名;
}
这是一个非常理想的状态:任何事物都被表示成了一个独立的对象。但其实作者都是懒的,没人会愿意为姓名单独写一个类然后单独处理它。除非逻辑非常复杂需要单独处理时,才会选择把它提取出来。
同样一个人,把它写成面向对象的代码之后,却有3种不同的写法(1. 不分离姓名。 2. 直接分离姓名。 3.提取姓名为单独一个类,然后单独处理)。而决定我们用哪个写法的是最需求。需求是软件开发中最不稳定的因素,因此面向对象的代码经常需要重构和重写。这就是面向对象中的一个设计陷阱。
再来看一个例子:
abstract class 鱼 {private int 价格;
private int 口感;public int get价格(){ return 价格; }
public int get口感(){ return 口感; }
public abstract string get名字();
}
class 鲤鱼: 鱼 {
public override string get名字() {
return “鲤鱼”;
}
}
class 桂鱼: 鱼 {
public override string get名字() {
return “桂鱼”;
}
}
这是一个最普通的面向对象的代码了。从上面看似乎完美到没有任何问题:通过鱼这个类以及它的多态特性,我们可以很轻松地处理所有鱼。
但是这时加了一个需求,我们需要处理金鱼,于是写了这么一行代码:
class 金鱼: 鱼 {
public override string get名字() {
return “金鱼”;
}
}
看上去依然完美,但问题是:金鱼是不能吃的,获取它的口感是没有任何意义的!但是通过继承,我们的金鱼也是可以有口感的。同时,还浪费了一个内存用来存储这个没有意义的口感字段。
现实中,很多人都忽视了这个问题:反正没意义,这个函数不要调用就行了。对的,但如果一个继承了一个父类之后,5个函数是有意义的,20个函数是没有意义的,这时我想作者该犯洁癖了吧。问题主要是在于继承一个类之后,有一些成员是不必要或者无意的,有2个改法:
1. 让金鱼不继承于鱼:这不科学,金鱼本来就是鱼,除了口感,其它的继承都是有意义并且需要使用的。
2. 提取一个公共父类:
abstract class 鱼 {
private int 价格;
public int get价格(){ return 价格; }
public virtual int get口感() { throw new Exception(“无意义”) }
public abstract string get名字();
}
abstract class 可以吃的鱼 : 鱼 {
private int 口感;
public override int get口感() { return 口感; }
}
class 鲤鱼: 可以吃的鱼 {
public override string get名字() {
return “鲤鱼”;
}
}
class 桂鱼: 可以吃的鱼 {
public override string get名字() {
return “桂鱼”;
}
}
class 金鱼: 鱼 {
public override string get名字() {
return “金鱼”;
}
}
仔细研究,你会发现网上很多代码,它定义了一个抽象类,并且名字是 XXXBase, XXXCore 或者一个类XXX还定义了一个类叫 XXXImpl 。这些抽象类就像上例中可以吃的鱼这个类一样,都是为了继承而存在的。这里,我们同样有这个问题:
父类的设计会因为需求和子类的增加和不得不作一些修改(如提取另外一个公共父类)。因此面向对象的代码经常需要重构和重写。这就是面向对象中的又一个设计陷阱。
总结一下上面的2个例子,就是:需求增加,代码就要重构。所以很多人认为这是一个只能靠经验才能解决的问题,只要写的代码够多了,就能预感到未来的需求并减少重构量。就像上例中,我们有经验就会先写好一个可以吃的鱼这个类。但这终究是一个幻想,没有谁能真正预知到未来的需求。很多作者都喜欢预留一个XXX接口,然后写了一个XXXImpl的实现类,认为以后只写另外一个XXXImpl2类,就可以重用处理XXX接口时的所有代码。其实以后需要写另外一个XXXImpl2类的概率几乎为0 。这样反而让修改XXX接口的成本上升不少。
需求决定设计,需求变化会导致不断重新设计。这就是面向对象的最大设计陷阱。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
相关推荐
-
固步只会自封:遗忘命令式编程 拥抱面向对象
最好的软件开发人员都知道,即使是最复杂的编程难题最后都会归结于简单的迭代语句,但对于Java却不仅是这样。
-
SOA架构的十大技术理论之面向对象
SOA是从面向对象、构件架构等逐步发展完善,且相互依托、相互补充、又各自适应不同范围,在其演化过程中,都继承了哪些理论体系?
-
TT SOA一月最佳文章TOP 5
2011年一月份已经接近尾声,我们即将迎来农历新年。春节到来之前,编辑精选一月份使用技巧推荐给各位读者,在休假之间给自己加油充电,以良好的状态来迎接新的一年。
-
SOA问答:敏捷开发和面向对象编程
大家都在探讨敏捷开发和面向对象编程。开发团队应该在他们接受SOA的时候继续使用这些方法吗?有两点要注意。首先,通常我们把方法分类。