对抽“.NET研究”象编程:接口和抽象类

简介:   1. 引言  在我之前的一篇post 《抽象类和接口的谁是谁非 》中,和同事管伟的讨论,得到很多朋友的关注,因为是不成体系的论道,所以给大家了解造成不便,同时关于这个主题的系统性理论,我认为也有必要做以总结,因此才有了本篇的新鲜出炉。

  1. 引言

  在我之前的一篇post 《抽象类和接口的谁是谁非 》中,和同事管伟的讨论,得到很多朋友的关注,因为是不成体系的论道,所以给大家了解造成不便,同时关于这个主题的系统性理论,我认为也有必要做以总结,因此才有了本篇的新鲜出炉。同时,我将把上贴中的问题顺便也在此做以交代。

  2. 概念引入

  •       什么是接口?

  接口是包含一组虚方法的抽象类型,其中每一种方法都有其名称、参数和返回值。接口方法不能包含任何实现,CLR 允许接口可以包含事件、属性、索引器、静态方法、静态字段、静态构造函数以及常数。但是注意:C# 中不能包含任何静态成员。一个类可以实现多个接口,当一个类继承某个接口时,它不仅要实现该接口定义的所有方法,还要实现该接口从其他接口中继承的所有方法。

  定义方法为:

 
 
1 . public interface System.IComparable
2 . {
3 . int CompareTo( object o);
4 . }
5 .
6 . public class TestCls: IComparable
7 . {
8 . public TestCls()
9 . {
10 . }
11 .
12 . private int _value;
13 . public int Value
14 . {
15 . get { return _value; }
16 . set { _value = value; }
17 . }
18 .
19 . public int CompareTo( object o)
20 . {
21 .
22 . // 使用as模式进行转型判断
23 . TestCls aCls = o as TestCls;
24 . if (aCls != null )
25 . {
26 .
27 . // 实现抽象方法
28 . return _value.CompareTo(aCls._value);
29 . }
30 . }
31 . }
  •        什么是抽象类?

  抽象类提供多个派生类共享基类的公共定义,它既可以提供抽象方法,也可以提供非抽象方法。抽象类不能实例化,必须通过继承由派生类实现其抽象方法,因此对抽象类不能使用new 关键字,也不能被密封。如果派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。另外,实现抽象方法由overriding 方法来实现。

  定义方法为:

 
 
