Interface vs Abstract Class with an empty method
我试图理解什么时候应该使用接口和抽象类。我在研究如何改进我的MVC应用程序设计,并看到了这篇文章:http://www.codeproject.com/articles/822791/developing-mvc-applications-using-solid-principles
在关于OCP的章节中,作者给出了一个关于书籍价格计算的例子。原始代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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; } } |
他的解决方案如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } } |
号
查看此解决方案时,我的问题是:
谢谢你帮我理解这个
这个例子是人为的,因为Book基类没有行为,它也可以是一个接口。然而,一个更现实的例子有许多其他的方法,例如
1 2 3 | getAuthor() getISBN() isFiction() |
如果这本书是学生或公司的话,这些行为可能不会改变,所以我们有一个有很多标准行为的基础班。所以这本书真的是一个类,因为它有一些由派生类共享的有用行为。
当你有好几组行为时,事情会变得更复杂一些,比如图书馆的书是一本书,但它也是一个重要的东西,在Java中你不能从两个不同的基类继承。
在实践中,我发现接口比抽象基类多。我将接口定义为我的面向外部的契约。也就是说,我编写了一些代码来处理调用方提供给我的对象。我告诉他们我需要满足这个接口的东西。我并没有说明这是怎么做到的,只要给我一些可以计算价格的东西。
AbstractClass更适合于实现某些代码的人。我们实际上是给一个部分编写的类,然后要求编码人员"填补空白"。我们能够有效地做到这一点的情况往往更加罕见。
在您给出的示例中,接口可能是最好的选择,但是如果扩展示例,这可能会改变。
一般来说,接口和抽象类都可以用来执行契约——即,实现契约的类型应该以某种方式运行。主要的区别在于,一个接口仅仅说明了实现类型应该能够做什么,而抽象类除了契约之外还能够共享功能。
您的书示例可以扩展为在所有类型的
限制是您只能在任何给定类型上实现一个抽象类,但是您可以实现任意多的接口。
我已经看到了一些例子,其中抽象类实现了接口,具体类实现了抽象类——这样,您就可以充分利用这两个领域;第三方不必与您在抽象类上实现
另一个要点是,一些模拟库将与模拟非虚拟方法作斗争,这包括抽象类上的方法——但是,它们将与接口完美结合。
作为一个tldr:interfaces是为那些根本不关心如何实现的类型而设计的,您只关心类型具有某些特性。当您关心类的某些部分是如何实现的而不是其他部分时,请使用抽象类。
在您的情况下,使用
1 2 3 4 | public interface IPriceCalculator { public double CalculatePrice(double price); } |
号
然后让您的类实现这个
1 2 3 4 5 6 7 | class StudentBook : Book, IPriceCalculator { public double CalculatePrice(double price) { return price - (price * 20); } } |
和
1 2 3 4 5 6 7 | class CorporateBook : Book, IPriceCalculator { public override double CalculatePrice(double price) { return price - (price * 10); } } |
。
另一方面,我建议采用另一种方法来计算该值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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) } } |
然后将类型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } } |
。
最后,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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) { } } |
。
你创建了你选择的
答案取决于一些因素,如常见行为和可扩展性级别。我将在这里解释它创造了一个虚拟的社会网络概念,所以对我们来说,社会网络是一种可以用图像发布信息并保存已发布信息历史的东西。然后我们的社交网络将共享行为,所以我将创建一个基类(抽象类)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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基类是我们知道的一个正常的社会网络,但是我们需要一个超抽象来定义一个社会网络做什么,而不定义它的任何行为,所以我们需要定义一个接口。
1 2 3 4 | public interface ISocialNetwork { void Post(string message, byte[] image); } |
现在,我们将进行socialNetwork类来实现isocialNetwork:
1 2 3 4 5 6 7 8 9 | public abstract class SocialNetwork : ISocialNetwork { ... public void Post(string comment, byte[] image) { ... } ... } |
。
下面是新的Snapchat类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(abstract类)的公共行为,并实现了isocialnetwork(interface)。Snapchat类不与Facebook和Twitter共享任何行为,但它也是一个社交网络,因此它直接实现等对话网络接口。
你可以从这里阅读全文:http://www.theagilethinker.com/2015/08/22/an-interest-example-of-convivence-between-abstract-classes-and-interfaces/
在C中,使用抽象类与使用接口的区别主要在于对CLS语言多态性的限制。在您给出的示例中,由于
我知道这是一个高度简化的例子,但希望这本书能说明,计算出的书的价格根本不属于这本书。S.O.L.I.D.的第一个原则是单一责任。这是迄今为止最重要的。计算其价格的Book类(和派生类)为Book增加了第二个责任(我假设包含内容是书籍的另一个责任,也是主要责任)。这违反了第一条原则。[它还违反了其他OOP"规则",如高级内聚,但这是另一个主题]。
如果要提供对Book类的价格计算的访问权限,则可以使用Book中的单独计算类:
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 | 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); } } |