接口隔离
接口即契约:甲方“我不会多要”;乙方“我不会少给”。
-
乙方不会少给:硬性规定,即一个类只要实现了接口,就必需实现接口里面的所有方法,一旦未全部实现,类就还只是个抽象类,任然不能实例化
- 甲方不会多要:软性规定,是个设计问题
胖接口及其产生原因
观察传给调用者的接口里面有没有一直没有调用的函数成员,如果有,就说明传进来的接口类型太大了。换句话说,这个“胖”接口是由两个或两个以上的小接口合并起来的。
设计失误
设计失误:把太多的功能包含在一个接口里面
-
这就导致实现该接口的类违反了单一职责原则
- 单一职责原则:一个类只做一件(或一组相关的)事
-
接口隔离原则是从服务调用者的角度来看接口类型,单一职责原则是从服务提供者的角度来看接口类型
-
解决方案就是把胖接口拆成单一的小接口(把本质不同的功能隔离开)
设计失误
设计失误:把太多的功能包含在一个接口里面
-
这就导致实现该接口的类违反了单一职责原则
- 单一职责原则:一个类只做一件(或一组相关的)事
-
接口隔离原则是从服务调用者的角度来看接口类型,单一职责原则是从服务提供者的角度来看接口类型
-
解决方案就是把胖接口拆成单一的小接口(把本质不同的功能隔离开)
例子:背景是 Driver(小女生)撞车了,小男生安慰她以后可以开 Tank 上班
using System;
namespace IspExample
{
class Program
{
static void Main(string[] args)
{
var driver = new Driver(new Car());
driver.Drive();
}
}
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running ...");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running ...");
}
}
interface ITank
{
void Fire();
void Run();
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka ka ka ...");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!");
}
public void Run()
{
Console.WriteLine("Ka! ka! ka! ...");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Ka!! ka!! ka!! ...");
}
}
}
Driver 要开 Tank,第一种方法就是将 IVehicle 改成 ITank
class Program
{
static void Main(string[] args)
{
var driver = new Driver(new HeavyTank());
driver.Drive();
}
}
class Driver
{
private ITank _tank;
public Driver(ITank tank)
{
_tank = tank;
}
public void Drive()
{
_tank.Run();
}
}
此时就出现了胖接口的问题,Driver 只是个小女生,ITank 里面 Fire 永远都用不到。将 Fire 和 Run 这两个完全不同的功能分隔开,分隔到两个不同的接口里面去。
下面也展示了接口的多实现。
class Program
{
static void Main(string[] args)
{
var driver = new Driver(new HeavyTank());
driver.Drive();
}
}
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
...
interface IWeapon
{
void Fire();
}
interface ITank : IVehicle, IWeapon { }
现在 Driver 里面又只需包含IVehicle 类型字段了。注:在使用接口隔离原则和单一职责原则时不要过犹不及,过头了就会产生很多细碎的接口和类,使用时必须把握度。
传了个大接口
传给调用者的胖接口是由两个设计很好的小接口合并而来的,本来应该传一个小接口就可以了,结果却传了个大接口。这种情况的问题在于你可能把原来合格的服务提供者给拒之门外了。例如上例中,将 Driver 类里面的 IVehicle 改成 ITank 后,能够作为服务提供者的就只剩下三个 Tank 类了,Car 和 Truck 这两个好好的交通工具都被挡在门外了。
下面以枚举举例,本来枚举只需要实现了 IEnumerable,结果你传了个 ICollection,就无法对数组进行 Sum 操作了。
注:ICollection 实现了 IEnumerable
class Program
{
static void Main(string[] args)
{
int[] nums1 = { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Sum(nums2));
// 可以迭代
var num3 = new ReadOnlyCollection(nums1);
foreach (var n in num3)
{
Console.WriteLine(n);
}
Console.WriteLine(Sum(num3));
}
// 调用者绝不多要
// static int Sum(ICollection nums)
static int Sum(IEnumerable nums)
{
int sums = 0;
foreach (var n in nums)
{
sums += (int)n;
}
return sums;
}
}
class ReadOnlyCollection : IEnumerable
{
private int[] _array;
public ReadOnlyCollection(int[] array)
{
_array = array;
}
public IEnumerator GetEnumerator()
{
return new Enumerator(this);
}
// 为了避免污染整个名称空间,所以用了成员类
public class Enumerator : IEnumerator
{
private ReadOnlyCollection _collection;
private int _head;
public Enumerator(ReadOnlyCollection collection)
{
_collection = collection;
_head = -1;
}
public object Current
{
get
{
// 因为 Enumerator 就在 ReadOnlyCollection 内部
// 所以能访问 private 的 _array 成员
object o = _collection._array[_head];
return o;
}
}
public bool MoveNext()
{
return ++_head < _collection._array.Length;
}
public void Reset()
{
_head = -1;
}
}
}
显式接口实现
接口隔离的第三个例子,专门用来展示 C# 特有的能力 —— 显式接口实现。
C# 能把隔离出来的接口隐藏起来,直到你显式的使用这种接口类型的变量去引用实现了该接口的实例,该接口内的方法才能被你看见被你使用。这个例子的背景是《这个杀手不太冷》,WarmKiller 有其两面性,即实现 IGentleman 又实现 IKiller。之前见过的多接口的默认实现方式,该方式从业务逻辑来说是有问题的,一个杀手不应该总将 Kill 方法暴露出来。
换言之,如果接口里面有的方法,我们不希望它轻易被调用,就不能让它轻易被别人看到。
class Program
{
static void Main(string[] args)
{
var wk = new WarmKiller();
wk.Love();
wk.Kill();
}
}
interface IGentleman
{
void Love();
}
interface IKiller
{
void Kill();
}
class WarmKiller : IGentleman, IKiller
{
public void Love()
{
Console.WriteLine("I will love you forever ...");
}
public void Kill()
{
Console.WriteLine("Let me kill the enemy ...");
}
}
显示实现
static void Main(string[] args)
{
IKiller killer = new WarmKiller();
killer.Kill();
var wk = (WarmKiller) killer;
wk.Love();
}
...
class WarmKiller : IGentleman, IKiller
{
public void Love()
{
Console.WriteLine("I will love you forever ...");
}
// 显示实现,只有当该类型(WarmKiller)实例被 IKiller 类型变量引用时该方法才能被调用
void IKiller.Kill()
{
Console.WriteLine("Let me kill the enemy ...");
}
}
WarmKiller 类型的实例,只能看到 Love 方法