我试图了解何时应该使用接口与抽象类。我正在考虑改进我的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);
}
}
我在查看此解决方案时遇到的问题是:
感谢您帮助理解这个
答案 0 :(得分:4)
该示例是人为的,因为Book基类没有行为,它也可以是一个接口。然而,一个更现实的例子会有许多其他方法,如
getAuthor()
getISBN()
isFiction()
如果这本书是学生或公司,那么这些行为可能不会改变,所以我们有一个有很多标准行为的基类。所以Book真的会成为一个类,因为它具有由派生类共享的有用行为。
当你有几组行为时,事情会变得复杂一些,例如,图书馆书是一本书,但它也是一个LendableThing,在Java中,你不能从两个不同的基类继承。
在实践中,我发现有比我做Abstract Base类更多的接口。我将Interfaces定义为我面向外的合同。那就是我写了一些代码,这些代码适用于我的调用者给我的对象。我告诉他们我需要一些能够满足这个界面的东西。我没有对如何做任何陈述,只要给我一些可以计算价格的东西。
AbstractClass更有利于实现某些代码的人。我们有效地提供了一个部分写的课程,然后要求编码员“填补空白”。我们可以做到这一点的情况往往更为罕见。
答案 1 :(得分:2)
在您的情况下,使用interface
而不是abstract class
会更合适。我这样说,因为你没有提供你的方法的任何实现,以后可能被继承你abstract class
的类覆盖。您希望CorporateBook
和StudentBook
拥有一个名为CalculatePrice
的方法具有相同的签名。因此,您可以定义一个名为<{p}的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
并将其传递给StudentBook
和CorporateBook
的构造函数。
答案 2 :(得分:2)
接口可能是您给出的示例中的最佳选项,但如果示例扩展可能会更改。
从广义上讲,接口和抽象类都可以用来强制执行合同 - 即,实现合同的类型应该以某种方式运行。主要区别在于界面只是说实现类型应该能够做什么,而抽象类除了合同之外还能够共享功能。
您的书籍示例可以扩展为具有在所有类型的Book
中具有相同实现的额外功能,在这种情况下,您可能希望使用抽象类。例如,如果我想共享一个getISBN()
方法,但实现没有实现合同的类型,那么使用抽象类可能更有意义。
限制是您只能在任何给定类型上实现单个抽象类,但您可以根据需要实现任意数量的接口。
我已经看到了一些抽象类实现接口的示例,具体类实现了抽象类 - 这样,您就可以获得两全其美的效果;第三方不必与您在抽象类上getISBN()
的实现相关联。
另一个切入点是,一些模拟库将难以模拟非虚方法,这包括抽象类的方法 - 但是,它们可以很好地与接口一起工作。
作为TLDR:接口适用于您完全不了解如何实现的类型,您只关心某个类型具有某些功能。当您关心如何实现类的某些部分而不是其他部分时,请使用抽象类。
答案 3 :(得分:1)
在C#中,使用抽象类与接口之间的区别主要在于CLS语言中对多态性的限制。在您给出的示例中,因为CalculatePrice
的两个实现非常简单,使用抽象类而不是接口会将多态约束添加到Book
的所有派生,并且几乎不会带来任何收益。 / p>
我知道这是一个高度简化的例子,但希望这本书将展示这本书的价格计算根本不属于本书的内容。 S.O.L.I.D.的第一个原则是单一责任。这是迄今为止最重要的。计算其价格的书类(和衍生物)增加了对该书的第二责任(我假设包含内容是另一本书,主要是本书的责任)。这违反了第一个原则。 [它也违反了其他OOP“规则”,如高级凝聚力,但这是另一个主题]。
如果您想要提供书籍类价格计算的访问权限,您可以在书中使用单独的计算类:
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);
}
}
答案 4 :(得分:1)
答案取决于一些因素,如常见行为和可扩展性水平。我将解释它在这里创建一个虚构的社交网络概念,因此对于我们来说,社交网络可以发布带有图像的消息并保存已发布消息的历史记录。然后我们的社交网络将共享行为,因此我将创建一个基类(抽象类)。
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接口。