这是一个很经典的问题,很简单,通过这个问题我们来认识面向对象的一些设计上的知识。问题是这样的:有一种沙发床,可以供像去坐沙发一样去坐,也可以供像睡床一样去睡,请用面向对象的语言去模拟。
最常规的想法是这样的:
interface Sofa{
void sit();
}
interface Bed{
void sleep();
}
class DefaultSofa implements Sofa{
public void sit(){
System.out.println("Sitting");
}
}
class DefaultBed implements Bed{
public void sleep(){
System.out.println("Sleeping");
}
}
class SofaBed implements Sofa,Bed{
public void sit(){
System.out.println("Sitting");
}
public void sleep(){
System.out.println("Sleeping");
}
}
以上代码看似完美,事实上在讲述java语法时,这个实现也确实是个很好的阐述java中如何避开多继承的例子。然而,聪明的读者一定可以嗅到这段设计的臭味了,DefaultSofa类和SofaBed类的sit方法实现采用了copy+paste的方式,DefaultBed类和SofaBed类的sleep方法也一样。
一个好的设计必须能很好的复用代码。显然这样的设计在系统架构师眼里就不再那么完美了。 也有部分聪明的读者甚至开始怀疑java的整体架构了,开始认为接口是否并不是像sun说的那么高明。 这样的想法很自然,这起码说明读者已开始思考到了这种设计的弊端。然而当你进一步了解了java接口的真正用意后你就会改变这种看法了。
先让我们来研究研究java接口。
接口和类的最重要的区别是,接口仅仅描述方法的特征,而不是给出方法的实现;而类不仅给出方法的特征,而且给出方法的实现。因此,接口把方法的特征和方法的实现分割开来。这种分割,体现在接口常常代表一个角色,它包装与该角色相关的操作和属性,而实现这个接口的类便是扮演这个角色的演员。一个角色可以由不同的演员来演,而不同的演员之间除了扮演一个共同的角色之外,并不要求有任何其他的共同之处。(出自<<java与模式>>)
有什么样的需求就会有什么样的设计。
所描述问题的需求为:沙发的sit方法和沙发床的sit方法具有同样的实现;床的sleep方法和沙发床的sleep方法具有同样的实现;只能坐在沙发上;只能睡在床上。
根据上面所说,就应该思考sit和sleep的这个职责到底该由谁来完成了。事实上沙发是没有坐的方法的,应该是人去坐沙发,宠物去坐沙发;人去睡床,宠物去睡床。由此,可以考虑下面的设计
interface SitAble{
void sit(Sofa sofa);
}
interface SleepAble{
void sleep(Bed bed);
}
interface Sofa{
}
interface Bed{
}
class SofaBed implements Sofa,Bed{
}
class Person implements SitAble,SleepAble{
public void sit(Sofa sofa){
}
public void sleep(Bed bed){
}
}
class Pet implements SitAble,SleepAble{
public void sit(Sofa sofa){
}
public void sleep(Bed bed){
}
}
结论:这样的设计解决了代码不能复用的问题,同时也符合具体实际。缺点就是类和接口多了点,这很正常,因为每个类可能还有很多别的代码,我们在这里仅关心问题的这些方面。一般来说要获得很好的架构,往往要去挖掘一些隐藏类。欢迎大家讨论和批评