1 . /// <summary>
2 . /// 定义抽象类
3 . /// </summary>
4 . abstract public class Animal
5 . {
6 . // 定义静态字段
7 . protected int _id;
8 .
9 . // 定义属性
10 . public abstract int Id
11 . {
12 . get ;
13 . set ;
14 . }
15 .
16 . 上海徐汇企业网站制作 // 定义方法
17 . public abstract void Eat();
18 .
19 . // 定义索引器
20 . public string this [ int i]
21 . {
22 . get ;
23 . set ;
24 . }
25 . }
26 .
27 . /// <summary>
28 . /// 实现抽象类
29 . /// </summary>
30 . public class Dog: Animal
31 . {
32 . public override int Id
33 . {
34 . get { return _id;}
35 . set {_id = value;}
36 . }
37 .
38 . public override void Eat()
39 . {
40 . Console.Write( " Dog Eats. " )
41 . }
42 . }

  3. 相同点和不同点

  3.1 相同点

  • 都不能被直接实例化,都可以通过继承实现其抽象方法。
  • 都是面向抽象编程的技术基础,实现了诸多的设计模式。

  3.2 不同点

  • 接口支持多继承;抽象类不能实现多继承。
  • 接口只能定义抽象规则;抽象类既可以定义规则,还可能提供已实现的成员。
  • 接口是一组行为规范;抽象类是一个不完全的类,着重族的概念。
  • 接口可以用于支持回调;抽象类不能实现回调,因为继承不支持。
  • 接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法;抽象类可以定义字段、属性、包含有实现的方法。 
  • 接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。例如,Struct 就可以继承接口,而不能继承类。

  通过相同与不同的比较,我们只能说接口和抽象类,各有所长,但无优略。在实际的编程实践中,我们要视具体情况来酌情量才,但是以下的经验和积累,或许能给大家一些启示,除了我的一些积累之外,很多都来源于经典,我相信经得起考验。所以在规则与场合中,我们学习这些经典,最重要的是学以致用,当然我将以一家之言博大家之笑,看官请继续。

  3.3 规则与场合

  • 请记住,面向对象思想的一个最重要的原则就是:面向接口编程。
  • 借助接口和抽象类,23 个设计模式中的很多思想被巧妙的实现了,我认为其精髓简单说来就是:面向抽象编程。
  • 抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
  • 接口着重于CAN-DO 关系类型,而抽象类则偏重于IS-A 式的关系;
  • 接口多定义对象的行为;抽象类多定义对象的属性; 
  • 接口定义可以使用public 、protected 、internal 和private 修饰符,但是几乎所有的接口都定义为public ,原因就不必多说了。
  •  “ 接口不变” ,是应该考虑的重要因素。所以,在由接口增加扩展时,应该增加新的接口,而不能更改现有接口。
  • 尽量将接口设计成功能单一的功能块,以.NET Framework 为例,IDisposable 、IDisposable 、IComparable 、IEquatable 、IEnumerable 等都只包含一个公共方法。
  • 接口名称前面的大写字母“I” 是一个约定,正如字段名以下划线开头一样,请坚持这些原则。
  • 在接口中,所有的方法都默认为public 。 
  • 如果预计会出现版本问题,可以创建“ 抽象类” 。例如,创建了狗(Dog )、鸡(Chicken )和鸭(Duck ),那么应该考虑抽象出动物(Animal )来应对以后可能出现风马牛的事情。而向接口中添加新成员则会强制要求修改所有派生类,并重新编译,所以版本式的问题最好以抽象类来实现。
  • 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。
  • 对抽象类不能使用new 关键字,也不能被密封,原因是抽象类不能被实例化。
  • 在抽象方法声明中不能使用 static 或 virtual 修饰符。

  以上的规则,我就厚颜无耻的暂定为T14 条吧,写的这么累,就当一时的奖赏吧。大家也可以互通有无,我将及时修订。

  4. 经典示例

  4.1 绝对经典

  .NET Framework 是学习的最好资源,有意识的研究FCL 是每个.NET 程序员的必修课,关于接口和抽象类在FCL 中的使用,我有以下的建议:

  • FCL 对集合类使用了基于接口的设计,所以请关注System.Collections 中关于接口的设计实现;
  • FCL 对数据流相关类使用了基于抽象类的设计,所以请关注System.IO.Stream 类的抽象类设计机制。

  4.2 别样小菜

  下面的实例,因为是我的理解,因此给经典打上“ 相对” 的记号,至于什么时候晋升为“ 绝对” ,就看我在.NET 追求的路上,是否能够一如既往的如此执着,因此我将把相对重构到绝对为止(呵呵)。 本示例没有阐述抽象类和接口在设计模式中的应用,因为那将是另一篇有讨论价值的文本,本文着眼与概念和原则的把握,但是真正的应用来自于具体的需求规范。

设计结构如图所示:

  1. 定义抽象类

 
 
1 . public abstract class Animal
2 . {
3 . protected string _name;
4 .
5 . // 声明抽象属性
6 . public abstract string Name
7 . {
8 . get ;
9 . }
10 .
11 . // 声明抽象方法
12 . public abstract void Show();
13 .
14 . // 实现一般方法
15 . public void MakeVoice()
16 . {
17 . Console.WriteLine( " All animals can make voice! " );
18 . }
19 . }

  2. 定义接口

 
 
1 . public interface IAction
2 . {
3 上海闵行企业网站制作>. // 定义公共方法标签
4 . void Move();
5 . }

  3. 实现抽象类和接口

 
 
1 . public class Duck : Animal, IAction
2 . {
3 . public Duck( string name)
4 . {
5 . _name = name;
6 . }
7 .
8 . // 重载抽象方法
9 . public override void Show()
10 . {
11 . Console.WriteLine(_name + " is showing for you. " );
12 . }
13 .
14 . // 重载抽象属性
15 . public override string Name
16 . {
17 . get { return _name;}
18 . }
上海企业网站设计与制作le="color: #800080;">19 .
20 . // 实现接口方法
21 . public void Move()
22 . {
23 . Console.WriteLine( " Duck also can swim. " );
24 . }
25 .
26 . }
27 .
28 . public class Dog : Animal, IAction
29 . {
30 . public Dog( string name)
31 . {
32 . _name = name;
33 . }
34 .
35 . public override void Show()
36 . {
37 . Console.WriteLine(_name + " is showing for you. " );
38 . }
39 .
40 . public override string Name
41 . {
42 . get { return _name; }
43 . }
44 .
45 . public void Move()
46 . {
47 . 上海徐汇企业网站设计与制作 Console.WriteLine(_name + " also can run. " );
48 . }
49 .
50 . }

  4. 客户端实现

 
 
