首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

越想越迷糊了,到底什么时候用接口,什么时候用抽象类?解决方案

2012-01-24 
越想越迷糊了,到底什么时候用接口,什么时候用抽象类?接口和抽象类的区别知道了,但实际应用中,到底如何选择

越想越迷糊了,到底什么时候用接口,什么时候用抽象类?
接口和抽象类的区别知道了,但实际应用中,到底如何选择呢?
其区别:
1:一个类可以实现任意多的接口,但是最多只能对一个抽象类进行子类化。
2:一个抽象类可以包括非抽象方法,而一个接口的所有方法在效果上都是抽象的。
3:一个抽象类可以申明并使用变量,而一个接口不行。
4:一个抽象类中的方法的访问修饰符可以使public,internal,protected,protected   internal,private,而接口成员的访问修饰符在默认情况下都是public,而且,在申明接口成员时,不允许使用访问修饰符(甚至不能使用public)。
5:一个抽象类可以定义构造函数,而一个接口不行。

[解决办法]
一般来讲:像实体类就要用抽象类.而操作类就要用接口.
[解决办法]
1.抽象类是一个不完全的类,需要进一步专业化.接口只是一个行为的规范或规定;
2.接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;
3.一个类一次可以实现若干个接口,但是只能扩展一个父类
4.接口可以用于支持回调,而继承并不具备这个特点.


如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
如果创建的功能将在大范围的全异对象间使用,则使用接口。
抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
如果要设计小而简练的功能块,则使用接口。
如果要设计大的功能单元,则使用抽象类。
如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。



[解决办法]
在这里我不想说太多的理论。
接口是规范,约定。
抽象类没有完全描述对象信息。
来看下例子。
需求,需要分析简历包括(中文,英文),给出伪代码
public class ChineseResume
{
public ChineseResume(){}
public ResumeDocument Parse(string text,string model)
{
ChineseRegex regex=new ChineseRegex();
ModelInfo modelInfo=regex.GetModelInfo(model);
TextDocument txt=new TextDocument(modelInfo);
TextInfo textInfo=txt.Parse();
if(textInfo!=null)
return new ResumeDocument(textInfo);
return new NothingResumeDocument();
}
}

public class EnglishResume
{
public EnglishResume(){}
public ResumeDocument Parse(string text,string model)
{
EnglishRegex regex=new EnglishRegex();
ModelInfo modelInfo=regex.GetModelInfo(model);
TextDocument txt=new TextDocument(modelInfo);
TextInfo textInfo=txt.Parse();
if(textInfo!=null)
return new ResumeDocument(textInfo);
return new NothingResumeDocument();
}
}

从对象角度分析,中文简历也好,英文简历也好都是简历,在运行时我不需要知道是中文还是英文,是简历就给我分析吧,很显然这两个类有大量的重复代码,应该抽象了。
public abstract Resume
{
public ResumeDocument Parse()
{
ModelInfo modelInfo=GetModelInfo();
TextDocument txt=new TextDocument(modelInfo);
TextInfo textInfo=txt.Parse();
if(textInfo!=null)
return new ResumeDocument(textInfo);
return new NothingResumeDocument();
}

public abstract ModelInfo GetModelInfo();
}

public class ChineseResume:Resume
{
private ChineseRegex regex=new ChineseRegex()
public ChineseResume(){}
public override ModelInfo GetModelInfo()
{
return regex.GetModelInfo();
}
}

英文简历也是一样。

假设我们有个插件平台,我需要将做好的对象加载到此平台中,但平台中中文和英文有很大不现体现时,接口显然很有用。
public class ChineseResume:Resume,IAddin,IResource
{
private ChineseRegex regex=new ChineseRegex()
public ChineseResume(){}
public override ModelInfo GetModelInfo()
{
return regex.GetModelInfo();
}

//..实现IAddin接口(平台加载和管理时需要)
//..实现IResource接口(平台对你要的资源进行管理,如文件,数据等等)
}
英文我不需要管理资源(假设没有资源)
public class EnglishResume:Resume,IAddin
{
private EnglishRegex regex=new EnglishRegex()
public EnglishResume(){}
public override ModelInfo GetModelInfo()
{
return regex.GetModelInfo();
}

//..实现IAddin接口(平台加载和管理时需要)


}
在这里可以看出接口规范的重要性,它不影响类原有的功能,轻松对别人开放的API进行扩展。
对于regex也可以抽象,加入其它语言扩展也很方便。
认真思考,多做实践,可以去看看理论了。
[解决办法]
如果实现同一功能的类在类的层次体系上有关联 用抽象类

如果只是功能的名字相同,而事实上实现功能的类并没有太大的关系,可以用接口


[解决办法]
用类的时候,首先想到是有可重用的代码。
用接口的时候,首先想到的是有共同的方法或者属性。

这是唯一让你选择是抽象类还是接口的理由。
[解决办法]
如果在实际写代码的时候,你还分不清应当用哪个,只能说明你还没有搞清楚:是不是有可重用的代码,这些代码是不是真的可重用,而不必被覆写。
[解决办法]
接口A和继承类B的关系..... B can do A;
抽象类A和继承类B的关系....B is a A;

