【问题标题】:Interface vs Abstract Class with an empty method [closed]具有空方法的接口与抽象类[关闭]
【发布时间】:2015-01-27 08:11:20
【问题描述】:

我试图了解何时应该使用接口和抽象类。我正在考虑改进我的 MVC 应用程序设计并偶然发现了这篇文章:http://www.codeproject.com/Articles/822791/Developing-MVC-applications-using-SOLID-principles

在关于 OCP 的部分中,作者给出了一个计算书籍价格的例子。原始代码如下所示:

enum Category
{
    student,
    corporate
}

class Book
{
    public double CalculatePrice(double price,Category category)
    {
        if (category == Category.corporate)
        {
            price = price- (price * 10);
        }
        else if (category == Category.student)
        {
            price = price - (price * 20);
        }

        return price;
    }

}

他的解决方案是这样的:

abstract class Book
{
    public abstract double CalculatePrice(double price);
}

class StudentBook : Book
{
    public override double CalculatePrice(double price)
    {
        return price - (price * 20);
    }
}

class CorporateBook : Book
{
    public override double CalculatePrice(double price)
    {
        return price - (price * 10);
    }
}

我在查看此解决方案时的问题是:

  1. 为什么在这里使用抽象类而不是接口?
  2. 如果你把它改成一个界面会有什么不同?真的很重要吗?

感谢您对理解这一点的任何帮助

【问题讨论】:

标签: c# oop solid-principles


【解决方案1】:

这个例子是人为的,因为 Book 基类没有行为,它也可以是一个接口。然而,更现实的例子会有许多其他方法,例如

getAuthor()
getISBN()
isFiction()

如果这本书是 Student 或 Corporate,那么这些行为可能不会改变,因此我们有一个包含许多标准行为的基类。所以 Book 将真正成为一个类,因为它具有由其派生类共享的有用行为。

当你有几组行为时,事情会变得有点复杂,例如图书馆书是一本书,但它也是一个 LendableThing 并且在 Java 中你不能从两个不同的基类继承。

在实践中,我发现接口比抽象基类更多。我将接口定义为我的面向外部的合同。那就是我编写了一些代码,这些代码适用于我的调用者给我的对象。我告诉他们我需要满足这个接口的东西。我不会对如何做任何陈述,只是给我一些可以计算价格的东西。

AbstractClass 更利于实现某些代码的人。我们有效地提供了部分书面课程,然后要求编码人员“填空”。我们可以有效地做到这一点的情况往往更加罕见。

【讨论】:

  • 我对接口做同样的事情,我使用的接口远远超过抽象类。谢谢你的解释,有道理。所以你会开始使用接口,直到你以后需要一个抽象类?
  • 或多或少。如果接口实现者需要帮助,我认为抽象类很有用。
【解决方案2】:

接口可能是您给出的示例中的最佳选择,但如果扩展示例,这可能会发生变化。

广义上讲,接口和抽象类都可以用来强制执行契约——即,实现契约的类型应该以某种方式表现。主要区别在于接口只是说明实现类型应该能够做什么,而抽象类除了合约之外还可以共享功能。

您的书示例可以扩展为具有在所有类型的Book 中具有相同实现的额外功能,在这种情况下,您可能希望使用抽象类。例如,如果我想共享一个 getISBN() 方法,但实现在实现契约的类型之间没有改变,那么使用抽象类可能更有意义。

限制是您只能在任何给定类型上实现单个抽象类,但您可以实现任意数量的接口。

我见过一些例子,其中抽象类实现接口,具体类实现抽象类 - 这样,您就可以两全其美;第 3 方不必耦合到抽象类上 getISBN() 的实现。

另一个切线的一点是,一些模拟库将难以模拟非虚拟方法,这包括抽象类上的方法 - 但是,它们可以与接口完美配合。

作为 TLDR:接口适用于您对 如何 实现是完全不感兴趣的类型,而您只关心类型具有某些特性。当您关心类的某些部分如何实现而不关心其他部分时,请使用抽象类。

