c#之接口,依赖反转,单元测试
1.接口
弱类型语言允许将一块内存看做多种类型。比如直接将整型变量与字符变量相加。C and C++ 是静态语言,也是弱类型语言;Perl and PHP 是动态语言,但也是弱类型语言。
强类型语言在没有强制类型转化前,不允许两种不同类型的变量相互操作。Java、C# 和 Python 等都是强类型语言。
下面代码简单介绍了使用接口的例子,因为c#是强类型语言,所以如果Sum()方法的参数不是IEnumerable的话,就只能是int[]或者ArryList,这种情况下,如果想num与arrayList对象都用sum和avg方法的话就得每个都写一遍,但是因为数组和ArrayList都继承了IEnumerable,所以可以直接用它做参数接收。
class Program { static void Main(string[] args) { int[] num = new int[] { 1, 2, 3, 4, 5 }; ArrayList arrayList = new ArrayList { 1, 2, 3, 4, 5 }; Console.WriteLine(Sum(num)); Console.WriteLine(Avg(num)); Console.WriteLine(Sum(arrayList)); Console.WriteLine(Avg(arrayList)); Console.ReadKey(); } static int Sum(IEnumerable num) { int sum = 0; foreach(var n in num) { sum += (int)n; } return sum; } static double Avg(IEnumerable num) { int sum = 0; double count = 0; foreach (var n in num) { sum += (int)n; count++; } return sum/ count; } }
结果:
15 3 15 3
2.依赖反转
https://blog.csdn.net/jerry11112/article/details/79027834 这里写的面向对象编程很不错
面向对象编程就是先抽象出对象,然后用对象执行方法的方式解决问题。
面向对象就是对现实世界的抽象,在现实世界中人与人之间相互协作分工做事,但是在抽象世界中,这种分工合作可以理解成“依赖”,相应的就出现了耦合。
(1)比如说汽车依赖于引擎才能跑,没有引擎,车也就没什么用处了。用面向对象的方式抽象出汽车和引擎2个类,代码如下:
class Program { static void Main(string[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.Run(3); Console.WriteLine(car.Speed); Console.ReadKey(); } } /// <summary> /// 引擎 /// </summary> class Engine { /// <summary> /// 转速,这里的属性只能读,不能在其他类进行修改 /// </summary> public int RPM { get; private set; } /// <summary> /// 引擎转动, /// </summary> /// <param name="gas">汽油,汽油越多,转速越快,</param> public void Work(int gas) { this.RPM = 1000 * gas; } } /// <summary> /// 车类 /// </summary> class Car { private Engine _engine; /// <summary> /// 车必须要有一个引擎,否则没法动。 /// </summary> /// <param name="engine"></param> public Car(Engine engine) { _engine = engine; } public int Speed { get; private set; } /// <summary> /// 汽车运行 /// </summary> /// <param name="gas"></param> public void Run(int gas) { _engine.Work(gas); this.Speed = _engine.RPM / 100; } }
从上面代码我们可以看出,car类是完全依赖于engine类的,被依赖的类一旦除了问题,依赖的一方也会出问题,如果代码一多,问题处理起来就会很麻烦。在实际工作中,如果一个程序员负责开发engine类,但是只有这个类问题改完,其他依赖于这个类的其他类才能继续使用。
(2)接口
如上述情况,我们可以使用接口,来实现降低耦合度。接口就是一组契约,用来约束一组功能。接口的调用者是被约束的,只能调用接口中所包含的功能。还保证了这些接口中的功能一定是实现好了的。
接口的产生:自底向上(重构),自顶向下(设计)
在接到一个项目的时候,如果你是一个很厉害的并且理解这个项目的业务逻辑,在一开始的时候就想到了在什么地方用接口,此时就是自顶向下的方式产生接口。但是更多的时候是我们在写代码的过程中不断的重构代码,觉得哪些地方需要接口
例子:比如以前的旧手机,一定会有的功能是打电话,接电话,发短信,收短信。我们用接口来实现这些功能。我用的手机是一定会有这些功能的,而且这些功能都是好用的。请注意,这个接口对于手机厂商来说,这四个功能是必须要实现的。使用接口实现的好处在于你更换手机之后还是可以直接用,不存在换了手机之后这4个功能就有不能用的了。
代码如下:
namespace TestClass { class Program { static void Main(string[] args) { var user = new PhoneUser(new HUANWEIPhone()); user.UsePhone(); Console.ReadKey(); } } public interface IPhone { void Dail(); void PickUp(); void Send(); void Receive(); } /// <summary> /// 诺基亚手机 /// </summary> public class NokiaPhone : IPhone { public void Dail() { Console.WriteLine("NokiaPhone is Dailing"); } public void PickUp() { Console.WriteLine("NokiaPhone is PickUping"); } public void Receive() { Console.WriteLine("NokiaPhone isReceiveing"); } public void Send() { Console.WriteLine("NokiaPhone is Sending"); } } public class HUANWEIPhone : IPhone { public void Dail() { Console.WriteLine("HUANWEIPhone is Dailing"); } public void PickUp() { Console.WriteLine("HUANWEIPhone is PickUping"); } public void Receive() { Console.WriteLine("HUANWEIPhone isReceiveing"); } public void Send() { Console.WriteLine("HUANWEIPhone is Sending"); } } /// <summary> /// 手机使用者:它的行为就是能够使用不同的手机 /// </summary> public class PhoneUser { private IPhone _phone; public PhoneUser(IPhone phone) { _phone = phone; } /// <summary> /// 使用手机以及其能力 /// </summary> public void UsePhone() { _phone.Dail(); _phone.PickUp(); _phone.Send(); _phone.Receive(); } } }
结果:
HUANWEIPhone is Dailing HUANWEIPhone is PickUping HUANWEIPhone is Sending HUANWEIPhone isReceiveing
(3)所以的依赖倒置,其中的依赖其实就是耦合性,如果A类依赖B类,那么当B类消失或修改时,对A类会有很大的影响,可以说是A类的存在完全就是为B类服务,就是说A类依赖B类。
看上图:1处是汽车司机只能开汽车,卡车司机只能开卡车,不能还换着开。Driver依赖Car,Trucker依赖Truck。
2:改完设计之后,创建了一个IVehicle接口,让Car和Truck都实现这个接口(此时这2个类和接口也是紧耦合),Driver依赖IVehicle。此时Driver既可以开Car,也可以开Trunk。注意看图,此时的箭头和1时方向变反了,所以依赖反转中的反转就是这样来的。
3:当有多种使用者,多个选择的时候,就可以实现多种配对,如图3处。此时再进一步,那就是设计模式了。
使用此时的设计模式来更改上面代码的话,可以加一个年轻人使用者类,如下:
public class YoungUser : PhoneUser { public YoungUser(IPhone phone) :base(phone) { } }
在调用的时候如下:
var userYong = new YoungUser(new HUANWEIPhone()); userYong.UsePhone();
(4)单元测试
写一个例子,表示接口,解耦,依赖倒置原则是怎么被单元测试应用的。
电扇都是由电启动的,电力越大,速度越快。但是它也是有电流保护的功能,电流过大会断开或警告。现在的依赖关系是电扇依赖电源(没有电源,电扇无法运行)。
我们先写一个紧耦合:
namespace TestClass { class Program { static void Main(string[] args) { var fan = new DeskFan(new PowerSupply()); fan.Work(); Console.ReadKey(); } } /// <summary> /// 电源供应类 /// </summary> public class PowerSupply { public int GetPower() { return 100; } } /// <summary> /// 电扇 /// </summary> public class DeskFan { private PowerSupply _powerSupply; public DeskFan(PowerSupply powerSupply) { _powerSupply = powerSupply; } /// <summary> /// 运行,还要验证电流力度 /// </summary> /// <returns></returns> public string Work() { int power = _powerSupply.GetPower(); //简单判断一下 if(power<=0) { return "Wont't work"; } else { return "Working"; } } } }
看上面代码,如果我们更改了PowerSupply中的 GetPower方法的返回值,来测试电扇,而且不只是电扇在使用这个电源,也许还有别的电器在使用,更改之后万一引起别的电器产生问题,那就不对了。所以我们可以抽象出一个电源提供接口,写一个测试类实现电源提供接口,专门用来测试电源对电力大小造成的影响,这样也不会影响到其他电器的使用,如下:
namespace TestClass { class Program { static void Main(string[] args) { var fan = new DeskFan(new TestPowerSupply()); fan.Work(); Console.ReadKey(); } } public interface IPowerSupply { int GetPower(); } /// <summary> /// 电源供应类 /// </summary> public class PowerSupply:IPowerSupply { public int GetPower() { return 100; } } /// <summary> /// 测试电流 /// </summary> public class TestPowerSupply : IPowerSupply { public int GetPower() { return 100000; } } /// <summary> /// 电扇 /// </summary> public class DeskFan { private IPowerSupply _powerSupply; public DeskFan(IPowerSupply powerSupply) { _powerSupply = powerSupply; } /// <summary> /// 运行,还要验证电流力度 /// </summary> /// <returns></returns> public string Work() { int power = _powerSupply.GetPower(); //简单判断一下 if(power<=0) { return "Wont't work"; } else { return "Working"; } } } }
要测试的话,我们可以使用单元测试进行调试。新建一个单元测试
namespace TestClassTests { public class UnitTest1 { [Fact] public void PowerLowerThanZero_OK() { var mock = new Mock<IPowerSupply>(); mock.Setup(s=>s.GetPower()).Returns(()=>0); var fan = new DeskFan(new PowerSupplyThanZero(0)); var test= fan.Work(); Assert.Equal("",test); //第二种写法,引用Moq.dll。此时就不需要再实现接口了,直接填需要的值 var mock = new Mock<IPowerSupply>(); mock.Setup(s => s.GetPower()).Returns(() => 0); var fan = new DeskFan(mock.Object); var test = fan.Work(); Assert.Equal("", test); } } public class PowerSupplyThanZero : IPowerSupply { private int _power; public PowerSupplyThanZero(int power) { _power = power; } public int GetPower() { return _power; } } }
引用于:https://www.bilibili.com/video/BV13b411b7Ht?p=28