接口隔离原则:功能调用者需要多少功能,接口里面就有多少功能,但是不能多,不能有用不到的功能
单一职责原则:一个类应该只做一件事或者一组相关的事,如果一个类实现了一个有过多功能的接口,这个类也必然要把接口中所有成员都实现,这个类就会做很多件事,就违反了这个原则
如果出现了上述的问题应该怎么解决呢
解决方案就是把一个有很多功能的接口分割成有部分功能的小接口,每个小接口都描述一个单一的功能;其实就是把本质不同的功能给他隔离开,再用接口封装
场景模拟:你女朋友开车撞了人,人没事车坏了,你安慰你女朋友说买辆坦克就不怕撞坏了。你女朋友很高兴的打出了GG,那么问题来了,你有女朋友吗????
按照注释顺序查看代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace _14.接口隔离_反射_特性_依赖注入
{
internal class Program
{
static void Main(string[] args)
{
//4. 实例化
var drive = new Driver(new HeavyTank());//5. 因为现在Tank也是实现我们IVehicle接口的,所以一样可以开
drive.Drive();
//4.1 如果是开坦克呢
//var drive1 = new Driver(new HeavyTank());
//drive1.Drive();
//4.2 当前存在的问题是为什么我不能又开车又开坦克呢
//问题就是我们传进来的接口中有多的东西,汽车又不会开炮,所以Fire方法就是那个多余的,违反了接口隔离原则
//此时应该把这个异常的接口分割成两个小接口
Console.ReadKey();
}
}
//3. 当前的状态是这两个接口都有类去实现,设计一个类
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)//5.1 调用tank的时候只关注其Run方法,不管其他的,此时就符合了接口隔离原则
{
_vehicle = vehicle;
}
//3.1 当他调用自己的Drive的时候,实际上调用的是_vehicle字段的Run
public void Drive()
{
_vehicle.Run();
}
}
//1. 创建接口和车辆类
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...");
}
}
//4.5 所以我们新建一个武器接口
interface IWeapon
{
void Fire();
}
//2. 创建坦克接口和坦克类
interface ITank : IVehicle, IWeapon { } //4.6我们让ITank将IVehicle和IWeapon这两个接口合并起来,这就是一个接口对其他多个接口的继承
//4.7 在C#中,类与类继承的时候只能有一个基类
//4.8 而当你用一个类或者一个接口去继承其他接口的时候,是可以有多个基接口的
//{
// void Fire();//4.3 实际上Fire这个方法应该隶属于武器
// void Run();//4.4 Run方法隶属于交通工具类
//}
class LightTank : ITank
{
public void Fire()
{
//throw new NotImplementedException();
Console.WriteLine("Boom!");
}
public void Run()
{
//throw new NotImplementedException();
Console.WriteLine("ka! ka! ka!...");
}
}
class MediumTank : ITank
{
public void Fire()
{
//throw new NotImplementedException();
Console.WriteLine("Boom!!");
}
public void Run()
{
//throw new NotImplementedException();
Console.WriteLine("ka!! ka!! ka!!...");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!!");
}
public void Run()
{
Console.WriteLine("ka!!!! ka!!!! ka!!!!...");
}
}
}
结果如下:
由例一了解了接口隔离原则,但是要注意,在使用接口隔离和单一职责原则的时候,如果玩得过火了就会产生很多很细碎,只有一个方法的接口和类
违反接口隔离原则的情况1
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace _14.接口隔离_反射_特性_依赖注入
{
internal class Program
{
static void Main(string[] args)
{
var drive = new Driver(new Car());//1.2 此时会报错,因为Drive内的IVehicle变成了ITank
drive.Drive();
Console.ReadKey();
}
}
class Driver
{
//1. 如果我把字段类型改成ITank的话
private ITank _ivehicle;
public Driver(ITank vehicle)//1.1 构造器同样更改,这样的话能作为服务提供者的类就只剩轻型,中型,重型三种坦克了,而Car和Truck类型的实例会被挡在门外,尽管实际上我们只想调用Run方法
{ //这就是典型的接口用大了(接口中功能太多,因为ITank继承了两个接口,所以有Run和Fire)
_ivehicle = vehicle;
}
public void Drive()
{
_ivehicle.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 IWeapon
{
void Fire();
}
interface ITank : IVehicle, IWeapon { }
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!!...");
}
}
}
如果上面把字段类型改成ITank,因为ITank继承了两个接口,不只有Run方法,还要实现Fire方法,但是汽车是没有Fire功能的,所以new Car的实例就会报错
违反接口隔离原则的情况2
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace _14.接口隔离_反射_特性_依赖注入
{
internal class Program
{
static void Main(string[] args)
{
//1.0 整型数组和ArrayList都是继承IEnumerable和ICollection接口的,都是可以迭代的,而ICollection接口又是继承IEnumerable接口的
//1.1 ICollection除了继承被迭代的功能以外,还继承类其他得功能,但是本例用不上
int[] num1 = { 1, 2, 3, 4, 5 };
ArrayList num2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(num1));
Console.WriteLine(Sum(num2));
//1.2 那么有没有一种集合,只实现了IEnumerable接口,没有实现ICollection接口
//4. 测试一下
//var roc = new ReadOnlyCollection(num1);
//foreach (var n in roc)
//{
// Console.WriteLine(n);
//}
//4.1 由此证明,我们这个只读的集合是可以被foreach迭代的
//4.2 foreach语句背后就是通过拿到你的Enumerator,把里面的Current元素给你迭代一遍
//4.3 但是我们发现,这个num3是无法被Sum函数调用的,因为我们传进去的东西太多了
var num3 = new ReadOnlyCollection(num1);
Console.WriteLine(Sum(num3));
Console.ReadKey();
}
//4.4 我们只用得上迭代,但是传进去的时候传的是ICollection,这样就会把一些合格的服务提供者挡在外面
//static int Sum(ICollection num)
static int Sum(IEnumerable num)//4.5 解决方法:把这里改成IEnumerable就好了,因为在服务使用者里面,只用得着迭代,用不着别的功能,IEnumerable更加符合接口隔离原则
{
int sum = 0;
foreach (var n in num)
{
sum += (int)n;
}
return sum;
}
}
//1.3 答案是没有,但是我们可以自己实现一个
class ReadOnlyCollection : IEnumerable//Alt + enter 选择实现接口
{
//2. 接下来看看这些方法怎么实现
private int[] _array;//2.1 创建一个_array用来接收数组
public ReadOnlyCollection(int[] array)//2.2 给他一个接受数组的构造器,进来之后这个数组就变成了只读的
{
_array = array;
}
//1.4 这个IEnumerable接口要求外界迭代其实例的时候,要提供一个迭代器IEnumerator
public IEnumerator GetEnumerator()//继承IEnumerable要实现的接口方法GetEnumerator,返回一个IEnumerator类型的变量
{
//2.3 当别人想要迭代我的时候,这里需要传进来一个Enumerator的实例
return new Enumerator(this);//2.5 返回对应的实例,转到3
}
#region 迭代器
//1.6 创建一个成员类Enumerator,现在这个类的全名是ReadOnlyCollection.Enumerator,转到2
public class Enumerator : IEnumerator//Alt + Enter 选择实现接口
{
//2.4 被我们这个迭代器所迭代的collection
private ReadOnlyCollection _collection;
public Enumerator(ReadOnlyCollection collection)
{
_collection = collection;
//3.5 这里也要给_head设一个初始值
_head = -1;
//3.6 完成以上步骤我们就有了一个只能被迭代,不能往里添加、删除新元素的集合,转到4
}
//3. 再来看看Enumerator里面怎么实现
private int _head;//3.2 那就给他一个
public object Current //3.1 需要一个当前的对象 //=> throw new NotImplementedException();这个语法我们不用,是一个只读属性
{//当前要传出去的就是_head这个index所指的我们数组里的那个元素
get
{
object o = _collection._array[_head];//将_head所指的整数传给o
return o;//因为这个方法是object类型的,所以不能直接返回整数,要返回object类型
}
// 现在是只读状态
}
//3.3 继续处理下一个方法
public bool MoveNext()
{
if (++_head < _collection._array.Length)//先加加再比较,如果大于就会越界
{
return true;
}
else
{
return false;//越界了
}
}
//3.4 再来处理这个
public void Reset()
{
_head = -1;
}
}
#endregion
}
//1.5 如果在外面声明一个Enumerator类可能会干扰整个程序,所以我们使用成员类
}
这个例子比较难懂,所以注释有点多,新手理解不了也没关系,慢慢学习就知道了,其实我也是一知半解,这个例子同样是违反了接口隔离原则,Sum方法中ICollection接口提供的功能过多了