接口,依赖反转,单元测试
接口是协约是规定,所以必须是公开的,只能是public;
static void Main(string[] args) { int[] num1 = new int[] { 1, 2, 3, 4, 5 }; Console.WriteLine(Sum(num1).ToString()); Console.WriteLine("=================="); Console.WriteLine(Avg(num1).ToString()); } static int Sum(int[] arr) { int result = 0; foreach (var item in arr)result += item; return result; } static double Avg(int[] arr) { int result = 0; double count = 0; foreach (var item in arr) { result += item; count++; }; return (result/count); } }
在上述代码中,如果我们的参数不是int[]类型,而是ArrayList类型(存放的是object类型)
方案一进行方法的重载,在方法内部进行强制类型转换:
static void Main(string[] args) { int[] num1 = new int[] { 1, 2, 3, 4, 5 }; ArrayList array = new ArrayList { 1, 2, 3, 4, 5 }; Console.WriteLine(Sum(array).ToString()); Console.WriteLine("=================="); Console.WriteLine(Avg(array).ToString()); } static int Sum(ArrayList arr) { int result = 0; foreach (var item in arr)result += (int)item; return result; } static double Avg(ArrayList arr) { int result = 0; double count = 0; foreach (var item in arr) { result += (int)item; count++; }; return (result/count); }
在上述问题中,我们要调用的求和和求平均数,就是甲方,提供的方法为乙方,仔细观察不难发现我们的甲方只要求其方法参数为可迭代的集合就行了,查看int[]和ArrayList的基类,它们都是可迭代的,实现了IEnumerable接口
static void Main(string[] args) { int[] num1 = new int[] { 1, 2, 3, 4, 5 }; ArrayList array = new ArrayList { 1, 2, 3, 4, 5 }; Console.WriteLine(Sum(num1).ToString()); Console.WriteLine("=================="); Console.WriteLine(Avg(array).ToString()); } static int Sum(IEnumerable arr) { int result = 0; foreach (var item in arr)result += (int)item; return result; } static double Avg(IEnumerable arr) { int result = 0; double count = 0; foreach (var item in arr) { result += (int)item; count++; }; return (result/count); }
通过这样的实现解决问题,把具体参数变抽象,用契约管理供需关系。
下一个实例继续:
public class Program { static void Main(string[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.Run(3); Console.WriteLine(car.Speed.ToString()); } } public class Engine { public int RPM { get;private set; } public void Work(int gas) { this.RPM = 1000 * gas; } } public class Car { public Engine engine { get;private set; } public int Speed { get;private set; } public Car(Engine engine) { this.engine = engine; } public void Run(int gas) { this.engine.Work(gas); this.Speed = this.engine.RPM / 100; } }
上面这段代码当Engine中的Work出现问题时,会导致Car调用出现问题,这是因为Car已经和Engine类紧耦合了
追求低耦合的原因:降低对提供方的需求,只需满足协约就满足要求,可替换。
接口与单元测试
接口的产生:自底而上(重构),自顶向下(设计)
C#中接口的实现(隐式,显示,多接口)
语言对面向对象设计的内建支持:依赖反转,接口隔离,开/闭原则
public class Program { static void Main(string[] args) { var fan = new DeskFan(new PowerSupply()); Console.WriteLine(fan.Work()); } } public class PowerSupply { public int GetPower() { return 100; } } public class DeskFan { private PowerSupply PowerSupply; public DeskFan(PowerSupply powerSupply) { this.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 "Waring"; } }
当前代码如果我们要测试的话会直接修改PowerSupply里面的方法内的数值,在当前是不能这样干的,可能有别的代码引用这个PowerSupply这个类,所以我们需要引入接口来解耦。
public interface IPowerSulpply { int GetPower(); } private IPowerSulpply PowerSupply; public DeskFan(IPowerSulpply powerSupply) { this.PowerSupply = powerSupply; }
现在可以进行单元测试了。具体步骤如下:
点击VS顶级菜单Test->Window->Test Explore
右击解决方案,添加项目,添加对应的单元测试项目。
命名为要测试的项目名加“.Test”即可。
接着引用被测试项目。
编写代码方法等
[Fact] public void PowerSupplyThanZeroTest() { var oldvalue = "won't work"; var power_ = new DeskFan(new PowerSupplyThanZero()); var newvalue = power_.Work(); Assert.Equal(oldvalue, newvalue); } public class PowerSupplyThanZero : IPowerSulpply { public int GetPower() { return 0; } }
打开测试管理器界面,点击对应的方法运行,全部变绿证明测试通过了。
上面调用是构造类的实例然后更改调用的方法的值,使用Moq可以极大减轻我们的工作量。
var oldvalue = "won't work"; //var power_ = new DeskFan(new PowerSupplyThanZero()); var mock = new Mock<IPowerSulpply>(); mock.Setup(a => a.GetPower()).Returns(() => 0); var power = new DeskFan(mock.Object); var newvalue = power.Work(); Assert.Equal(oldvalue, newvalue);