Effective C#之19:Prefer Defining and Implementing Interfaces to Inheritance

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 19: Prefer Defining and Implementing Interfaces to Inheritance

定义并实现接口优于继承

Abstract base classes provide a common ancestor for a class hierarchy. An interface describes one atomic piece of functionality that can be implemented by a type. Each has its place, but it is a different place. Interfaces are a way to design by contract: A type that implements an interface must supply an implementation for expected methods. Abstract base classes provide a common abstraction for a set of related types. It's a cliché, but it's one that works: Inheritance means "is a," and interfaces means "behaves like." These clichés have lived so long because they provide a means to describe the differences in both constructs: Base classes describe what an object is; interfaces describe one way in which it behaves.

抽象基类为类体系提供了通用的祖先。接口描述了能被类型实现的原子性的功能。每个都有它自己的位置,但是是不同的。接口是通过约定进行设计的方式:实现了接口的类必须为一个期望的方法提供支持。抽象基类为一系列相关类型提供共同的抽象。这是陈腐的但能工作的:继承意味这“是一个”接口意味着“像什么一样行为”。这些陈腐性存在了很长的时间,是因为它们提供了一个方式来描述每个结构里的不同:基类描述了对象是什么,接口描述了它的行为方式。

Interfaces describe a set of functionality, or a contract. You can create placeholders for anything in an interface: methods, properties,indexers, and events. Any type that implements the interface must supply concrete implementations of all elements defined in the interface. You must implement all methods, supply any and all property accessors and indexers, and define all events defined in the interface. You identify and factor reusable behavior into interfaces. You use interfaces as parameters and return values. You also have more chances to reuse code because unrelated types can implement interfaces. What's more, it's easier for other developers to implement an interface than it is to derive from a base class you've created.

接口描述了一系列功能,或者是约定。你可以为接口里面的任何东西创建占位符:方法,属性,索引器,事件。任何实现该接口的类型必须为在接口里面定义的全部元素提供具体实现。你必须实现所有方法,支持所有任何属性访问和索引器,定义在接口里面定义的事件。你将可重用的行为,进行定义提炼到接口中去。使用接口作为参数和返回值。因为一些不相关的类型可以实现接口,所以你也有更多的机会来重用代码。还有,对其它开发者来说,实现一个接口比从一个已有的基类进行派生更容易。

What you can't do in an interface is provide implementation for any of these members. Interfaces contain no implementation whatsoever, and they cannot contain any concrete data members. You are declaring the contract that must be supported by all types that implement an interface.

在接口里面不能做的是:为任何这些成员提供实现。接口不包含任何实现,也不能包含任何具体数据成员。你声明的是约定,实现该接口的任何类型都必须支持该约定。

Abstract base classes can supply some implementation for derived types, in addition to describing the common behavior. You can specify data members, concrete methods, implementation for virtual methods, properties, events, and indexers. A base class can provide implementation for some of the methods, thereby providing common implementation reuse. Any of the elements can be virtual, abstract, or nonvirtual. An abstract base class can provide an implementation for any concrete behavior; interfaces cannot.

抽象基类除了描述通用的行为,还可以为派生类提供一些实现。你可以指定数据成员,具体方法,对虚方法、属性、事件、索引器的实现。基类可以为一些方法提供实现,从而提供可重用的通用实现。任何这些元素可以是虚的,抽象的,或者非虚的。一个抽象基类可以为任何具体行为提供实现;接口不能。

This implementation reuse provides another benefit: If you add a method to the base class, all derived classes are automatically and implicitly enhanced. In that sense, base classes provide a way to extend the behavior of several types efficiently over time: By adding and implementing functionality in the base class, all derived classes immediately incorporate that behavior. Adding a member to an interface breaks all the classes that implement that interface. They will not contain the new method and will no longer compile. Each implementer must update that type to include the new member.

可重用的实现提供另一个好处:如果你向基类添加一个方法,所有的派生类自动隐式的得到了加强。那样的话,随着时间的流逝,基类提供了扩展一些类行为的高效方式:通过在基类里面加入和实现方法,所有的派生类迅速继承了该行为。向接口里面加入成员,会破坏所有实现该接口的类。它们将不含有新方法,不能编译通过。每个实现者都必须更新类型来包含新的成员。

Choosing between an abstract base class and an interface is a question of how best to support your abstractions over time. Interfaces are fixed: You release an interface as a contract for a set of functionality that any type can implement. Base classes can be extended over time. Those extensions become part of every derived class.

