文章目录
什么是接口?
接口的本质:功能的调用者与功能的提供者之间共同遵循的契约(contract)
为什么要使用接口?
在代码中,如果有可以替换的地方,就一定有接口的存在;接口就是为了松耦合而生,而松耦合最大的好处,就是让功能的提供者变得可替换
例1:对一组整数进行求和,求平均值操作
(这一组整数可放在整型数组中,也可放在 ArrayList 中)
(1)不使用接口:需要分别对两种类型的整数组写求和,求平均值的静态方法,一共要写4个方法
(2)使用接口:由于 int[ ],ArrayList 都实现了【 IEnumerable 】这个接口,都拥有可被迭代的功能,所以,只要让功能的调用者——求和,求均值的方法的形参类型为 IEnumerable 类型,就不必再写功能重复的方法了
例2:汽车的启动需要引擎
也就是说:汽车依赖于引擎,它们之间形成了紧耦合关系
(1)不使用接口:汽车与引擎形成了紧耦合关系,但如果将Engine类中的_rpm字段误写为0,那么就算Car实例的Speed属性值为0,还是会打印出状态 “car is running…”
(2)使用接口以达到松耦合的目的(写的好像不太好)
例3:手机坏了怎么办
功能的提供者:各种手机;功能的调用者:手机用户
(1)不使用接口,手机用户就只能使用一种手机
(2)使用接口:旧的不去新的不来~给爷整个最新款,爷会使!
再次强调:接口就是为了松耦合而生,它解决了在紧耦合中,功能的提供方不可替换所带来的高风险,高成本
依赖反转原则
High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象
依赖反转原则——面向接口编程,解耦在代码中的表现,其实就是依赖反转
例4:全能的司机
(1)司机依赖于车:每个司机只能开一种车
(2)开始大乱斗:多个功能的提供者与多个功能的调用者都遵循同一个接口
(3)再加一个无人驾驶系统还不是小菜一碟?
- 依赖反转原则,其实就是给我们提供了一种新的思维方式,用来【平衡】【自顶向下,逐步求精】这种单一的思维方式
- 自顶向下,逐步求精:将大问题分解为小问题,小问题逐一解决后 ,大问题也就迎刃而解了;那么,解决问题的编程实体,就是函数,从而各个函数之间形成了【依赖关系】
- 有了面向对象之后,函数都是封装在类和对象中的,所以依赖关系存在于类与类,对象与对象之间
- 适当地引用接口,运用【依赖反转原则】,可以有效地减少耦合度,使代码更"清爽",更好维护
单元测试
单元测试其实就是依赖反转在开发当中的直接应用和直接受益者
什么是单元测试?
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
比如对函数abs(),我们可以编写出以下几个测试用例:
- 输入正数,比如1、1.2、0.99,期待返回值与输入相同;
- 输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;
- 输入0,期待返回0;
- 输入非数值类型,比如None、[]、{},期待抛出TypeError。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
-
单元测试通过后有什么意义呢?如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。
-
这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。
例5:不准乱碰电源
(1)风扇的运转是依赖于电源的
(2)引入进口,进行解耦
接口的产生:自底向上(重构),自顶向下(设计)
自顶向下的情况一般是非常了解业务逻辑,一上来就知道该怎样设计代码,设计接口;但更多的情况是不断地重构代码,发现需要解耦,才返回来声明一个接口,如此下去
(3)进行标准的单元测试
<1>打开 Test Explorer ,测试case和结果都会显示在其中
(Test ——Window)
<2>选择测试框架并命名
(右键 Solution ——Add ——new project ——Test),一般测试项目的命名都是被测试项目后加 “.Tests”
- 每一个类就是一组 Test case 【TestClass】
- 每一个 Test case 就是一个方法 【TestMethod】
<3>在【InterfaceExample.cs】中引用被测试项目
<4>写测试case
<5>再写出其他电流情况进行测试
假设在生产电扇时出了问题,当电压超过200V时,不是给警告,而是直接爆炸
测试:
<6>开始debug
在出现问题的测试case处设置断点,右键 Failed Tests 开始debug,就会发现问题出在什么地方了;把 “Explode!” 修改为 “Warning!”,测试通过
注意:测试代码在工作中同样非常重要,不要忽略测试代码
补充
在例5中,我第一次写代码出现了这样的问题
这是因为:C#中变量的初始化优先于构造函数
所以编译器无法保证在初始化变量 I 时,能够得到预期的值
- 参考资料
单元测试
字段初始值无法引用非静态字段、方法或属性