【问题标题】:Is it not the waste of time to create abstract classes?创建抽象类不是浪费时间吗?
【发布时间】:2014-01-18 10:25:49
【问题描述】:

我对抽象类感到很困惑,我知道这不是与编程问题相关的问题。但这是我唯一能找到解决方案的地方。
众所周知,抽象类不能被实例化,你不能在这些类中实现任何东西。我们可以只派生这些类,并且可以在派生类中实现。所以我的问题是:

如果我们必须在派生类中实现逻辑,并且我们必须在派生类中使用完整的函数和类,那么为什么要创建抽象类?

谁能给我一个明确的解释?如果有什么好的例子,请告诉我

【问题讨论】:

  • @MitchWheat 我使用它,但没有找到任何可以更好地解释它的东西......但是堆栈溢出有很多专家,这就是为什么我比谷歌更好地询问开发人员:)
  • 您搜索了整个互联网,但找不到一个解释为什么抽象类是一个有用的想法以及如何使用它们?我们必须使用不同的互联网。
  • 你不能在这些类中实现任何东西。那不是真的。抽象基类可以实现方法。并且没有任何非抽象方法,抽象类与接口非常相似。
  • 不完整但我没有找到任何满意的定义和解释这个@MitchWheat
  • 您可能想在Programmers Stack Exchange 询问,因为这实际上并不是一个特定于代码的程序。但是,是的,我感觉到了你。网络上有大量信息,但有时需要几个小时才能筛选出您想要的确切信息,而这里的专家可以在几秒钟内告诉您。

标签: c# oop abstract-class


【解决方案1】:

当您使用抽象类时,您在抽象类中有一些实现,但将特定于派生类的位保留为抽象。例如,我可以为所有形状定义一个抽象类:

abstract class Shape
{
  public int Sides{get;protected set;}
  public abstract int CalculateArea();
}

所有形状都有特定的边数,因此可以进入一般情况(即Shape)。面积的计算取决于形状的类型(三角形、正方形等),因此是否属于派生类。但是,我希望我的类层次结构的用户能够从任何 Shape 实例调用 CalculateShape

我如何创建派生类并覆盖CalculateLength

class Square : Shape
{
  public Square(int sideLength)
  {
    this.Sides = 4;
    this.SideLength = sideLength;
  }

  public int SideLength{get;private set;}

  public override int CalculateArea()
  {
    return this.SideLength * this.SideLength
  }
}

请注意,我没有重新实现Square 中的所有Shape,我只实现了Square 的特定位。

