1

现在,我正在学习 OOP,主要是在 c# 中。我对创建一个无法实例化的类的主要原因很感兴趣。何时创建抽象类的正确示例是什么?我发现自己以继承方式使用抽象类过于热情。当类在系统中是抽象的并且类不应该是抽象的时,是否有一些规则?例如,我创建了在某种程度上相似的医生和病人类,所以我从抽象类 Person 派生了它们(因为它们都有名字和姓氏)。那是错的吗?抱歉,如果这个问题很愚蠢,我对此很陌生。

4

8 回答 8

2

到目前为止,有几件事没有人指出,所以我只想指出它们。

您只能从一个基类(可能是抽象的)继承,但您可以实现许多接口。所以从这个意义上说,继承抽象类比实现接口的关系更密切。

因此,如果您后来意识到您需要一个实现两个不同抽象类的类,那么您就大错特错了 :)

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

为了更好地理解 OOP,我建议您看一下Design Patterns: Elements of Reusable Object-Oriented Software(一本书),它很好地概述了 OO 设计和 OO 组件的可重用性。OO 设计不仅仅是继承 :)

于 2013-09-24T12:51:24.023 回答
1

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

abstract class AbstractImporter 
{
    public abstract List<SoldProduct> FetchData();
    public bool UploadData(List<SoldProduct> productsSold)
    {
        // here you can do code to save data in common destination 
    }
}

public class ExcelImporter : AbstractImporter 
{
  public override List<SoldProduct> FetchData()
  {
  // here do code to get data from excel

  }
}

public class XMLImporter : AbstractImporter 
{
  public override List<SoldProduct> FetchData()
  {
  // here do code to get data from XML

  }
}

public class AccessDataImporter : AbstractImporter 
{
  public override List<SoldProduct> FetchData()
  {
  // here do code to get data from Access database

  }
}

和调用可以是这样的

    static class Program
    {

          static void Main()
          {
             List<SoldProduct> 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);


          }
    }

因此,在上面的示例中,数据导入功能的实现在扩展(派生)类中分离,但数据上传功能对所有人来说都是通用的。

于 2013-09-24T10:58:56.120 回答
0

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

尽管有一种说法是您编写代码是为了解决当前问题,而不是为了迎合可能永远不会出现的问题,因此如果您需要实例化 Person 类,请不要将其标记为抽象类。

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

看看抽象类和接口之间的区别......

“抽象类和接口的区别在于,抽象类可以有方法的默认实现,所以如果你不在派生类中重写它们,就会使用抽象基类实现。接口不能有任何实现。 " 取自这篇 SO 帖子

于 2013-09-24T09:54:20.947 回答
0

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

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

通常,除非您想阻止 Person 被实例化,否则我不会像您那样对 Model 类的类使用抽象类。

通常,如果我还定义了一个接口并且我需要为该接口编写不同的实现,但还希望拥有一个已经涵盖所有实现的一些通用功能的 BaseClass,我会使用抽象类。

于 2013-09-24T10:02:53.060 回答
0

从抽象类 'Person' 派生 'Doctor' 和 'Patient' 很好,但您可能应该让 Person 只是一个普通类。不过,这取决于使用“Person”的上下文。

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

例如,GameObject 可能会这样说:“所有 GameObjects 必须看起来像什么东西”。手枪可能会在 GameObject 所说的基础上扩展,它会说“所有 Pistols 必须看起来像一个长枪管,一端有把手和扳机。”

于 2013-09-24T10:05:05.007 回答
0

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

它通常用于创建必须由具体类扩展的“模板”。所以一个抽象类可以实现一些共同的特性,例如实现一个接口的一些方法,一个委托给具体类实现特定行为。

于 2013-09-24T10:39:57.410 回答
0

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

一个经典的例子是 Shape 基类,它有 Square、Circle 和 Triangle 子类。Shape 永远不应该被实例化,因为根据定义,你不知道你想要它是什么形状。因此,将 Shape 设为抽象类是有意义的。

于 2013-09-24T10:45:03.113 回答
0

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

例如,如果 IEnumerable 是一个抽象类(当然有很多理由不这样做),那么Count当它的用处变得明显时,可以添加类似方法之类的东西;它的默认实现Count可能很像IEnumerable<T>.Count扩展方法,但是知道新方法的实现可以更有效地实现它(尽管IEnumerable<T>.Count会尝试利用ICollection<T>.Countor的实现ICollection.Count,但它首先必须确定它们是否存在;相比之下,任何override 会知道它有代码可以Count直接处理)。

本来可以添加一个ICountableEnumerable<T>继承自IEnumerable<T>但包含的接口Count,并且现有代码将继续IEnumerable<T>像往常一样正常工作,但是任何时候ICountableEnumerable<T>通过现有代码传递,接收者都必须重新转换它ICountableEnumerable<T>以使用Count方法。远不如直接分派的Count方法方便得多,它可以简单地直接作用于IEnumerable<T>[Count扩展方法并不可怕,但它的效率远低于直接分派的虚拟方法]。

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

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

[假设接口包含该静态方法],那么可以在不破坏现有实现的情况下让接口添加成员,前提是它们还包括可以由默认实现调用的静态方法。不幸的是,我知道没有添加任何此类功能的计划。

于 2013-10-11T16:37:37.727 回答