本次利用代码细致解释一下接口的具体使用方法,以及介绍解耦的重要性和方法
借用刘铁猛老师PPT里的一个图,感觉很清晰的说明了接口,抽象之间的联系
接口与单元测试
使用接口的好处
接口的本质,决定了接口中的纯虚方法必须是public,而抽象类中的方法,只要不是private就好了。成员访问级别(public)决定了接口的本质。它的本质是服务的消费者与服务提供者的“契约”
抽象类当中,被protected修饰的成员,只能够让自己的子类看到,internal出了自己的程序集,就看不到了。但是接口想要别人看到,就不能加以限制,必须使用public。
在未使用接口的情况下,会发生这种事。定义了一个数组和一个Arraylist 类型的,想对他们进行求和和求取平均数,由于两个的类型不同,于是,需要写出四组函数。
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
}
static int Sum(int[] nums)
{
int sum = 0;
foreach (var n in nums) sum += n;
return sum;
}
static double Avg(int[] nums)
{
int sum = 0;double count = 0;
foreach (var n in nums) { sum += n;count++;}
return sum / count;
}
上述是求出了数组的Sum和Avg,接下来是Arraylist的:
static int Sum(ArrayList nums)//这里要改成Arraylist类型
{
int sum = 0;
foreach (var n in nums) sum += (int) n;//因为Arraylist里的是object类型的,所以要强制转换一下类型
return sum;
}
static double Avg(ArrayList nums)
{
int sum = 0;double count = 0;
foreach (var n in nums) { sum += (int)n;count++;}
return sum / count;
}
这样要写出四个函数,是相当不方便的。
但如果引入了接口以后,就会变的简易很多,因为Array里面,是有IEnumerable这个接口,而Arraylist类型中也支持IEnumerable接口,于是在函数传进去的值类型里面写入IEnumerable,就可以达到通用的效果,如下。比上述定义4个函数要更方便
static void Main(string[] args)//这样只需要两个函数便能达到之前的目的,便捷了许多
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
Console.WriteLine(Sum(nums2));
Console.WriteLine(Avg(nums2));
}
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var n in nums) sum += (int) n;
return sum;
}
static double Avg(IEnumerable nums)
{
int sum = 0;double count = 0;
foreach (var n in nums) { sum += (int)n;count++;}
return sum / count;
}
}
在面向对象中的合作,有个术语,叫依赖。但是依赖的越直接,耦合就会越紧。
下列代码,Engine类和Car类就是紧耦合了,若Engine类里面出了问题,那么Car类就永远不可能正常运行(Engine类是被依赖类),这就是紧耦合出现的弊端。
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
}
}
class Engine
{
public int RPM { get; private set; }
public void Work(int gas)
{
this.RPM = 1000 * gas;
}
}
class Car
{
private Engine _engine;//这是个字段,这样他们两个类就耦合在一起了
public Car(Engine engine)//这个是构造器
{
_engine = engine;
}
public int Speed { get; private set; }
public void Run(int gas)
{
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
将耦合变松的方法,就是引用接口方法(这个例子要十分熟悉,最好自己好好的重写几次)
可以看见下面,只需要改变实例对象传入的值即可。
_phone = phone这里的注释很关键
class Program
{
static void Main(string[] args)
{
var user = new PhoneUser(new NokiaPhone());
//这里就引用的Nokia的,想改变只用改这里就能调用不同的手机了
user.UsePhone();
}
}
interface Iphone
{
void Dail();
void PickUp();
void Send();
void Receive();
}
class PhoneUser
{
private Iphone _phone;//这是接口类型的phone
public PhoneUser(Iphone phone)
{
_phone = phone;
//这里实际上是把NokiaPhone的实例,交给了接口类型的字段来引用,这样的话_phone访问的就是Iphone里的方法了。然后根据多态原则,所以访问的是NokiaPhone里的具体方法。
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Receive();
_phone.Send();
}
}
class NokiaPhone : Iphone
{
public void Dail()
{
Console.WriteLine("NokiaPhone calling......");
}
public void PickUp()
{
Console.WriteLine("Hello,this is tt");
}
public void Receive()
{
Console.WriteLine("NokiaPhone ring......");
}
public void Send()
{
Console.WriteLine("NokiaPhone sending");
}
}
class HuaWeiPhone : Iphone
{
public void Dail()
{
Console.WriteLine("HuaWeiPhone calling......");
}
public void PickUp()
{
Console.WriteLine("Hello,this is tt");
}
public void Receive()
{
Console.WriteLine("HuaWeiPhone ring......");
}
public void Send()
{
Console.WriteLine("HuaWeiPhone sending");
}
}
这样就达到了松耦合的目的,有更高的可靠性。可以让功能的提供方可以被替换
依赖反转
用来平衡我们常规思路——自顶向下,这种单一的思路。而是将问题从下向上思考:不是使用紧耦合的方法,改用接口,实现使用者和提供者不是紧密一对一的关系,而是通过接口这样一个交换机一样的存在,让使用者自主选择使用的内容,提供者只需提供自己的方法即可。这样可以极大减小出错的检查的成本。
接口与单元测试
下述代码实现了一个,关于桌面电扇的电源供电问题,在给予电量方面不一样的值会有不一样的反应。
但是我们不知道,是否其中哪里有问题,或者是其中哪一个单元板块会异常,这个时候就需要用到测试。用了接口的好处也可体现在,现在可以用单元测试。测试每一个模块,更容易定位问题。
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
}
}
public interface IPowerSupply
{
int GetPower();
}
public class PowerSupply:IPowerSupply
{
public int GetPower()
{
return 100;
}
}
public class DeskFan
{
private IPowerSupply _powersupply;
public DeskFan(IPowerSupply powersupply)
{
_powersupply = powersupply;
}
public string Work()
{
int power = _powersupply.GetPower();
if (power <= 0)
{
return "Won't work";
}
else if (power<100)
{
return "slow";
}
else if (power<200)
{
return "Work fine";
}
else return "Fire";
}
}
单元测试
需要新建一个项目:
然后选择:
接着在这个项目里添加对要测试项目的依赖,就可以开始编写代码了
public class DeskFanTests
{
[Fact]//特征或特性,暂时先不管他
public void PowerLowerThanZero_Ok()
//根据名字很好判断要测试的是什么单元
{
var fan = new DeskFan(new PowerSupplyLowerThanZero()
var expected = "Won't work";
var actual = fan.Work();
Assert.Equal(expected, actual);
//测试两个是否相等,正确测试就会通过
}
[Fact]
public void PowerSupplyHigherThan200_Warning()
{
var fan = new DeskFan(new PowerSupplyHigherThan200());
var expected = "Fire";
var actual = fan.Work();
Assert.Equal(expected, actual);
}
}
class PowerSupplyLowerThanZero : IPowerSupply
{
public int GetPower()
{
return 0;
}
}
class PowerSupplyHigherThan200 : IPowerSupply
{
public int GetPower()
{
return 230;
}
}
但是在测试里,每次定义类,会特别麻烦,如图:
所以,这里可以引用一下Moq,在Nuget程序包里搜索Moq,在安装Moq后,使用这个名称空间。就可以不用定义类了
using Moq;
namespace Interface.Test
{
public class DeskFanTests
{
[Fact]//特征或特性,暂时先不管他
public void PowerLowerThanZero_Ok()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 0);
var fan = new DeskFan(mock.Object);
var expected = "Won't work";
var actual = fan.Work();
Assert.Equal(expected, actual);
}
[Fact]
public void PowerSupplyHigherThan200_Warning()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 0);
var fan = new DeskFan(mock.Object);
var expected = "Fire";
var actual = fan.Work();
Assert.Equal(expected, actual);
但是Moq,其实不知道具体的实现原理,先知道可以这样写,mock的写法也不太懂。