[解决办法]
接口(interface)用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。有了这个协定,就可以抛开编程语言的限制(理论上)。接口可以从多个基接口继承,而类或结构可以实现多个接口。接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或接口必须提供的成员。

  接口好比一种模版,这种模版定义了对象必须实现的方法,其目的就是让这些方法可以作为接口实例被引用。接口不能被实例化。类可以实现多个接口并且通过这些实现的接口被索引。接口变量只能索引实现该接口的类的实例。例子:

interface IMyExample {
 string this[int index] { get ; set ; }
 event EventHandler Even ;
 void Find(int value) ;
 string Point { get ; set ; }
}
public delegate void EventHandler(object sender, Event e) ;

  上面例子中的接口包含一个索引this、一个事件Even、一个方法Find和一个属性Point。

[解决办法]
1、C#中的接口是独立于类来定义的。这与 C++模型是对立的,在 C++中接口实际上就是抽象基类。

  2、接口和类都可以继承多个接口。

  3、而类可以继承一个基类,接口根本不能继承类。这种模型避免了 C++的多继承问题,C++中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。C#的简化接口模型有助于加快应用程序的开发。

  4、一个接口定义一个只有抽象成员的引用类型。C#中一个接口实际所做的,仅仅只存在着方法标志,但根本就没有执行代码。这就暗示了不能实例化一个接口,只能实例化一个派生自该接口的对象。

  5、接口可以定义方法、属性和索引。所以,对比一个类,接口的特殊性是:当定义一个类时,可以派生自多重接口,而你只能可以从仅有的一个类派生。

  接口与组件


[解决办法]
这个是我在别的帖子上发的,给楼主看看

接口只是定义了一个规范,适用于没有什么关联的类实现名称相同的功能(只是名称相同,干得事可以大不相同)

电视,钓鱼竿,马桶 都有开关,我们一听开关就知道,开关一定包括两个相反的功能,一个是开,一个是关。但开和关究竟干的是什么,显然它们三个不一样...

那这个什么都干不了的东西我们还要它干什么?

刚才说过了,接口是一个规范,它定义了我们做事的框架。

比如,为什么大多数的显卡能查在大多数的主板上,并能正常工作?

难道所有的显卡厂商要把自己的产品在所有的主板厂商上测试一遍?

同样难道所有的主板厂商要把自己的产品在所有的显卡厂商上测试一遍?

不用这样,只要,显卡厂商和主板厂商答成一个协议,对双方的数据交换定义一个统一的格式即可,但真正去实现这个协议的,是各家厂商(这应该就是显卡能有公版驱动的一个原因)。

太多了... 不说了...

[解决办法]
1. Interface, "like a "关系; abstract class, "is a "关系

2. Interface是一种契约, abstract class是事物本质的抽象概括

由以上两点, 什么时候用Interface什么时候用abstract class有看你如何理解你的设计对象, 例如, 某一天你可能会要设计一个Door的类, 它有Open和Close两个功能, 于是

public interface Door
{
void Open();
void Close();
}

public abstract class Door
{
void Open(){};
void Close(){};
}

就目前来说, 你用Interface还是用abstract class基本上没什么区别, 两者都用来描述Door的行为, 只不过用Interface是在强调Door额外的有Open和Close这两个功能, 而用abstract class则是在强调Door自带有Open和Close的能力

走远一点, 假如又有一天, 你要写一个AlarmDoor的类, 这个时候...

public interface AlarmDoor
{
void open();
void close();
void alarm();
}

public abstract class AlarmDoor
{
void open(){};
void close(){};
void alarm(){};
}

现在, 你应该想清楚你究竟是想设计一个报警器还是想设计一扇门, 如果选择Interface, 那你是在想设计一个报警器, 如果选择abstract class, 那你是在想设计一扇门, 说到现在, 是否有点眉目了?

C#里面不允许多个父类, 但却允许多个接口, 接口无非是让某个类拥有某种行为, 而抽象类却是在让某个类拥有某种本质, 因此, 我们设计的类, 应该用一个抽象类把它的所有本质都抽象出来, 而它的一些行为(或者说是契约)则通过Interface来实现, 这也能说明为什么C#会不允许多层父类却允许多层接口


[解决办法]
深入理解abstract class和interface

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

理解抽象类

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:

abstract class Demo {
abstract void method1();
abstract void method2();



使用interface的方式定义Demo抽象类的方式如下:

interface Demo {
void method1();
void method2();

}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

从编程的角度来看,abstract class和interface都可以用来实现 'design by contract '的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了 'one rule,one place '原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。



[解决办法]
从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在 'is a '关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于 'is a '关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

abstract class Door {
abstract void open();
abstract void close();
}


使用interface方式定义Door:


interface Door {
void open();
void close();
}


其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

abstract class Door {


abstract void open();
abstract void close();
abstract void alarm();
}


或者

interface Door {
void open();
void close();
void alarm();
}


那么具有报警功能的AlarmDoor的定义方式如下:

class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}


或者

class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }


这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念 '报警器 '的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为 '报警器 '这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是 'is a '关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}


这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是 'is a '关系,interface表示的是 'like a '关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

结论

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。

热点排行