我们首先来看下抽象class能发挥优势的使用场景。  

  假设有一个Cars基类,具体型号的Car继承该基类,并实现自己独有的属性或方法。

  1. public class Cars
  2. {
  3. public string Wheel()
  4. {
  5. return "I have 4 wheeler";
  6. }
  7. }

  有两种具体型号的汽车CarA和CarB均继承自Cars基类。也即它们拥有Cars基类的属性和方法。现在有一个需求,即需要添加一些对CarA和CarB类通用(commen)的但各自的实现不同的方法,比如colors方法,我们如何做到这一点呢?下面罗列了可能想到的解决方法。

  1. 直接在Cars基类中添加方法,让CarA和CarB继承
  2. 新创建一个interface,让CarA和CarB继承Cars类和该interface
  3. 新创建一个抽象class,让CarA和CarB仅继承该抽象class

  下面论证每种方案的可行性。

  1. 对于方案1而言,由于普通的Cars基类中仅能添加有共同实现(common implementation)的普通(normal)方法,故不能满足需求。
  2. 方案2可行,但需要子类继承interface和Cars基类,具体实现如下:
  1. interface IExtra
  2. {
  3. string colors();
  4. }

  3. 方案3可行,将具有共同实现的方法定义为普通方法,将通用但各自实现不同的方法定义为抽象方法,这两种方法均封装在抽象class中,子类只需继承该抽象class。另外,该抽象基类中还可以添加一些公共fields,这是interface所不能实现的。

  1. public abstract class Cars
  2. {
  3. //将公有但实现(implementation)不同的功能声明为抽象方法
  4. public abstract string colors();
  5.  
  6. //将公有且实现(implementation)相同的功能声明为普通方法
  7. public string Wheel()
  8. {
  9. return "4 wheeler";
  10. }
  11. }

  从以上代码实现可以看到,使用抽象class能封装字段,公有且实现相同的方法和公有但实现不同的方法(抽象方法),其作用相当于一个普通class和interface。故当我们需要实现一个包含公有且实现相同的方法和公有但实现不同的方法的基类(或许还可能包含字段)时,应该首先考虑创建抽象class,然后才是interface(需要和一个普通class配合来达到和抽象class相同的效果)。

  现在,我们来看下抽象class的第二种用途。

  还是拿上面的Cars基类举例,我们让CarA类继承Cars基类,并添加自己的DataRecorder方法。本来用户能通过创建CarA类的实例对象来访问CarA类和Cars基类的属性和方法,但这里的主要问题是允许创建Cars基类的实例对象,但基类对象不能够访问子类独有的属性和方法,这并不满足我们的要求。为了限制这一点,我们应禁止在子类中创建基类的对象,仅允许创建子类对象来同时访问子类和基类的属性和方法。

  接下来我们讨论接口interface的使用场景,在讨论之前,我们先看一个OOP中存在的多继承(Multiple Inheritance)问题。

  在C++中存在多继承(Multiple Inheritance)的概念,但是多继承存在一个严重的问题,即Diamond Problem。

  1. public class print1
  2. {
  3. public void print()
  4. {
  5. Console.WriteLine("hello");
  6. }
  7. }
  8.  
  9. public class print2
  10. {
  11. public void print()
  12. {
  13. Console.WriteLine("world");
  14. }
  15. }
  16. class Program : print1, print2
  17. {
  18. static void Main(string[] args)
  19. {
  20.  
  21. }
  22. }

  以上就是多继承(Multiple Inheritance)的例子,该例子不能通过编译,因为在class Program中,如果调用继承的print方法,系统将不能确定到底调用从class print1还是从class print2继承来的print方法,从而引发程序错误。通过使用interface可以解决这个问题。

  1. public interface print1
  2. {
  3. void print();
  4. }
  5. public interface print2
  6. {
  7. void print();
  8. }
  9. class Program : print1, print2
  10. {
  11. void print1.print()
  12. {
  13. Console.WriteLine("hello");
  14. }
  15. void print2.print()
  16. {
  17. Console.WriteLine("world");
  18. }
  19. static void Main(string[] args)
  20. {
  21. Program p = new Program();
  22. ((print2)p).print();
  23. Console.ReadLine();
  24. }
  25. }

  现在,我们看看interface如何在实际开发场景中使用interface。还是拿上面的Cars类例子来讲,现在CarA要添加行车记录仪新功能DataRecorder,但CarB不具备该功能。如何实现这一需求?

  对于这一新需求,我们通常能想到以下4种解决方案:

  1. 创建一个新的普通class,该class定义来DataRecorder方法,然后让CarA类继承该新class
  2. 直接在Cars类中添加DataRecorder方法,并将该方法声明为abstract方法
  3. 创建一个抽象class,该抽象class定义一个抽象DataRecorder方法,然后让CarA类继承该抽象class并实现DataRecorder方法
  4. 直接在CarA类中创建DataRecorder方法并使用它
  5. 利用interface实现

  下面依次验证每个解决方案的可行性。

  1. 对于方案1来讲,由于CarA已经继承了Cars基类,此时不能同时继承新创建的普通class,故此方案不可行。
  2. 对于方案2来讲,当CarB继承Cars类时也必须override该DataRecorder方法,这违背了我们的需求。
  3. 方案3和方案1情形相同。
  4. 方案4似乎是能想到的最直接的方式,简单粗暴。这个方案在当添加的方法较少且我们能保证记住每个要添加的方法情形下工作良好,但是当添加的方法较多时,若我们忘记添加其中一两个方法时,没有一种机制通知我们这种疏忽。若没有语法错误,程序就能编译通过,但执行时不能得到期望输出。
  5. 方案5能克服上述方案的缺点,可行,代码实现见下面:
  1. public class Cars
  2. {
  3. public string Wheel()
  4. {
  5. return "I have 4 wheeler";
  6. }
  7. }
  1. interface IDataRecorder
  2. {
  3. void DataRecorder();
  4. }
  1. interface IDataRecorder
  2. {
  3. void DataRecorder();
  4. }
  5. public class CarA:Cars, IDataRecorder
  6. {
  7. public void DataRecorder()
  8. {
  9. Console.WriteLine("DataRecorder supported.");
  10. }
  11.  
  12. static void Main(string[] args)
  13. {
  14. CarA car = new CarA();
  15.  
  16. Console.WriteLine(car.Wheel());
  17. car. DataRecorder();
  18. Console.ReadLine();
  19. }
  20. }

  以上方案5代码实现克服了方案1和3多继承(Multiple Inheritance)报错问题,同时克服了方案2中CarB类必须override DataRecorder方法的缺陷,并且在后期维护添加多个方法时由于疏忽导致忘记添加某些方法时,能通过编译错误提醒开发者,从而克服了方案4的问题。

  除了从使用场景等表象层区分抽象类和接口,我们还能从以下三个更接近本质的方面对接口和抽象类进行区分:

  • 类是对对象的抽象,抽象类是对类的抽象,接口是对行为的抽象。接口是对类的局部(行为)进行的抽象,而抽象类是对类整体(字段、属性、方法)对抽象。如果只关心行为抽象,那么也可以认为接口就是抽象类。
  • 如果行为跨越不同类的对象,可使用接口;对于一些相似的类对象,用继承抽象类。抽象类本质上表达的是“is-A”(是...的一种)的关系,而接口本质上表达的是can-do的关系,即实现了接口就具备了某种功能。实现接口和继承抽象类并不冲突。
  • 从设计角度讲,抽象类是从子类中发现了公有的东西,然后进行提取形成抽象类,然后子类继承父类,而接口根本不知道子类的存在,方法如何实现还不确定,预先定义。可以说,抽象类自底而上抽象出来的,而接口是自顶而下设计出来的。

  具体展开第三点来讲,就是说往往我们在开发一个大型应用时,事先定义一个类,之后某个时间点随着功能的增加又添加了一个类,此时发现该类和事先定义的一个类有较多类似之处,此时就可以泛化出抽象类,让子类继承抽象类,复用代码的同时也提升了应用后期的可维护性。这也体现了敏捷开发的思想,通过重构改善既有代码的设计。事实上,在只有一个类时,就去考虑定义抽象类,极有可能造成过度设计。所以说抽象类往往都是通过重构得来的。当然,如果你事先就意识到多种分类的可能,提前设计出抽象类也是完全可以的。