随着时间变化,如何最好的支持你的抽象,在抽象类和接口之间进行选择是一个问题。接口是修正性的:发布一个接口作为功能集合的约定,让任何类都能实现它。基类可以随着时间而被扩展。这些扩张会成为所有派生类的一部分。

The two models can be mixed to reuse implementation code while supporting multiple interfaces. One such example is System.Collections.CollectionBase.. This class provides a base class that you can use to shield clients from the lack of type safety in .NET collections. As such, it implements several interfaces on your behalf: IList, ICollection, and IEnumerable. In addition, it provides protected methods that you can override to customize the behavior for different uses. The IList interface contains the Insert() method to add a new object to a collection. Rather than provide your own implementation of Insert, you process those events by overriding the OnInsert() or OnInsertCcomplete() virtual methods of the CollectionBase class.

在支持多接口时,这2个模型会和重用的实现代码搞混。一个这样的例子是System.Collections.CollectionBase。该类提供了一个基类,你可以用来在.Net集合里面防止客户缺少类型安全性。像这个一样,为你实现了一些接口:IListICollection,和IEnumerable。另外,它提供了一些保护方法,你可以为不同的需要重写这些方法来满足不同的行为。IList接口包含了Insert()方法来向集合里面添加新的对象。比你提供Insert实现更好,通过重写CollectionBase类的 OnInsert()或者OnInsertCcomplete()虚方法来处理这些事件。

 

  1.    public class IntList : System.Collections.CollectionBase
  2.     {
  3.         protected override void OnInsert(int index, object value)
  4.         {
  5.             try
  6.             {
  7.                 int newValue = System.Convert.ToInt32(value);
  8.                 Console.WriteLine("Inserting {0} at position {1}",
  9.                  value.ToString(),index.ToString());
  10.                 Console.WriteLine("List Contains {0} items",
  11.                 this.List.Count.ToString());
  12.             }
  13.             catch (FormatException e)
  14.             {
  15.                 throw new ArgumentException(
  16.                  "Argument Type not an integer",
  17.                  "value", e);
  18.             }
  19.         }
  20.         protected override void OnInsertComplete(int index,object value)
  21.         {
  22.             Console.WriteLine("Inserted {0} at position {1}",
  23.               value.ToString(),index.ToString());
  24.             Console.WriteLine("List Contains {0} items",
  25.               this.List.Count.ToString());
  26.         }
  27.     }
  28.  
  29.     public class MainProgram
  30.     {
  31.         public static void Main()
  32.         {
  33.             IntList l = new IntList();
  34.             IList il = l as IList;
  35.             il.Insert(0, 3);
  36.             il.Insert(0, "This is bad");
  37.         }
  38. }

The previous code creates an integer array list and uses the IList interface pointer to add two different values to the collection. By overriding the OnInsert() method, the IntList class tests the type of the inserted value and throws an exception when the type is not an integer. The base class provides the default implementation and gives you hooks to specialize the behavior in your derived classes.

前面的代码创建了一个整数数组列表,使用IList接口指针向集合中添加了2个不同的值。通过重写OnInsert()方法,IntList类检查了插入值的类型,当类型不是整型时,就抛出异常。基类提供了默认的实现,同时给你提供了一个钩子,让你在派生类里面可以指定行为。

CollectionBase, the base class, gives you an implementation that you can use for your own classes. You need not write nearly as much code because you can make use of the common implementation provided. But the public API for IntList comes from the interfaces implemented by CollectionBase: the IEnumerable, ICollection, and IList interfaces. CollectionBase provides a common implementation for the interfaces that you can reuse.

基类CollectionBase给你提供了一个实现,你可以在自己的类里面使用。你没必要写很多代码,因为你可以利用通用的实现。但是给IntList的公共的API来自由CollectionBase实现的接口:IEnumerableICollectionIList接口。CollectionBase为你要重用的接口提供了一个通用的实现。

That brings me to the topic of using interfaces as parameters and return values. An interface can be implemented by any number of unrelated types. Coding to interfaces provides greater flexibility to other developers than coding to base class types. That's important because of the single inheritance hierarchy that the .NET environment enforces.

那将我带到这个话题上:将接口作为参数和返回值使用。接口可以被无数个互不关联的类型使用。面向接口编程比对面向基类类型编程,可以为其它开发者提供了更好的弹性。因为.Net环境强制的单继承体系,这很重要。

These two methods perform the same task:

