谈谈C#中的接口隔离

接口隔离

接口即契约:甲方“我不会多要”;乙方“我不会少给”。

  • 乙方不会少给:硬性规定,即一个类只要实现了接口,就必需实现接口里面的所有方法,一旦未全部实现,类就还只是个抽象类,任然不能实例化

  • 甲方不会多要:软性规定,是个设计问题

胖接口及其产生原因

观察传给调用者的接口里面有没有一直没有调用的函数成员,如果有,就说明传进来的接口类型太大了。换句话说,这个“胖”接口是由两个或两个以上的小接口合并起来的。

设计失误

 

设计失误:把太多的功能包含在一个接口里面

  • 这就导致实现该接口的类违反了单一职责原则

  • 单一职责原则:一个类只做一件(或一组相关的)事
  • 接口隔离原则是从服务调用者的角度来看接口类型,单一职责原则是从服务提供者的角度来看接口类型

  • 解决方案就是把胖接口拆成单一的小接口(把本质不同的功能隔离开)

设计失误

设计失误:把太多的功能包含在一个接口里面

  • 这就导致实现该接口的类违反了单一职责原则

  • 单一职责原则:一个类只做一件(或一组相关的)事
  • 接口隔离原则是从服务调用者的角度来看接口类型,单一职责原则是从服务提供者的角度来看接口类型

  • 解决方案就是把胖接口拆成单一的小接口(把本质不同的功能隔离开)

例子:背景是 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 方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值