关于抽象类( abstract class )和接口( interface )的理解
Saturday, February 24, 2007 4:56:27 AM
1. 相似点
2. 不同点
3. 如何使用抽象类和接口
4. 综合例子
2. 不同点
3. 如何使用抽象类和接口
4. 综合例子
在c#, java等高级开发语言中, 都提供了抽象类和接口这两种概念. 从表面上看, 它们有如下相似点:
1. 相同的应用方式( 继承 )
2. 都起到了隐藏类型的作用
3. 都可以作为模块功能扩充的基础( 抽象类的子类或者接口的实现类可以有无限多个, 每一个都可以作为模块功能的一种扩充 )
尽管有这些相似点, 我们在设计时还是需要对抽象类和接口的应用范围加以区分, 因为它们还有如下不同的地方:
1. 抽象类是类, 而接口仅仅是类的公共成员的声明. 因此抽象类可以包含功能定义和实现, 而接口却只能够包含功能定义.
2. 抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性; 接口着眼于需求, 是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性.
我认为应该按照下面的规则来使用抽象类和接口:
1. 分析对象, 提炼内部共性并生成抽象类
2. 用抽象类来确定对象本质, 即"是什么"( is a ). 在c#和java中, 都规定当前类只能够继承一个父类, 我想这也进一步明确了当前类到底是什么的问题
3. 当模块功能将要提供为外部模块调用时, 我们应该设计一个接口
4. 如果当前模块的功能需要扩充, 我们优先考虑接口
最后用一个小例子来说明以上观点:
假设要为一个电视厂家开发一套软件系统, 那么我们需要为电视机设计一个类:
abstract class TVBase
{
public virtual void TurnOn() {...}
public virtual void TurnOff() {...}
public virtual void ChangeChannel() {...}
public abstract void ChangeMode();
}
如果厂家生产的TV分为普通屏幕和宽屏的, 普通屏幕没有特殊功能, 而宽屏可以在普通屏幕和宽屏两种显示比例之间切换. 那么我们应该这样表示:
class TVNormal : TVBase
{
public override void ChangeMode() {...};
}
interface IWideScreen
{
void SetWideProportion(bool);
}
class TVWideScreen : TVBase , IWideScreen
{
public override void ChangeMode() {...}
public void SetWideProportion(bool isWide) {...}
}
从上述代码我们可以了解以下信息:
TVNormal是电视( class TVNormal : TVBase ); TVWideScreen也是电视, 但是是宽屏的( class TVWideScreen : TVBase, IWideScreen ); 宽屏具备切换显示比例的功能.
为了提高市场竞争力, 电视机厂家又为宽屏电视添加了录像功能, 于是我们也为录像功能设计了新接口:
interface IRecord
{
void StartREC();
void PauseREC();
void StopREC();
void PlayREC();
}
并修改了TVWideScreen的实现以满足录像功能:
class TVWideScreen : TVBase, IWideScreen, IRecord
{
public override void ChangeMode() {...}
public void SetWideProportion(bool isWide) {...}
public void StartREC() {...}
public void PauseREC() {...}
public void StopREC() {...}
public void PlayREC() {...}
}
宽屏电视有了录像功能之后, 市场反应十分热烈, 于是电视机厂家决定让普通电视也具备录像功能. 呵呵, 修改随之而来:
class TVNormal : TVBase, IRecord
{
public override void ChangeMode() {...}
public void StartREC() {...}
public void PauseREC() {...}
public void StopREC() {...}
public void PlayREC() {...}
}
不过细心的你可能已经发现, 不管是宽屏电视还是普通电视, 能够录像已经成为标准配置, 仔细斟酌今后可能发生的需求, 我们认为这个厂家未来生产的电视机都会具备录像功能, 于是我们决定进行重大修改, 将录像这个共性抽象出来:
abstract class TVBase : IRecord
{
public virtual void TurnOn() {...}
public virtual void TurnOff() {...}
public virtual void ChangeChannel() {...}
public abstract void ChangeMode();
public abstract void StartREC();
public abstract void PauseREC();
public abstract void StopREC();
public abstract void PlayREC();
}
相应的, 我们修改一下TVNormal和TVWideScreen的实现:
class TVNormal : TVBase
{
public override void ChangeMode() {...}
public override void StartREC() {...}
public override void PauseREC() {...}
public override void StopREC() {...}
public override void PlayREC() {...}
}
class TVWideScreen : TVBase, IWideScreen
{
public override void ChangeMode() {...}
public void SetWideProportion(bool isWide) {...}
public override void StartREC() {...}
public override void PauseREC() {...}
public override void StopREC() {...}
public override void PlayREC() {...}
}
更加明智的做法是我们将录像功能做成单独的模块, 然后在TVBase的抽象基类中去引用这个功能就行了:
class Record : IRecord
{
public void StartREC() {...}
public void PauseREC() {...}
public void StopREC() {...}
public void PlayREC() {...}
}
abstract class TVBase
{
public virtual void TurnOn() {...}
public virtual void TurnOff() {...}
public virtual void ChangeChannel() {...}
public abstract void ChangeMode();
public Record Recorder;
}
这样处理之后, 子类无需做任何改动就具备了录像功能. 我们需要的灵活性和可维护性也就展现处来了 ^_^