2个方法执行了同样的任务:

  1.     public void PrintCollection(IEnumerable collection)
  2.     {
  3.         foreach (object o in collection)
  4.             Console.WriteLine("Collection contains {0}",
  5.               o.ToString());
  6.     }
  7.      public void PrintCollection(CollectionBase collection)
  8.     {
  9.         foreach (object o in collection)
  10.             Console.WriteLine("Collection contains {0}",
  11.               o.ToString());
  12.      }

The second method is far less reusable. It cannot be used with Arrays, ArrayLists, DataTables, Hashtables, ImageLists, or many other collection classes. Coding the method using interfaces as its parameter types is far more generic and far easier to reuse.

第二个方法在重用性方面有点欠缺,它不能和ArrarArrayListDataTableHashtableImageList或者很多一些集合类一起使用。使用接口作为参数类型对方法编码更普通更易于重用。

Using interfaces to define the APIs for a class also provides greater flexibility. For example, many applications use a DataSet to transfer data between the components of your application. It's too easy to code that assumption into place permanently:

使用接口为类定义API同样提供了更大的弹性。例如,很多应用程序使用DataSet在的程序组件之间传递数据。(不会翻,汗)

  1.     public DataSet TheCollection
  2.     {
  3.         get { return dataSetCollection; }
  4.     }

That leaves you vulnerable to future problems. At some point, you might change from using a DataSet to exposing one DataTable, using a DataView, or even creating your own custom object. Any of those changes will break the code. Sure, you can change the parameter type, but that's changing the public interface to your class. Changing the public interface to a class causes you to make many more changes to a large system; you would need to change all the locations where the public property was accessed.

这会使你会受到未来问题的骚扰。在有些时候,你可能从使用DataSet转变为使用DataTableDataViewManage甚至创建符合自己习惯的对象。任何这些改变都将会破坏代码。当然,你可以改变参数类型,但是那也修改了你的类的公共接口。在一个大系统里面,修改一个类的公共接口会让你做更多的修改,你将需要修改任何公共属性被使用的地方。

The second problem is more immediate and more troubling: The DataSet class provides numerous methods to change the data it contains. Users of your class could delete tables, modify columns, or even replace every object in the DataSet. That's almost certainly not your intent. Luckily, you can limit the capabilities of the users of your class. Instead of returning a reference to the DataSet type, you should return the interface you intend clients to use. The DataSet supports the IListSource interface, which it uses for data binding:

第二个问题更紧迫也更烦人。DataSet类提供了很多方法来改变包含的数据。你的类的对象可以删除表格,修改列,甚至替换DataSet里面的每个对象。那几乎可以肯定不是你的意图。幸运的是,你可以限制你的类的用户能力。不是返回DataSet类型的引用,而是应该返回你希望客户使用的接口。DataSet支持用作数据绑定的IListSource接口。 

  1.    using System.ComponentModel;
  2.     public IListSource TheCollection
  3.     {
  4.         get { return dataSetCollection as IListSource; }
  5.      }

IListSource lets clients view items through the GetList() method. It also has a ContainsListCollection property so that users can modify the overall structure of the collection. Using the IListSource interface, the individual items in the DataSet can be accessed, but the overall structure of the DataSet cannot be modified. Also, the caller cannot use the DataSet's methods to change the available actions on the data by removing constraints or adding capabilities.

IListSource让客户通过GetList()方法来访问里面的项。它还有一个ContainsListCollection属性,那样的话用户可以修改集合里所有的结构。使用IListSource接口,DataSet里面的单个项可以被访问,但是DataSet全部的结构不能被修改。同样,调用者不能通过移除限制或者增加能力,使用DadaSet的方法来改变数据上的可用动作。

When your type exposes properties as class types, it exposes the entire interface to that class. Using interfaces, you can choose to expose only the methods and properties you want clients to use. The class used to implement the interface is an implementation detail that can change over time (see Item 23).

当你的类型以类向外暴露属性时,就暴露了那个类的整个接口。使用接口,你就可以选择只向客户暴露你希望它们使用的方法和属性。实现接口的类是随着时间的改变而改变的一个实现细节。

Furthermore, unrelated types can implement the same interface. Suppose you're building an application that manages employees, customers, and vendors. Those are unrelated, at least in terms of the class hierarchy. But they share some common functionality. They all have names, and you will likely display those names in Windows controls in your applications.

