何时使用接口
关于何时使用或不使用接口的建议
引言
我经常遇到软件开发中违背逻辑解释的做法。其中一种更常见的做法是滥用接口。在本文中,我将描述接口类型构造的基本目的,并探讨何时使用它们的问题。
我想在本文开头声明,我将描述的所有情况都有例外,并且一些开发人员可能对某些观点有异议。但是,我相信我的方法产生的代码更容易遵循,更容易维护,并且避免在这样做没有提供可衡量的收益时增加所需的努力。
什么是接口?
接口只不过是两段代码如何交互的协议。一段代码实现了接口,另一段代码使用该接口。该接口充当解耦层,可以替换实现该接口的对象。
为了说明这一点,让我们检查一个简单的接口用法。
public interface IAdder
{
int Add(int a, int b);
}
public class Example
{
public IAdder Adder
{
get;
set;
}
public void DoSomething()
{
var two = Adder.Add(1, 1);
}
}
在上面的例子中,DoSomething
使用一个加法器来添加两个数字,但Example
类不知道加法器的实现。
操作/服务
提供操作或服务的类通常是接口抽象的候选者。一个典型的例子是 WCF 服务。WCF 服务定义服务契约,并且通过接口执行通信,客户端不依赖于服务实现。
让我们看看加法器接口的一个变体,并假设与上面相同的用法。
public interface IAdder
{
double Add(double a, double b);
}
public class DoubleAdder : IAdder
{
public double Add(double a, double b)
{
return a + b;
}
}
public class IntAdder : IAdder
{
public double Add(double a, double b)
{
return (int)a + (int)b;
}
}
当我们有各种各样的操作实现可以被一个不变的代码使用时,定义接口的用处就显而易见了,唯一的要求是为代码提供所需实现的实例。
数据结构
表示数据的类不太可能成为接口抽象的候选者。这样做的原因在于数据的实际结构可能与使用数据结构中的成员一样重要。当使用 WCF 数据契约时,可以看到一个例子,其中数据的序列化形式对于能够使用数据至关重要。
同样,我们将修改加法器接口来说明这一点。
public class Number { /* ... */ }
public interface IAdder
{
Number Add(Number a, Number b);
}
在这种情况下,Number
是作为参数传递给加法器的数据。本质上,Number
类与Add
方法本身一样是加法器接口的一部分。虽然可以使用数字接口,但这样做的好处尚不清楚,并且远程访问加法器的能力可能会受到损害。真的有必要用不同的底层数据实现来替换数字吗?
内部细节
在某些活动中内部使用但未从程序集中公开的类不太可能成为接口抽象的候选者。尽管肯定有例外,但内部类的典型情况并不能从接口抽象中受益。
下面的加法器接口方法的实现说明了典型情况。
public class DoubleAdder : IAdder
{
public double Add(double a, double b)
{
return new MyCalculator().Add(a, b);
}
}
internal class MyCalculator
{
internal double Add(double a, double b)
{
return a + b;
}
}
在这里,我们可以看到内部MyCalculator
类用于包含逻辑,而DoubleAdder
类只是内部计算器实现的适配器。向内部类添加接口不会带来任何好处,并且如果内部方法签名要更改,则会增加维护该类所需的努力。
历史
- 2010年1月27日:初始帖子