【讨论】:

    【解决方案2】:

    根据MSDN

    抽象类不能被实例化。抽象类的目的是提供多个派生类可以共享的基类的通用定义。例如,类库可以定义一个抽象类,用作其许多函数的参数,并要求使用该库的程序员通过创建派生类来提供他们自己的类实现。

    以上都是技术解释,通俗地说,就像创建一个蓝图,提供信息它可以做什么,所有派生类都将提供自己的过程如何做到这一点。
    例如,我们可以想到Driver,我们知道有许多驱动程序类型,如auto-rickshaw drivertruck drivertractor driver 等,但它们都有一个共同的功能,即Driving,但每个Driving过程会有所不同,因此

    Driver:带有Driving函数的抽象类
    Auto-RickshawDriver:扩展Driver类的派生类

    你的问题,

    如果我们必须在派生类中实现逻辑,并且必须在派生类中使用完整的函数和类,那么为什么要创建抽象类?

    仅仅因为我们想定义一些通用功能,其他所有类都必须遵守成为它的成员。一般而言,基于功能创建组,即我们可以说如果该类属于一个组,我们肯定会出现该组的那些常见功能。

    【讨论】:

    • 英语网站中的 StackOverflow。请看this FAQ
    【解决方案3】:

    抽象类将包含常见的不完整实现,然后可以从多个派生类中重用。

    • 其中protected方法只能从派生类内部调用。
    • 抽象方法需要在派生类中实现。
    • 虚拟方法可以在派生类中使用

    在 .NET 框架中,您可以找到许多此类抽象类的示例,例如System.IO.Stream.

    使用所有这些类的代码可以使用它们的公共公共接口Stream 来引用它们,而不必知道它使用了哪个具体的Stream 实现。

    【讨论】:

      【解决方案4】:

      只是为了纠正一个小细节,抽象类可以是部分抽象的或完全抽象的,这取决于它们是否部分实现(在这种情况下,我们通常称它们为抽象类) 或者根本没有实现并且没有成员变量(在这种情况下,我们通常称它们为接口)。

      它们是抽象类的许多用途,其中大多数暗示着无需修改此代码即可扩展代码行为的能力。

      假设您要编写一个程序,从键盘读取数据并将其写入屏幕。 (我将使用 C# 编写代码,因为它比 C++ 略短)

      void Copy() {
        int c;
        while((c = Console.Read()) != -1) {
          Console.Write(c);
        }
      }
      

      现在,需求发生了变化。您希望能够从文件而不是键盘读取,具体取决于变量。

      TextReader reader;
      
      void Copy(bool readFromFile) {
        int c;
        while((c = ReadNextChar(readFromFile)) != -1) {
          Console.Write(c);
        }
      }
      
      int ReadNextChar(bool readFromFile) {
        if(readFromFile) {
          return reader.Read();
        } else {
          return Console.Read();
        }
      }
      

      现在,您希望能够从网络读取。 bool 变成 enumif/else 变成 switch/case 等等等等。

      enum ReadMode { FROM_CONSOLE, FROM_FILE, FROM_NETWORK };
      
      TextReader fileReader;
      TextReader networkReader;
      
      void Copy(ReadMode readMode) {
        int c;
        while((c = ReadNextChar(readMode)) != -1) {
          Console.Write(c);
        }
      }
      
      int ReadNextChar(ReadMode readMode) {
        switch(readMode) {
          case FROM_CONSOLE:
              return Console.Read();
          case FROM_FILE:
              return reader.Read();
          case FROM_NETWORK:
              return networkReader.Read();
          default:
              return -1;
        }
      }
      

      这可以继续下去。写作也可能出现同样的要求。该程序的问题在于它明确列出了您可以从文件中读取的所有方式。要从其他来源阅读,您必须修改代码(在enum ReadMode 中添加一个成员并在ReadNextChar 中添加一个case。如果您可以访问源代码,您可能认为这没问题,但如果它是外部库,您可能会认为这种做法很糟糕,因为您无法扩展这个美妙的复制库。

      现在认识抽象类。如果您将所有来源抽象为一个Source 概念会怎样,因为毕竟您只需要向来源询问int。所以让我们来写这个抽象的概念。

      interface Source {
        int ReadNextChar();
      }
      

      以及所有三个实现:

      class KeyboardSource : Source {
        int ReadNextChar() {
          return Console.Read();
        }
      }
      
      class FileSource : Source {
        TextReader reader;
      
        FileSource(string path) {
          reader = new StreamReader(path);
        }
      
        int ReadNextChar() {
          return reader.Read();
        }
      }
      
      class NetworkSource : Source {
        TextReader reader;
      
        NetworkSource (string url) {
          reader = new StreamReader(path);
        }
      
        int ReadNextChar() {
          return reader.Read();
        }
      }
      

      并实现复制功能:

      void Copy(Source source) {
        int c;
        while((c = source.ReadNextChar()) != -1) {
          Console.Write(c);
        }
      }
      

      现在您想添加任何源类型,您只需实现Source 的新派生,无需修改现有代码。 Copy 程序保持正确且未受影响,Source 的现有衍生程序也是如此。

      这称为多态性。如果您查看Copy 的依赖关系,它们不包含有关文件、网络或用户界面的代码。它可以打包在一个库中,允许用户扩展它可以读取的源类型。

      希望很清楚。写作部分也可以这样做。

      【讨论】:

      • 很好的答案,但离题了。您提取了一个由具体读者实现的接口,但是 OP 想了解 abstract classes 而不是 abstraction 一般。如果您要向 Source 添加一些常用方法并使 ReadNextChar() 方法抽象化,那就是主题。
      • 他说“你不能在这些类中实现任何东西”,显然是指完全抽象的类又名接口,而不是部分抽象的类。因此,我的帖子的介绍部分。让 OP 决定它是否离题。
      • 那么你选择了错误的语言,因为 C# 中的接口不是一个完全抽象的类,它是一个接口。一个完全抽象的类,仍然是一个类,它可以实现一个或多个接口。
      • 就设计而言,它一个完全抽象的类。仅允许派生一个超类是 C# 的一种选择。请注意,OP 将问题标记为“C#”。我没有在 C# 中使用抽象类,正是因为你只有一个弹药。
      • 接口和抽象类之间存在语义差异。接口表达意图和特定行为。抽象类只是一个基类,等待被继承。直到某一点,从技术上讲,您可以用一个替换另一个,但这并不总是可取的。在您的示例中,您刚刚向 OP 确认不需要抽象类,一个接口就足够了。
      【解决方案5】:

      看看这个例子:

      abstract class A 
      {
         // forces subclasses to implement
         abstract void method1();
      
         virtual void method2() 
         {
            // some common logic
            // that sublcasses can
            // modify
         }
      
         protected void method3()
         {
            // some common logic
         }
      }
      
      class B : A 
      {
          void method1() 
          {
             // needs to be implemented
          }
      
          override void method2()
          {
             // optional: e.g. something added
             base.method1();
          }
      
      }
      
      class C : A    {
         //...
      }
      

      不,您可以在基类中指定通用逻辑,以及定义每个子类将具有的抽象方法。所以你的基类就像一个接口。您可以对 B 和 C 的每个实例调用 method1()

      所以:不!这绝对是不会浪费时间 ;-)。

      【讨论】:

        【解决方案6】:

        不是,

        假设你有一个基类

        class Foo{
           public String getGreetingMessage(){
              return "Hello " + getName();
           }
        
           public abstract String getName();
        }
        

        你有一个派生类

        class Bar extends Foo{
          public String getName(){
             return "MeMamo";
          }
        }
        class Boo extends Foo{
           public String getName(){
               return "Boo";
           }
        }
        

        在另一个类中,我从派生中调用方法,

        Bar b = new Bar();
        System.out.println(b.getGreetingMessage());  // Hello MeMamo.
        
        Boo boo = new Boo();
        System.out.println(boo.getGreetingMessage());  // Hello Boo;
        

        我不需要重新编写基本方法。 假设另一个派生类的操作。

        【讨论】:

          猜你喜欢
          • 2010-10-23
          • 2016-10-09
          • 1970-01-01
          • 2011-05-22
          • 2013-12-03
          • 1970-01-01
          • 1970-01-01
          • 2012-09-23
          • 1970-01-01
          相关资源
          最近更新 更多