进一步说,毫不相关的类型可以实现同样的接口。假设你正在构建一个应用程序,用来管理雇员,客户和卖主。它们是不相关的,至少在类体系上是不相关的。但是它们共享一些通用的功能。它们都有名字,你也很可能在应用程序的Windows窗体上显式他们的名字。

 

  1.    public class Employee
  2.     {
  3.         public string Name
  4.         {
  5.             get
  6.             {
  7.                 return string.Format("{0}, {1}", last, first);
  8.             }
  9.         }
  10.         // other details elided.
  11.     }
  12.  
  13.     public class Customer
  14.     {
  15.         public string Name
  16.         {
  17.             get
  18.             {
  19.                 return customerName;
  20.             }
  21.         }
  22.         // other details elided
  23.     }
  24.  
  25.     public class Vendor
  26.     {
  27.         public string Name
  28.         {
  29.             get
  30.             {
  31.                 return vendorName;
  32.             }
  33.         }
  34. }

The Employee, Customer, and Vendor classes should not share a common base class. But they do share some properties: names (as shown earlier), addresses, and contact phone numbers. You could factor out those properties into an interface:

雇员,客户,卖主类不应该共享一个通用的基类。但是它们确实共享了一些属性:名字(像前面展示的一样),住址,联系电话号码。你可以将这些属性提炼到一个接口里面。

 

  1.    public interface IContactInfo
  2.     {
  3.         string Name { get; }
  4.         PhoneNumber PrimaryContact { get; }
  5.         PhoneNumber Fax { get; }
  6.         Address PrimaryAddress { get; }
  7.     }
  8.  
  9.     public class Employee : IContactInfo
  10.     {
  11.         // implementation deleted.
  12.    }

This new interface can simplify your programming tasks by letting you build common routines for unrelated types:

新接口通过让你为不相关的类型建立通用的子程序,可以简化编程任务。

 

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

This one routine works for all entities that implement the IContactInfo interface. Customer, Employee, and Vendor all use the same routine but only because you factored them into interfaces.

这一个子程序可以为所有实现了IContactInfo接口的实体工作。客户,雇员和卖主都能使用同样的子程序只是因为你将它们提炼到了接口中。

Using interfaces also means that you can occasionally save an unboxing penalty for structs. When you place a struct in a box, the box supports all interfaces that the struct supports. When you access the struct through the interface pointer, you don't have to unbox the struct to access that object. To illustrate, imagine this struct that defines a link and a description:

使用接口也意味着,你可以偶尔的节省对结构体进行拆箱的代价。但你将一个结构体装箱时,箱子支持该结构体支持的所有接口。当你通过接口指针访问接口的时候,没必要对结构体进行拆箱后再访问那个对象了。为了举例,想象该结构体定义了一个链接和一个描述。

 

  1.    public struct URLInfo : IComparable
  2.     {
  3.         private string URL;
  4.         private string description;
  5.  
  6.         public int CompareTo(object o)
  7.         {
  8.             if (o is URLInfo)
  9.             {
  10.                 URLInfo other = (URLInfo)o;
  11.                 return CompareTo(other);
  12.             }
  13.             else
  14.                 throw new ArgumentException(
  15.                   "Compared object is not URLInfo");
  16.         }
  17.  
  18.         public int CompareTo(URLInfo other)
  19.         {
  20.             return URL.CompareTo(other.URL);
  21.         }
  22.  }

You can create a sorted list of URLInfo objects because URLInfo implements IComparable. The URLInfo structs get boxed when added to the list. But the Sort() method does not need to unbox both objects to call CompareTo(). You still need to unbox the argument (other), but you don't need to unbox the left side of the compare to call the IComparable.CompareTo() method.

由于URLInfo实现了IComparable,所以你可以创建一个URLInfo对象的有序列表。当URLInfo结构被加入一个list的时候,被装箱。但是Sort()方法不需要对所有的对象进行拆箱再调用CompareTo()。你依然需要拆箱参数(其它),但是在调用IComparable.CompareTo()方法的时候,你不需要对比较的左值拆箱。

Base classes scribe and implement common behaviors across related concrete types. Interfaces describe atomic pieces of functionality that unrelated concrete types can implement. Both have their place. Classes define the types you create. Interfaces describe the behavior of those types as pieces of functionality. If you understand the differences, you will create more expressive designs that are more resilient in the face of change. Use class hierarchies to define related types. Expose functionality using interfaces implemented across those types.

基类抄写并实现遍布相关具体类型的通用行为。接口描述了可以由不相关的具体类型实现的原子功能。各自有各自的使用场合。类定义你创建的类型。接口以一个一个的功能描述类的行为。如果你理解了这些不同,将会创建更多高级的设计,能够更好的面对变化。使用类继承体系定义相关的类型。通过接口暴露遍布那些类型的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值