接口和抽象类的使用场景以及多类继承存在的问题(c#)的更多相关文章

  1. 从接口、抽象类到工厂模式再到JVM来总结一些问题

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习! 涉及到的知识点总结如下: 为什么使用接口? 接口和抽象类的区别 简单工厂模式总结 Java中new和newInstance的区别 J ...

  2. Java基础——关于接口和抽象类的几道练习题

    呃,一定要理解之后自己敲!!!这几道题,使我进一步了解了接口和抽象类. 1.设计一个商品类 字段: 商品名称,重量,价格,配件数量,配件制造厂商(是数组,因为可能有多个制造厂商) 要求: 有构造函数 ...

  3. C#_接口与抽象类

    .Net提供了接口,这个不同于Class或者Struct的类型定义.接口有些情况,看似和抽象类一样,因此有些人认为在.Net可以完全用接口来替换抽象类.其实不然,接口和抽象类各有长处和缺陷,因此往往在 ...

  4. java中接口与抽象类的区别

    接口和抽象类的共同特征如下: 接口和抽象类都不能被实例化,位于继承树的顶端,用于被其他类实现和继承. 接口和抽象类都可以包含抽象的方法,实现接口的类或者继承抽象类的类都必须实现这些抽象的方法. 区别: ...

  5. Java 中接口和抽象类的 7 大区别!

    本文已收录<Java常见面试题>:https://gitee.com/mydb/interview ​ Java 是一门面向对象的编程语言,面向对象的编程语言有四大特征:抽象.封装.继承和 ...

  6. [drp 6]接口和抽象类的区别,及其应用场景

    导读:在很多时候,接口和抽象类可以替换.发现这个问题,还是之前学习设计模式的时候,看到那个UML图发现的.那么,究竟在什么时候使用接口,什么时候使用抽象类呢?现在结合这个项目,做一个总结. 一.接口 ...

  7. C#基础系列——一场风花雪月的邂逅:接口和抽象类

    前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的.那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗 ...

  8. [JAVA设计模式]第一部分:接口、抽象类、设计原则

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  9. Java:接口和抽象类,傻傻分不清楚?

    01. 来看网络上对接口的一番解释: 接口(英文:Interface),在 Java 编程语言中是一个抽象类型,是抽象方法的集合.一个类通过继承接口的方式,从而来继承接口的抽象方法. 兄弟们,你们怎么 ...

随机推荐

  1. 精《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #8 调度策略

    HACK #8 调度策略 本节介绍Linux的调度策略(scheduling policy).Linux调度策略的类别大致可以分为TSS(Time Sharing System,分时系统)和实时系统这 ...

  2. Vim插件之ale,LeaderF,completor.vim(win10)配置

    内容包含 vim-plug,异步插件管理,总之就是下起来快. ale,异步语法检查 LeaderF,快速查找文件 completor.vim vim8的快速补全 markdown预览 common s ...

  3. AES 加密算法 跨语言

    aes加密算法 delphi .java.c# .网页在线工具 4个相同 AES/ECB/PKCS5Padding 与网页在线工具加密结果相同 http://tool.chacuo.net/crypt ...

  4. Linux Makefile 教程(转)

    原文地址:http://blog.csdn.net/liang13664759/article/details/1771246 ------------------------------------ ...

  5. 规则引擎以及blaze 规则库的集成初探之二——JSR94 的规则引擎API和实现

    http://jefferson.iteye.com/blog/67839 规则引擎以及blaze 规则库的集成初探之二——JSR94 的规则引擎API和实现

  6. nice & renice

    [nice & renice & getpriority & setpriority] 1.nice & renice 参考:http://man.ddvip.com/ ...

  7. This usually happens because your environment has changed since running `npm install`

    此时运行按照提示执行  npm rebuild node-sass  命令,(如若不行,则先运行npm install node-sass命令执行) 然后再运行 node命令,启动服务.

  8. LinuxC编程怎么MakeFile

    在linux下我们都知道可以利用命令gcc hello.c -o hello 命令来变异c语言程序.其中gcc hello.c -o hello中 hello是给这个编译后生成的可执行文件取个别名 再 ...

  9. oracle中如何修改process

    转自https://blog.csdn.net/qq_35686181/article/details/52350922 oracle中修改process  在 oracle中,要经常查看proces ...

  10. PEAR

    简介:pear是php扩展与应用库(the php extension and application repository)的缩写.它是一个php扩展及应用的一个代码仓库. 编码规范:参考(http ...