【讨论】:

    【解决方案3】:

    在您的情况下,使用interface 而不是abstract class 会更合适。我这么说是因为你没有提供你的方法的任何实现,以后可能会被继承你的abstract class 的类覆盖。你想要的只是CorporateBookStudentBook 有一个名为CalculatePrice 的方法,它具有相同的签名。因此,您可以定义一个名为

    interface
    public interface IPriceCalculator
    {
        public double CalculatePrice(double price);
    }
    

    然后让你的类实现这个interface

    class StudentBook : Book, IPriceCalculator
    {
        public double CalculatePrice(double price)
        {
            return price - (price * 20);
        }
    }
    

    class CorporateBook : Book, IPriceCalculator
    {
        public override double CalculatePrice(double price)
        {
            return price - (price * 10);
        }
    }
    

    另一方面,我会建议另一种计算价值的方法:

    public interface IPriceCalculator
    {
         public double CalculatePrice(double price);
    }
    
    public class PriceCalculator
    {
        public double Discount { get; private set; }
    
        public PriceCalculator(double discount)
        {
            Discount = discount;
        }
    
        public double CalculatePrice(double price)
        {
            return price - (price*Discount)
        }
    }
    

    然后将IPriceCalculator 类型的对象注入Book 构造函数。

    public class Book
    {
        // The initial price.
        public double Price { get; private set; }
        public IPriceCalculator PriceCalculator { get; private set; } 
    
        public Book(double price, IPriceCalculator priceCalculator)
        {
            Price = price;
            PriceCalculator = priceCalculator;
        }
    
        public double CalculatePrice()
        {
            return PriceCalculator.CalculatePrice(Price);
        }
    }
    

    最后,

    class StudentBook : Book
    {
        public StudentBook(double price, IPriceCalculator priceCalculator) : 
            base(double price, IPriceCalculator priceCalculator)
        {
    
        }
    }
    
    class CorporateBook : Book
    {
        public CorporateBook(double price, IPriceCalculator priceCalculator) : 
            base(double price, IPriceCalculator priceCalculator)
        {
    
        }
    }
    

    然后创建您选择的PriceCalculator 并将它们传递给StudentBookCorporateBook 的构造函数。

    【讨论】:

      【解决方案4】:

      在 C# 中,使用抽象类与接口的区别主要在于 CLS 语言中对多态性的限制。在您给出的示例中,由于CalculatePrice 的两个实现非常简单,因此使用抽象类而不是接口将多态约束添加到Book 的所有派生类中,几乎没有任何收益。

      我知道这是一个高度简化的示例,但希望本书将说明如何计算书的价格根本不属于书内。 S.O.L.I.D.的第一原则是单一职责。这是迄今为止最重要的。计算其价格的图书类(和衍生品)为图书增加了第二个责任(我假设包含内容是图书的另一个主要责任)。这违反了第一原则。 [它还违反了其他 OOP “规则”,例如高级凝聚力,但这是另一个话题]。

      如果您想为 book 类提供价格计算的访问权限,您可以在 book 中使用一个单独的计算类:

      public interface IBookPriceCalculator
      {
          double CalculatePrice(double price);
      }
      
      public class StudentBookPriceCalculator : IBookPriceCalculator
      {
          public double CalculatePrice(double price)
          {
              return price - (price * 0.20);
          }
      }
      
      public class StudentBook
      {
          IBookPriceCalculator _priceCalculator;
      
          public StudentBook()
          {
              _priceCalculator = new StudentBookPriceCalculator();
          }
      
          public double BasePrice { get; set; }
      
          public double GetPrice()
          {
              return _priceCalculator.CalculatePrice(BasePrice);
          }
      }
      

      【讨论】:

      • 如果这本书没有更多的功能(我知道它会是一本废书,但对我来说毫无意义)那么这将是不必要的,对吧?就目前而言,它只有一个改变的理由,但是一旦我们添加另一种方法,它就有超过 1 个?目前它不违反该规则
      • 对。这就是为什么我写道,我假设这本书的首要(主要)责任是包含内容 - 文本和图像。
      【解决方案5】:

      答案取决于一些因素,例如常见行为和可扩展性级别。我将解释它在这里创建一个虚构的社交网络概念,因此对我们来说,社交网络是可以发布带有图像的消息并保存发布消息的历史的东西。然后我们的社交网络将共享行为,因此我将创建一个基类(抽象类)。

      public abstract class SocialNetwork
      {
          public List<string> History { get; private set; }
      
          protected SocialNetwork()
          {
              History = new List<string>();
          }
      
          public void Post(string comment, byte[] image)
          {
              DoPost(comment, image);
              History.Add(comment);
          }
      
          protected virtual void DoPost(string comment, byte[] image)
          {
          }
      }
      

      现在我将创建我们的社交网络:facebook 和 twitter

      public class Facebook : SocialNetwork
      {
          protected override void DoPost(string comment, byte[] image)
          {
              //Logic to do a facebook post
          }
      }
      
      public class Twitter : SocialNetwork
      {
          protected override void DoPost(string comment, byte[] image)
          {
              //Logic to do a twitter post
          }
      }
      

      到目前为止,一切看起来都很好。好吧,想象一下我们必须处理一种完全不同的社交网络,例如一些不存储消息历史的社交网络,比如 Snapchat:

      public class Snapchat : SocialNetwork
      {
          private string _lastMessage;
      
          protected override void DoPost(string comment, byte[] image)
          {
              //Logic to do a snapchat post
              _lastMessage = comment;
              ProcessLastMessage();
              History.Clear();
          }
      
          private void ProcessLastMessage()
          {
              //Some logic here.
          }
      }
      

      您可以在上面注意到 Snapchat 类继承自 SocialNetwork 类,因此 Snapchat 类也将存储帖子的历史记录。但是我们不想要它,所以我们必须放代码来清除历史列表。

      接口发挥作用 上面实现的问题是 Snapchat 有一个他不需要的东西,即历史,所以我们在这里需要更高级别的抽象,SocialNetwork 基类是我们所知道的普通社交网络,但我们需要一个超级抽象来定义SocialNetwork 在没有为其定义任何行为的情况下做什么,所以我们需要定义一个接口。

      public interface ISocialNetwork
      {
          void Post(string message, byte[] image);
      }
      

      现在我们将做 SocialNetwork 类来实现 ISocialNetwork:

      public abstract class SocialNetwork : ISocialNetwork
      {
          ...     
          public void Post(string comment, byte[] image)
          {
              ...
          }
          ...
      }
      

      现在这是新的 Snapchat 类:

      public class Snapchat : ISocialNetwork
      {
          private string _lastMessage;
      
          public void Post(string message, byte[] image)
          {
              //Logic to do a snapchat post
              _lastMessage = message;
              ProcessLastMessage();
          }
      
          private void ProcessLastMessage()
          {
              //Some logic here.
          }
      }
      

      现在设计已经足够强大了。 Facebook 和 Twitter 共享来自 SocialNetwork(抽象类)的共同行为,并实现 ISocialNetwork(接口)。 Snapchat 类不与 Facebook 和 Twitter 共享任何行为,但它也是一个社交网络,因此它直接实现 ISocialNetwork 接口。

      您可以从这里阅读全文:http://www.theagilethinker.com/2015/08/22/an-interesting-example-of-convivence-between-abstract-classes-and-interfaces/

      【讨论】:

        猜你喜欢
        • 2021-12-11
        • 2013-01-19
        • 2017-11-15
        • 1970-01-01
        • 2013-07-21
        • 1970-01-01
        • 2016-12-30
        • 2023-03-03
        • 2015-06-27
        相关资源
        最近更新 更多