- 来自名为wanghualiang 的评论
很佩服楼主的发散型思维。但是远远还没到颠覆传统的地步。
这里谈谈我的观点,
面向对象设计时完全从接口来描述对象本身的特性是不是有问题。
从鱼是不是可吃应该只能作为其一个属性来辨识,
Class Fish
{
public bool IsEatable;
}
当客户想吃这条鱼的时候,IsEatable=true;如果是河豚的话就是 False了。
当然有许多种不确定的因素,在可吃不可吃之间。那我们应该
[Flags]
Enum Eattype
{
DeliciousEate,//美味
Distasteful,
Barbed,
..
}
Class Fish
{
public Eattype eattype;
}
我想wanghualiang 的评论代表了相当一部分朋友的看法,从传统的面向对象思想(或者说普遍接受)的观点来看这样做是没有问题的,甚至还被作为了Demo写入了很多的面向对象的教科书。但是实际上这种看法是存在问题的,至少我个人是这么认为的,首先我们需要考虑的问题是:鱼自己能不能决定自己能不能吃,能不能决定自己好不好吃?应该是不能吧,决定鱼能不能吃,好不好吃的应该是吃鱼的对象对吧。也许从普通人的角度来看河豚是不能吃的,但是从高明的大厨或者资深的老饕的角度来看河豚就是无比的美味了,这也是我在序章的最后专门添加一幅图片重点解说对象行为的原因。这里谈谈我的观点,
面向对象设计时完全从接口来描述对象本身的特性是不是有问题。
从鱼是不是可吃应该只能作为其一个属性来辨识,
Class Fish
{
public bool IsEatable;
}
当客户想吃这条鱼的时候,IsEatable=true;如果是河豚的话就是 False了。
当然有许多种不确定的因素,在可吃不可吃之间。那我们应该
[Flags]
Enum Eattype
{
DeliciousEate,//美味
Distasteful,
Barbed,
..
}
Class Fish
{
public Eattype eattype;
}
话说回来,既然鱼不能决定能不能吃、好不好吃,也就是说明能不能吃的行为不是有鱼能够决定的,那么也许有人会问那为什么要实现IEatable接口呢,和直接做一个属性不是一样吗?这个问题问的非常的好,确实既然鱼不能够决定自己能不能吃、好不好吃,那么为什么鱼要实现IEatable接口呢。其实,在Fish上实现IEatable接口完全是出于使用方便性和接口的层次(后续的文章中会重点讨论这个问题)来考虑的,完全面向对象的搞法应该是有另外一个对象来鉴定这个鱼是否能吃、好不好吃的(这也是基于设计的平衡来考虑的,可以参看开放-封闭原则)。在这个地方使用接口和属性本质上没有什么差别,但是一旦鉴别鱼能不能吃、好不好吃的鉴别方式(实现方法)发生变化的时候,使用属性的方式就难以扩展了,只能修改代码了,但是使用接口的好处是我可以使用其它的方式补救,例如做一个实现IEatable接口的装饰对象来装饰鱼对象。
另外,导致需要鱼对象实现IEatable接口的原因可能是出于接口隔离原则的考虑,如果使用属性来辨别鱼是否能吃,必然使用的地方就依赖鱼对象了,提高了系统的耦合性。示意代码如下:
1 '检索是否可以吃的食物
2 Public Function GetEatableFish(ByVal foods As IEatable()) As IEatable()
3
4 '用于保存列表
5 Dim tempList As New ArrayList
6
7 '循环的检索
8 For Each item As IEatable In foods
9
10 '判断是否可以吃
11 If item.IsEatable Then
12
13 tempList.Add(item)
14 End If
15 Next
16
17 '返回结果
18 Return tempList.ToArray(GetType(IEatable))
19
20 End Function
21
22 '是否可以吃接口
23 Public Interface IEatable
24
25 '是否可以吃
26 Function IsEatable() As Boolean
27
28 End Interface
软件设计的时候没有一味的好,也没有一味的差,任何事情都有其两面性,这个是需要取舍的,我们能够做到的事情就是让设计可控,如果设计失控了,那就全部完蛋了。话说回来,如果能够确认当前的系统中评判鱼能不能吃的标准不会发生改变,把这个不会发生改变的东西集成到鱼对象中是完全可以的,在具体的实现上用属性来实现也是一个非常不错的搞法。2 Public Function GetEatableFish(ByVal foods As IEatable()) As IEatable()
3
4 '用于保存列表
5 Dim tempList As New ArrayList
6
7 '循环的检索
8 For Each item As IEatable In foods
9
10 '判断是否可以吃
11 If item.IsEatable Then
12
13 tempList.Add(item)
14 End If
15 Next
16
17 '返回结果
18 Return tempList.ToArray(GetType(IEatable))
19
20 End Function
21
22 '是否可以吃接口
23 Public Interface IEatable
24
25 '是否可以吃
26 Function IsEatable() As Boolean
27
28 End Interface
- 来自名为 Anders Liu 的评论
嗯。。。建议你在多想想再继续写。
比如这个:public class Goby : IFish, IClimbable, IEatable
我认为这样比较好:public class Goby : Fish, IClimbable, IEatable
看到区别了么?
至于Anders Liu
的评论应该与wanghualiang
的意见基本上是一致的,如果我没有理解错误的话。我们引入一个Class的时候需要考虑的是引入这个Class的目的,如果没有任何目的的引入一个类或者是仅仅简单的为了封装一个方法来引入一个类是不可靠的,后面我会写一篇随笔来专门讨论类和接口。比如这个:public class Goby : IFish, IClimbable, IEatable
我认为这样比较好:public class Goby : Fish, IClimbable, IEatable
看到区别了么?