1 . public class TestAnmial
2 . {
3 . public static void Main( string [] args)
4 . {
5 . Animal duck = new Duck( " Duck " );
6 . duck.MakeVoice();
7 . duck.Show();
8 .
9 . Animal dog = new Dog( " Dog " );
10 . dog.MakeVoice();
11 . dog.Show();
12 .
13 . IAction dogAction = new Dog( " A big dog " );
14 . dogAction.Move();
15 . }
16 . }

  5. 他山之石

  正所谓真理是大家看出来的,所以将园子里有创新性的观点潜列于此,一是感谢大家的共享,二是完善一家之言的不足,希望能够将领域形成知识,受用于我,受用于众。

  • dunai 认为:抽象类是提取具体类的公因式,而接口是为了将一些不相关的类“ 杂凑” 成一个共同的群体。至于他们在各个语言中的句法,语言细节并不是我关心的重点。
  • 桦山涧 的收藏也很不错。
  • Artech 认为:所代码共用和可扩展性考虑,尽量使用Abstract Class 。当然接口在其他方面的优势,我认为也不可忽视。
  • shenfx 认为:当在差异较大的对象间寻求功能上的共性时,使用接口;当在共性较多的对象间寻求功能上的差异时,使用抽象基类。

  最后,MSDN 的建议是:

  • 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
  • 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
  • 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。
  • 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。

  6. 结论

  接口和抽象类,是论坛上、课堂间讨论最多的话题之一,之所以将这个老话题拿出来再议,是因为从我的体会来说,深刻的理解这两个面向对象的基本内容,对于盘活面向对象的抽象化编程思想至关重要。本文基本概况了接口和抽象类的概念、异同和使用规则,从学习的观点来看,我认为这些总结已经足以表达其核心。但是,对于面向对象和软件设计的深入理解,还是建立在不断实践的基础上,Scott 说自己每天坚持一个小时用来写Demo ,那么我们是不是更应该勤于键盘呢。对于接口和抽象类,请多用而知其然,多想而知其奥吧。

目录
相关文章
|
4月前
|
存储 JSON 开发工具
Visual Studio编程效率提升技巧集(提高.NET编程效率)
Visual Studio编程效率提升技巧集(提高.NET编程效率)
Visual Studio编程效率提升技巧集(提高.NET编程效率)
|
21天前
|
传感器 数据采集 物联网
探索.NET nanoFramework:为嵌入式设备编程的新途径
探索.NET nanoFramework:为嵌入式设备编程的新途
36 7
|
6月前
|
开发框架 .NET 中间件
七天.NET 8操作SQLite入门到实战 - (2)第七天Blazor班级管理页面编写和接口对接
七天.NET 8操作SQLite入门到实战 - (2)第七天Blazor班级管理页面编写和接口对接
134 7
|
3月前
|
大数据 开发工具 开发者
从零到英雄:.NET核心技术带你踏上编程之旅,构建首个应用,开启你的数字世界探险!
【8月更文挑战第28天】本文带领读者从零开始,使用强大的.NET平台搭建首个控制台应用。无论你是新手还是希望扩展技能的开发者,都能通过本文逐步掌握.NET的核心技术。从环境搭建到创建项目,再到编写和运行代码,详细步骤助你轻松上手。通过计算两数之和的小项目,你不仅能快速入门,还能为未来开发更复杂的应用奠定基础。希望本文为你的.NET学习之旅开启新篇章!
33 1
|
3月前
|
开发框架 前端开发 .NET
七天.NET 8操作SQLite入门到实战 - (3)第七天Blazor学生管理页面编写和接口对接
七天.NET 8操作SQLite入门到实战 - (3)第七天Blazor学生管理页面编写和接口对接
|
3月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
98 7
|
3月前
|
Java Spring 自然语言处理
Spring 框架里竟藏着神秘魔法?国际化与本地化的奇妙之旅等你来揭开谜底!
【8月更文挑战第31天】在软件开发中,国际化(I18N)与本地化(L10N)对于满足不同地区用户需求至关重要。Spring框架提供了强大支持,利用资源文件和`MessageSource`实现多语言文本管理。通过配置日期格式和货币符号,进一步完善本地化功能。合理应用这些特性,可显著提升应用的多地区适应性和用户体验。
40 0
|
3月前
|
传感器 数据采集 物联网
探索未来:.NET nanoFramework引领嵌入式设备编程革新之旅
【8月更文挑战第28天】.NET nanoFramework 是一款专为资源受限的嵌入式设备设计的轻量级、高性能框架,基于 .NET Core,采用 C# 进行开发,简化了传统底层硬件操作的复杂性,极大提升了开发效率。开发者可通过 Visual Studio 或 Visual Studio Code 快速搭建环境并创建项目,利用丰富的库和驱动程序轻松实现从基础 LED 控制到网络通信等多种功能,显著降低了嵌入式开发的门槛。
57 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
46 0
|
3月前
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
下一篇
无影云桌面