面向对象设计:何时制作抽象类

现在,我正在学习OOP,主要是在c#中。 我感兴趣的是创建一个无法实例化的类的主要原因是什么。 什么时候制作抽象类的正确例子是什么? 我发现自己在inheritance方式中使用抽象类太热情了。 当类在系统中是抽象的并且类不应该是抽象的时候是否有一些规则? 例如,我以某种方式制作了相似的医生和患者类,所以我从抽象类Person中派生出它们(因为它们都有名字和姓氏)。 那是错的吗? 对不起,如果问题很愚蠢,我对此非常陌生。

这可能是一个非学术性的定义,但是一个抽象类应该代表一个如此“抽象”的实体,实例化它是没有意义的。

它通常用于创建必须由具体类扩展的“模板”。 因此抽象类可以实现通用function,例如实现接口的某些方法,特定行为的具体类实现的委托。

到目前为止,还有一些事情没有人指出过,所以我只想指出它们。

您只能从一个基类inheritance(可能是抽象的),但您可以实现许多接口。 因此,在这个意义上,inheritance抽象类比实现接口更紧密。

因此,如果您后来意识到您需要一个实现两个不同抽象类的类,那么您就会深陷其中:)

要回答你的问题“什么时候做一个抽象课”,我会说永远不要,如果可能的话,避免它,从长远来看它永远不会得到回报,如果主类不适合作为一个普通的类,它可能不是真的需要抽象,使用接口。 如果您遇到复制代码的情况,它可能适合抽象类,但总是首先查看接口和行为模式(除了策略模式解决了很多人错误地使用inheritance来解决的问题,总是更喜欢组合而非inheritance)。 使用抽象类作为最后的解决方案,而不是设计。

为了更好地理解OOP,我建议您查看设计模式:可重用面向对象软件的元素(一本书) ,它可以很好地概述OO设计和OO组件的可重用性。 OO设计远不止inheritance:)

例如:您需要从不同来源提取数据,例如“Excel文件,XML,任何数据库等”并保存在一个公共目标中。 它可以是任何数据库。 所以在这种情况下你可以使用像这样的抽象类。

 abstract class AbstractImporter { public abstract List FetchData(); public bool UploadData(List productsSold) { // here you can do code to save data in common destination } } public class ExcelImporter : AbstractImporter { public override List FetchData() { // here do code to get data from excel } } public class XMLImporter : AbstractImporter { public override List FetchData() { // here do code to get data from XML } } public class AccessDataImporter : AbstractImporter { public override List FetchData() { // here do code to get data from Access database } } 

和呼唤可以是这样的

  static class Program { static void Main() { List lstProducts; ExcelImporter excelImp = new ExcelImporter(); lstProducts = excelImp.FetchData(); excelImp.UploadData(lstProducts); XMLImporter xmlImp = new XMLImporter (); lstProducts = xmlImp.FetchData(); xmlImp.UploadData(lstProducts); AccessDataImporterxmlImp accImp = new AccessDataImporter(); lstProducts = accImp .FetchData(); accImp.UploadData(lstProducts); } } 

因此,在上面的示例中,数据导入function的实现在扩展(派生)类中分开,但数据上载function对所有人都是通用的。

从本质上讲,如果你不想实例化一个Person类,那么你做的很好,但是因为我猜你可能想在将来某个时候实例化一个Person类,所以它不应该是抽象的。

虽然有一个论点是您编写代码来解决当前问题,而不是为了解决可能永远不会出现的问题,所以如果您需要实例化Person类,请不要将其标记为抽象。

抽象类是不完整的,必须在派生类中实现…一般来说,我更喜欢抽象基类而不是接口。

研究抽象类和接口之间的区别……

“抽象类和接口之间的区别在于抽象类可以具有方法的默认实现,因此如果不在派生类中重写它们,则使用抽象基类实现。接口不能具有任何实现。 “ 取自这篇SOpost

如前所述,没有人会强迫您使用抽象类,它只是一种抽象某些function的方法,这在许多类中很常见。

您的案例是使用抽象类的一个很好的示例,因为您在两种不同类型之间具有共同属性。 但是因为它限制了你自己使用Person作为一个类型。 如果你想要这个限制基本上取决于你。

一般情况下,我不会像你一样使用类似类的抽象类,除非你想要阻止Person被实例化。

通常我使用抽象类,如果我也定义了一个接口,我需要为这个接口编写不同的实现,但也希望有一个BaseClass,它已经涵盖了所有实现的一些常用function。

从抽象类’Person’中获取’Doctor’和’Patient’都很好,但是你应该把Person变成普通的类。 但这取决于使用“人物”的上下文。

例如,您可能有一个名为“GameObject”的抽象类。 游戏中的每个对象(例如Pistol,OneUp)都扩展了“GameObject”。 但是你不能自己拥有一个’GameObject’,因为’GameObject’描述了一个类应该有什么,但是没有详细说明它们是什么。

例如,GameObject可能会说:“所有游戏对象必须看起来像某样东西。”手枪可能延伸到游戏对象所说的内容,并且它说“所有手枪必须看起来像长筒,一端握住并触发。”

关键是该类的实例化是否有意义。 如果实例化该类永远不合适,那么它应该是抽象的。

一个典型的例子是Shape基类,包含Square,Circle和Triangle子类。 永远不应该实例化Shape,因为根据定义,您不知道您希望它是什么形状。 因此,使Shape成为抽象类是有意义的。

顺便提一下,另一个尚未提及的问题是,可以将成员添加到抽象类,让现有实现自动支持它们,并允许消费者使用了解新成员和实现的实现,而不是,互换。 虽然有一些似是而非的机制,未来的.NET运行时可以通过这种机制允许接口以这种方式工作,但目前它们却没有。

例如,如果IEnumerable一直是一个抽象类(当然有很多原因导致它没有),那么当它的用处变得明显时,就可以添加类似Count方法的东西。 它的默认实现Count可能与IEnumerable.Count扩展方法非常相似,但了解新方法的实现可以更有效地实现它(尽管IEnumerable.Count将尝试利用ICollection.Count的实现ICollection.CountICollection.Count ,它首先要确定它们是否存在;相反,任何覆盖都会知道它有代码直接处理Count

可以添加一个ICountableEnumerable接口,该接口inheritance自IEnumerable但包含Count ,并且现有代码将继续与IEnumerable一样正常工作,但任何时候ICountableEnumerable通过现有代码传递,接收方必须将其重新转换为ICountableEnumerable才能使用Count方法。 比直接调度的Count方法简单得多,它可以简单地直接作用于IEnumerable [ Count扩展方法并不可怕,但它的效率远远低于直接调度的虚方法]。

如果有一个接口可以包含静态方法的方法,并且如果类加载器在发现声称实现IFoo的类Boz缺少方法string IFoo.Bar(int)时会自动添加到该类:

 stringIFoo.Bar(int p1) { return IFoo.classHelper_Bar(Boz this, int p1); } 

[假设接口包含该静态方法],那么可以让接口添加成员而不破坏现有实现,只要它们还包含可以由默认实现调用的静态方法。 不幸的是,我知道没有计划添加任何此类function。