关于C#:面向对象设计:何时创建抽象类

Object oriented design: when to make an abstract class

现在,我正在学习OOP,主要是C。我感兴趣的主要原因是什么使得类不能被实例化。什么时候创建抽象类是正确的例子?我发现自己在继承中使用抽象类的方式太热情了。当类在系统中是抽象的,而类不应该是抽象的时候,是否有一些规则?例如,我创建了医生和病人类,它们在某种程度上相似,所以我从抽象类人(因为两者都有名字和姓氏)中派生出来。那是错的吗?对不起,如果这个问题是愚蠢的,我是新来的。


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

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

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

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

为了更好地理解OOP的一般性,我建议您看一下设计模式:可重用面向对象软件(一本书)的元素,它对OO设计和OO组件的可重用性有了很好的概述。OO设计不仅仅是继承:)


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

  }
}

打电话可以是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    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);


          }
    }

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


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

例如,如果IEnumerable是一个抽象类(当然有很多理由不这样做),那么当它的实用性变得明显时,可以添加类似于Count方法的东西;它默认的Count实现的行为与IEnumerable.Count扩展方法非常相似,但是知道新方法可以更有效地实现它(尽管IEnumerable.Count将尝试利用ICollection.CountICollection.Count的实现,但首先必须确定它们是否存在;相反,任何重写都会知道它有直接处理Count的代码)。

可以添加从IEnumerable继承但包含CountICountableEnumerable接口,现有代码将继续与IEnumerable正常工作,就像以前一样,但任何时候通过现有代码传递ICountableEnumerable时,接收者都必须将其重新转换为ICountableEnumerable才能使用EDOCX1。〔0〕法。与直接发送的Count方法相比,直接发送的Count方法可以直接作用于IEnumerableCount扩展方法并不可怕,但其效率远低于直接发送的虚拟方法〕。

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

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

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


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

一个典型的例子是一个图形基类,包含方形、圆形和三角形子类。形状不应该被实例化,因为根据定义,您不知道您希望它是什么形状。因此,将shape设置为抽象类是有意义的。


这可能是一个非学术性的定义,但抽象类应该表示一个如此"抽象"的实体,无法对其进行实例化。

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


从抽象类"人"中派生"医生"和"病人"是可以的,但您可能应该使人成为一个普通类。不过,这取决于"人"的使用环境。

例如,您可能有一个名为"gameobject"的抽象类。游戏中的每一个对象(如手枪、一个以上)都扩展了"游戏对象"。但是你不能单独拥有一个"gameobject",因为"gameobject"描述了一个类应该拥有什么,但是没有详细描述它们是什么。

例如,gameobject可能会说:"所有gameobject都必须看起来像某个东西"。一把手枪可以延伸到游戏物体所说的地方,它说:"所有的手枪必须看起来像一个长枪管,一端有一个把手,另一端有一个扳机。"


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

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

一般来说,我不会像您所拥有的那样为模型类类使用抽象类,除非您希望防止对人进行实例化。

通常我使用抽象类,如果我还定义了一个接口,我需要为这个接口编写不同的实现代码,但是我也希望有一个已经包含了所有实现的一些公共功能的基类。


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

尽管有一个参数要求您编写代码来解决当前问题,而不是解决可能永远不会出现的问题,所以如果需要实例化Person类,则不要将其标记为抽象的。

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

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

"抽象类和接口之间的区别在于抽象类可以有方法的默认实现,因此如果不在派生类中重写这些方法,则使用抽象基类实现。接口不能有任何实现。"摘自此so post