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

时间:2013-09-24 09:49:37

标签: c# oop inheritance abstract-class

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

8 个答案:

答案 0 :(得分:2)

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

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

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

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

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

答案 1 :(得分: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);


          }
    }

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

答案 2 :(得分:0)

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

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

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

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

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

答案 3 :(得分:0)

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

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

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

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

答案 4 :(得分:0)

从抽象类'Person'中获取'Doctor'和'Patient'都很好,但是你应该将Person设为普通类。但这取决于使用“人物”的背景。

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

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

可以添加一个ICountableEnumerable<T>接口,该接口继承自IEnumerable<T>但包含Count,现有代码可以继续与IEnumerable<T>一起使用,因为它总是有,但是只要ICountableEnumerable<T>通过现有代码传递,收件人就必须将其重新转换为ICountableEnumerable<T>以使用Count方法。使用直接发送Count方法直接在IEnumerable<T> [{1}}扩展方法上行动的方法并不太方便,但它的效率远低于直接-dispatched virtual method]。

如果存在接口可以包含静态方法的方法,并且如果类加载器在发现声称实现Count的类Boz时缺少方法{{1} },会自动添加到该类:

IFoo

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