在设计 C # 类库时,什么时候应该选择继承而不是接口?

我有一些 Processor类,它们可以完成两种截然不同的任务,但都是从通用代码(一种“控制反转”的情况)中调用的。

我想知道在决定他们是否都应该继承自 BaseProcessor,还是将 IProcessor实现为接口时,我应该认识到(或认识到,对于用户来说)哪些设计考虑因素。

62443 次浏览

Generally, the rule goes something like this:

  • Inheritance describes an is-a relationship.
  • Implementing an interface describes a can-do relationship.

To put this in somewhat more concrete terms, let's look at an example. The System.Drawing.Bitmap class is-an image (and as such, it inherits from the Image class), but it also can-do disposing, so it implements the IDisposable interface. It also can-do serialization, so it implements from the ISerializable interface.

But more practically, interfaces are often used to simulate multiple inheritance in C#. If your Processor class needs to inherit from something like System.ComponentModel.Component, then you have little choice but to implement an IProcessor interface.

The fact is that both interfaces and abstract base class provide a contract specifying what a particular class can do. It's a common myth that interfaces are necessary to declare this contract, but that's not correct. The biggest advantage to my mind is that abstract base classes allow you provide default functionality for the subclasses. But if there is no default functionality that makes sense, there's nothing keeping you from marking the method itself as abstract, requiring that derived classes implement it themselves, just like if they were to implement an interface.

For answers to questions like this, I often turn to the .NET Framework Design Guidelines, which have this to say about choosing between classes and interfaces:

In general, classes are the preferred construct for exposing abstractions.

The main drawback of interfaces is that they are much less flexible than classes when it comes to allowing for the evolution of APIs. Once you ship an interface, the set of its members is fixed forever. Any additions to the interface would break existing types implementing the interface.

A class offers much more flexibility. You can add members to classes that you have already shipped. As long as the method is not abstract (i.e., as long as you provide a default implementation of the method), any existing derived classes continue to function unchanged.

[ . . . ]

One of the most common arguments in favor of interfaces is that they allow separating contract from the implementation. However, the argument incorrectly assumes that you cannot separate contracts from implementation using classes. Abstract classes residing in a separate assembly from their concrete implementations are a great way to achieve such separation.

Their general recommendations are as follows:

  • Do favor defining classes over interfaces.
  • Do use abstract classes instead of interfaces to decouple the contract from implementations. Abstract classes, if defined correctly, allow for the same degree of decoupling between contract and implementation.
  • Do define an interface if you need to provide a polymorphic hierarchy of value types.
  • Consider defining interfaces to achieve a similar effect to that of multiple inheritance.

Chris Anderson expresses particular agreement with this last tenet, arguing that:

Abstract types do version much better, and allow for future extensibility, but they also burn your one and only base type. Interfaces are appropriate when you are really defining a contract between two objects that is invariant over time. Abstract base types are better for defining a common base for a family of types.

I am not pretty good at design choices, but if asked, I will prefer implementing an iProcessor interface if there are only members to be extended. If there are other functions which need not to be extended, inheriting from baseprocessor is better option.

If whatever you choose don't matter, always choose Interface. It allows more flexibility. It might protect you from future changes (if you need to change something in the base class, the inherited classes might be affected). It also allow to encapsulate the details better. If you are using some Inversion of Control of Dependency Injection, they tend to favor interface.

Or if inheritance can't be avoided, probably a good idea to use both of them together. Create a abstract base class that implements an interface. In your case, ProcessorBase implements an IProcessor.

Similar to ControllerBase and IController in ASP.NET Mvc.

Design purity aside, you can inherit only once so, if you need to inherit some framework class the question is moot.

If you have a choice, pragmatically you can choose the option that saves the most typing. Its usually the purist choice anyway.

EDIT:

If the base class will have some implementation then this could be useful, if it is purely abstact then it may as well be an interface.

Richard,

Why CHOOSE between them? I'd have an IProcessor interface as the published type (for use elsewhere in the system); and if it so happens that your various CURRENT implementations of IProcessor have common-behaviour, then an abstract BaseProcessor class would be a real good place to implement that common behaviour.

This way, if you require an IProcessor in future which does NOT have been for BaseProcessor's services, it doesn't HAVE to have it (and possibly hide it)... but those that do want it can have it... cutting down in duplicated code/concepts.

Just my humble OPINION.

Cheers. Keith.

Interfaces are a "contract", these are ensuring some class implements a desired set of members - properties, methods and events -.

Base classes (concrete or abstract, doesn't matter) are the archetype of some entity. That's these are entities representing what's common in some actual physical or conceptual one.

When to use interfaces?

Whenever some type needs to declare that, at least, has some behaviors and properties that a consumer should care about and use them to accomplish some task.

When to use base classes (concrete and/or abstract)

Whenever a group of entities share same archetype, meaning B inherits A because B is A with differences, but B can be identified as A.


Examples:

Let's talk about tables.

  • We accept recyclable tables => This must be defined with an interface like "IRecyclableTable" ensuring that all recyclable tables are going to have a "Recycle" method.

  • We want desktop tables => This must be defined with inheritance. A "desktop table" is a "table". All tables have common properties and behaviors and desktop ones will have same ones adding such things that make a desktop table work different than other types of tables.

I could talk about associations, meaning of both cases in an object graph, but in my humild opinion, if I need to give arguments in a conceptual point of view, I would exactly answer with this argumentation.

Given that the SOLID principles offer more maintainability and extensibility to your project, I'd prefer interfaces over inheritance.

Also, if you need to add "additional functionality" to your interface, best option is to create a new interface altogether, following the I in SOLID, which is Interface Seggregation Principle.