《Effective C#》读书笔记——条目22:通过定义并实现接口替代继承<使用C#表达设计>

简介:

接口是一种按照契约设计的方式,一个类型必须实行接口中定义的方法。抽象基类则为一组相关的类型提供了一个共有的抽象。要注意二者的使用场景和区别:基类描述了对象是什么;接口描述了对象将如何表现行为。

 

1.关于接口

   接口描述了一组功能,是一个契约,任何实现接口的类型必须为接口中定义的所有所有元素提供具体的实现。我们应该将可重用的行为提取出来,定义在接口中;由于不同相关的类型均可以实现一个接口,所有这会增加代码的重用率。对于开发者本身来说,实现接口要比继承自定义的基类更容易。

  

2.关于抽象基类

  抽象基类除了描述共同行为,抽象基类还可以为派生类提供一些具体的实现(为子类通过通用、可重用的代码)。抽象基类可以为任何具体行为提供一个实现,而接口则不能。这种实现重用的方式为我们提供了另一种好处:在扩展系统功能时,通过向基类中添加并实现某种功能,所有的派生类立即拥有该功能,而向接口中添加一个成员,则会破坏所有实现该接口的类

 

3.使用接口替代继承

   使用抽象基类还是接口,代表了对日后可能发生的变化两种不同的态度将一组功能封装在一个接口中,作为其他类型的实现契约。而基类则可以再日后进行扩展,这些扩展也会自动的成为子类的一部分

 

3.1通过扩展方法来模拟继承

  其实,这两种方式可以混合使用,也就是说:既可以让类型支持多个接口,也让其可以重用。我们知道,接口不能包含实现,也不能包含任何具体的数据成员。但是,扩展方法却是可以应用在接口上。例如:System.Linq.Enumerable类中就包含了30多个声明于IEnumerable<T>接口之上的扩展方法,所有实现了该接口的类型都会自动获得这些扩展方法自身的实现(注意:接口本身并不能包含任何实现,只是通过扩展方法的形式模拟地提供一些实现)。例如,下面的这个针对IEnumerable接口扩展方法:

复制代码
 1 public static class Extensions
 2  {
 3      /// <summary>
 4      /// 为IEnumerable<T>类型添加扩展方法
 5      /// </summary>
 6      /// <typeparam name="T"></typeparam>
 7      /// <param name="sequence"></param>
 8      /// <param name="action"></param>
 9      public static void ForAll<T>(this IEnumerable<T> sequence, Action<T> action)
10      {
11          foreach (T item in sequence)
12          {
13              action(item);
14          }
15      }
16  }
复制代码

所有实现了IEnumerable接口的类型,会自动获取这个方法的实现;同样的,如果一个类实现了IEnumerable接口,那么它也会获得该接口的所有扩展方法的实现

 

3.2 接口编程的灵活性

  在.NET 环境下的继承是单根继承,根据接口编程要比根据基类编程拥有更大的灵活性,因为一个类型可以实现多个接口。同样的不相关的类型也可以实现同一个接口,这对在为不相关类编写公有逻辑时,使用接口可以简化你的工作。看下面的示例:假设某个程序需要管理员工、客户、第三方员工,这些类型并不相关(从继承体系来看),但是他们有一些公有属性,例如:名称、地址、电话等等:

View Code

 

上面显示姓名属性,其他属性略过。现在我们可以把公共属性抽象成一个接口:

复制代码
public interface IContactInfo
{
    //姓名
    string Name{get;}
    //联系电话
    PhoneNumber primaryContact{get;}
    //传真
    PhoneNumber Fax{get;}
    //住址
    Address primaryAddress{get;}
}
复制代码

 

 我们将上面所以类都实现IContactInfo接口:

1 //..其他类 略..
2 public class Employee:IContactInfo
3 {
4     //...略
5 }

 

 现在我们需要编写一个为这些类型打印自身信息的新方法:

1 public void PrintMailingLabel(IContactInfo ic)
2 {
3     //...略
4 }

 我们可以看到所有的,只要是实现了该接口的类型都可以使用该方法,这得益于将我们公有逻辑放在了接口中。

  同时,使用接口定义类的API可会提供更好的灵活性,当类型的属性以类的形式暴露时,也就暴露了该类的所有接口。而若是以暴露某个接口,那么就可以选择仅为使用者提供那些必要的方法和属性。而实现该接口的类属性实现细节,可能会随着时间改变。

 

3.3 在结构中使用接口避免拆箱

  接口和抽象基类还有一个不同之处在于,抽象基类仅限于引用类型,而接口则没有这个限制。当我们将struct装箱时,该装箱对象实际上支持struct支持的所有接口。当通过接口指针来访问该struct时,我们不必拆箱即可访问到内部数据。如下面的例子:

复制代码
 1     public struct URLInfo : IComparable<URLInfo>, IComparable
 2     {
 3         private string URL;
 4         private string description;
 5 
 6         public int CompareTo(URLInfo other)
 7         {
 8             return URL.CompareTo(other.URL);
 9         }
10 
11         #region IComparable 成员
12 
13         int IComparable.CompareTo(object obj)
14         {
15             if (obj is URLInfo)
16             {
17                 URLInfo other = (URLInfo)obj;
18                 return CompareTo(other);
19             }
20             else
21             {
22                 throw new ArgumentException("比较的对象不是URLInfo类型");
23             }
24         }
25 
26         #endregion
27     }
复制代码

  由于URLInfo实现了IComparable<T>和IComparable接口,所有我们可以轻松的创建一个保护URLInfo对象的排序链表。即使在那些依赖老版本的IComparable的代码也会减少装箱和拆箱的次数,因为客户代码可以再不拆箱的情况下直接调用IComparable.CompareTo()。

 

小节

接口是一种按契约设计的方式:一个实现了某个接口的类型,必须提供接口中约定的所有方法实现。抽象基类则为一组项目类型提供了一个共用的共同抽象。仔细理解二者之间的差别,使用我们能够创建更富表现力和提高应对变化的设计。使用类层次来定义相关的类型,用接口暴露功能,并可以让不同类型实现这些接口。

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2013/01/15/EffectiveCSharp_22.html ,如需转载请自行联系原作者
相关文章
|
5月前
|
达摩院 Linux API
阿里达摩院MindOpt求解器V1.1新增C#接口
阿里达摩院MindOpt求解器发布最新版本V1.1,增加了C#相关API和文档。优化求解器产品是求解优化问题的专业计算软件,可广泛各个行业。阿里达摩院从2019年投入自研MindOpt优化求解器,截止目前经历27个版本的迭代,取得了多项国内和国际第一的成绩。就在上个月,2023年12月,在工信部产业发展促进中心等单位主办的首届能源电子产业创新大赛上,MindOpt获得电力用国产求解器第一名。本文将为C#开发者讲述如何下载安装MindOpt和C#案例源代码。
187 3
阿里达摩院MindOpt求解器V1.1新增C#接口
|
5月前
|
存储 C#
C#学习系列相关之数组(一)---数组的定义与使用
C#学习系列相关之数组(一)---数组的定义与使用
|
5月前
|
IDE C# 开发工具
C#系列之接口介绍
C#系列之接口介绍
|
5天前
|
C#
C# 接口(Interface)
接口定义了所有类继承接口时应遵循的语法合同。接口定义了语法合同 "是什么" 部分,派生类定义了语法合同 "怎么做" 部分。 接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明。成员的定义是派生类的责任。接口提供了派生类应遵循的标准结构。 接口使得实现接口的类或结构在形式上保持一致。 抽象类在某种程度上与接口类似,但是,它们大多只是用在当只有少数方法由基类声明由派生类实现时。 接口本身并不实现任何功能,它只是和声明实现该接口的对象订立一个必须实现哪些行为的契约。 抽象类不能直接实例化,但允许派生出具体的,具有实际功能的类。
29 9
|
23天前
|
安全 C# 索引
C#一分钟浅谈:属性与索引器的定义
本文深入浅出地介绍了C#编程中的属性和索引器。属性让字段更安全,通过访问器方法在读写时执行额外操作,如验证数据有效性;索引器则赋予类数组般的访问方式,支持基于索引的数据访问模式。文章通过示例代码展示了如何定义及使用这两种特性,并提供了常见问题及其解决方案,帮助读者写出更健壮、易维护的代码。希望读者能从中学习到如何有效利用属性和索引器增强C#类的功能性。
59 12
|
17天前
|
安全 C#
C# 面向对象编程的三大支柱:封装、继承与多态
【9月更文挑战第17天】在C#中,面向对象编程的三大支柱——封装、继承与多态,对于编写安全、可维护、可复用的代码至关重要。封装通过访问修饰符和属性保护数据;继承允许子类继承父类的属性和方法,实现代码复用和多态;多态则提高了代码的灵活性和通用性。掌握这三大概念能显著提升C#编程能力,优化开发效率和代码质量。
|
1月前
|
C# 索引
C# 一分钟浅谈:接口与抽象类的区别及使用
【9月更文挑战第2天】本文详细对比了面向对象编程中接口与抽象类的概念及区别。接口定义了行为规范,强制实现类提供具体实现;抽象类则既能定义抽象方法也能提供具体实现。文章通过具体示例介绍了如何使用接口和抽象类,并探讨了其实现方式、继承限制及实例化差异。最后总结了选择接口或抽象类应基于具体设计需求。掌握这两者有助于编写高质量的面向对象程序。
66 5
|
1月前
|
存储 C#
C# 一分钟浅谈:继承与多态性的实践
【9月更文挑战第2天】本文从基础入手,详细介绍了面向对象编程中继承与多态性的核心概念。通过 `Animal`、`Dog` 和 `Cat` 类的示例代码,展示了如何利用继承重用代码及多态性实现不同对象对同一方法的多样化响应,帮助读者更好地理解和应用这两个重要概念,提升面向对象编程能力。
33 3
|
2月前
|
C#
C#中的类和继承
C#中的类和继承
37 6
|
2月前
|
API C# 数据库
SemanticKernel/C#:实现接口,接入本地嵌入模型
SemanticKernel/C#:实现接口,接入本地嵌入模型
62 1
下一篇
无影云桌面