(六)接口
1.什么是接口

- 接口与抽象类的使用方式非常类似,同样也是一种仅作功能声明不做代码实现的语法结构。
- 在.net中有一个行业标准,就是接口的名称无论是什么都需要在前面加上个大写I,代表interface。
- 接口的成员方法没有访问修饰符。这里,我们没有public、没有私有、没有protected。这是因为,凡是接口内声明成员方法全部都是public,而且绝对没有更改的可能。
- 使系统之间的耦合减到最小,甚至耦合为零。

2. 代码
订单处理系统的确与价格系统有依赖,但是订单处理系统依赖不是价格系统,而是价格系统的接口。也就是说谁能给我提供价格计算,我就依赖于谁。而这种依赖关系仅存在于main方法层面上的,这是两个系统的外部依赖而不是内部依赖。
在订单计算系统中
private readonly IShippingCalculator _shippingCalculator;
public OrderProcessor(IShippingCalculator shippingCalculator)
{
_shippingCalculator = shippingCalculator;
}
不同促销方式去实现这个接口
在main方法中,传递不同参数给计算系统就行
// 双十一
IShippingCalculator doubleEleven = new DoubleElevenShippingCalculator();
// 普通
IShippingCalculator putong = new ShippingCalculator();
OrderProcessor orderProcessor;
if (DateTime.Now == new DateTime(2050, 11, 11))
{
orderProcessor = new OrderProcessor(doubleEleven);
}
else
{
orderProcessor = new OrderProcessor(putong);
}
orderProcessor.Process(order);
3.接口与单元测试
- 右键点击“解决方案”,选择“添加”,点击“添加新项目”。
- 在添加新项目窗口的类型选择菜单中,找到“测试”。选择“MSTest”。
这个MSTest是什么呢?其实就是微软开发的测试框架,同样的道理,这里的xUnit、NUint同样也是测试框架,只不过是由不同的组织开发的第三方测试框架。 - 选项中还有一个“单元测试项目”,这是基于传统.net平台的,只能在windows平台上运行,不建议选择。

4.TDD-测试驱动开发到底是什么
测试驱动开发,英文全称是Test-Driven Development,简称为TDD,它是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写好测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。
4.1 单元测试
从上引出的一个概念-单元测试,这里简单说明下。单元测试一般是由开发者在编写业务功能代码时, 为了验证自己开发的每个代码单元而编写的一种独立的测试。完成单元测试后,开发人员会出一份单元测试报告以及单元测试覆盖率报告, 测试人员需要以以上报告作为设计功能测试用例的依据。
4.2 测试驱动开发的流程

4.3 测试驱动开发与传统开发的区别
传统开发模式下编写的单元测试脚本其实还是为了验证功能,仍属于测试的一种形式。而测试驱动开发已经不再是一种简单的测试行为了,准确地说,它是一种设计行为。测试驱动开发类似于是对一段代码的用法而编写的设计规格说明,可以从编写代码的内聚性、可测性、可复用性以及缺陷密度、接口的简易水平看出。在测试驱动开发中,以需求的剖析、用户业务功能的理解都是层层递进的,是可以逐步细化到代码级别的,而这样的过程也恰恰提高了测试的覆盖率,同时能从根本上解决一些因业务逻辑设计错误而导致的后期大量的缺陷修复工作。
4.4 测试驱动开发的优缺点
从以上的TDD周期图中也可以看出,测试驱动开发最大的优点就是重构了,不断迭代,不断地对现有代码进行重构,不断优化代码的内部结构,最终实现对整体代码的改进。以此不断减少一些设计冗余、代码冗余、接口复杂度等等。
另外,对于一些前期需求不明确,甚至需求信息量特别少,且后期又会有大量业务功能修改时,传统的开发模式需要加班加点以此赶工开发,测试,缺陷修复,人工、时间成本且不说,最重要的产品质量也无法得到保证。当然 ,这种模式下也最适合采用原型法、敏捷开发模式了,毕竟拥抱变化是敏捷的宗旨 。而测试驱动开发也是敏捷开发模式的基础,这样无论是来自客户的紧急需求还是项目团队的一次技术改革,都可以通过重构设计、增加测试脚本来实现了。
4.5 测试驱动开发的应用领域
以上分析了测试驱动开发的诸多优势,但目前并没有被大量广泛的使用起来,一来这是一种技术或管理方式上的变革需要慢慢被大众所熟知 ; 二来需要足够专业技能、专业素质的人才来保证整个过程的通畅与专业; 第三,前期需要一定的投入,而它产生的输出足以解决目前多数企业中遇到的质量、效率、成本等难题,所以选择是一步,真正能够严格执行下去的很少。
5.反转控制与依赖注入

随着工业级软件应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系。

为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”。
5.1 什么是IOC
参考:https://www.cnblogs.com/zouwangblog/p/11381339.html
- IOC全称Inversion of Control,中文翻译为控制反转或者控制倒置
IOC的概念早在1996年就被大神Michael Mattson提出来了。简单来说,就是把原本复杂、高度耦合系统分解成一个个独立的接口interface, 然后通过一个中间人也就是IOC容器通过调用接口把已经独立出来的对象重新串联起来的过程。通过使用ioc容器对对象进行封装以后,我们的系统内部就实现了对外部的透明,从而显著的降低了系统的整体复杂度,而通过IOC容器,组件还可以灵活地被重用和扩展。

在引入了中间人、也就是IOC容器的概念以后,A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠中间人,而全部对象的控制权也全部交给了这个中间人。
5.2 依赖注入(DI)
2004年,Martin Fowler在提出了ioc理论的8年后,有跑出了一个问题,“既然IOC是控制反转,那么到底是哪些方面的控制被反转了呢?”
- “获得依赖对象的过程被反转了”。
- 所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
举个例子,USB接口和USB设备
USB接口,我们可以从u盘中读取文件。电脑在读取u盘中文件的时候,它不会关心USB接口上连接的是什么设备,它只需要知道它的任务就是读取USB接口就可以了,接入什么设备,电脑是决定不了,它只能被动的接受。需要使用到某个外部设备的时候,我就会主动帮它接入usb插口。这就是我们生活中常见的依赖注入的例子。在这个过程中,我就起到了IOC容器的作用。
依赖注入(DI)和控制反转(IOC)
- 从不同的角度的描述的同一件事情
- 通过引入IOC容器,实现对象之间的解耦。
- 核心思想:interface接口,面向接口
5.3 IOC的好处
比如说USB的例子,使用USB外部设备比使用内置硬盘,到底带来什么好处?
- 低耦合性。
USB设备作为电脑的外部设备,在插入主机之前,与电脑主机没有任何关系,只有被我们插入usb接口连接在一起以后,两者才会发生联系。所以,无论两者中的任何一方出现什么的问题,都不会影响另一方的运行。 - 标准性
正是因为USB设备和电脑的之间没有关系,所以,生产USB设备的厂商和生产电脑主机的厂商可以使两组完全不想干的人,他们各司其职,唯一需要遵守的就是USB接口标准。这种特性体现在软件开发过程中,就叫做面向接口编程。 - 可重复性
同一个USB可以接到任何支持USB的设备上,可以插接到电脑上,也可以插接到智能电视上,也就是说这个USB设备使可以被反复利用的。 - 热插拔性
使用ioc容器的最后一个好处就是,代码模块可以具有热插拔特性。IOC容器生成对象的方式可以简单理解为外挂模式,于是,我们在更换一个组件的时候就会非常简单,只要在ioc容器中挂载另一个组件就可以了,这就是模块热插拨特性。
对于ioc来说,.net实现了一整套非常详细的技术框架,每一个.net程序员都一定会接触。不过,对于初学者来说,不要试图一上来就去看ioc的实现原理、去看ioc的源代码,那样只会越看越晕。我们可以先暂时把ioc容器看作一个黑盒子,我们不知道也不需要知道他是如何工作的,但是我们知道的是使用了ioc可以使两个对象的依赖关系通过接口interface转化为引用关系。
5.4 官方源码
首先,我们来看看微软官方是如何处理反转控制容器的。
https://docs.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection
理论的部分跳过,我们直接找到main方法部分的案例实现。

请注意绿色框框的这行代码。在这里,我们将会把所有的class,比方说这个MessageWriter通过接口的方式添加到反转控制容器中,让反转控制容器在程序运行的过程中自动帮我进行实例化,创建相应的对象。而这整个过程可以全程委托给反转控制容器自动处理,我们不需要手动参与。
5.5 实操知识点
- 反转控制容器叫做ServiceCollection,同样也是一个对象
- 把价格计算和订单处理都放入IOC容器
- IOC容器通过接口识别服务
- 把订单服务接口化处理
当我们在ioc容器中注册好服务以后,这个服务的生命周期就全权委托给ioc来负责了。所以,在向ioc容器中分别注册了两个服务以后,我们就不需要手动使用 new 关键词来创建新实例了。创建新实例的过程都被封装在ioc容器了,它会自己去分析两个服务的依赖关系,自己来决定实例化的处理时机。
main方法:
//创建一张订单
var order=new Order
{
Id=123,
DatePlaced=DateTime.Now,
TotalPrice=100f
};
//IShippingCalculator doubleEleven = new DoubleElevenShippingCalculator();
////创建订单处理系统
//var orderProcessor = new OrderProcessor(doubleEleven);
//orderProcessor.Process(order);
//配置IOC
ServiceCollection services = new ServiceCollection();
//singleton,单例模式
//scoped,作用域模式
//tansient,瞬时模式
services.AddScoped<IOrderProcessor,OrderProcessor>();
services.AddScoped<IShippingCalculator,DoubleElevenShippingCalculator>();
//从IOC提取服务
IServiceProvider serviceProvider=services.BuildServiceProvider();
var orderProcess=serviceProvider.GetService<IOrderProcessor>();
//处理订单
orderProcess.Process(order);
5.6 在.net中, ioc服务的添加方式有三种
- Singleton:单例模式,ioc在管理单例服务的时候,全局创建唯一的一个实例。
- Scoped:而 scope 也叫做作用域模式,ioc会在在一个作用域内创建唯一一个实例。比如,在后端api程序中,处理一次htpp请求的过程相当于一个Scoped
- Transient:第三种,瞬时模式,每次调用服务的时候ioc都会创建一个新的实例对象,而服务操作结束以后,ioc也会自动回收内存,删除这个实例,基本上用Transient所定义服务都是
services.AddSingleton<IOrderProcessor,OrderProcessor>();

services.AddTransient<IOrderProcessor,OrderProcessor>();

services.AddScoped<IOrderProcessor,OrderProcessor>();

6.nuget
我们需要引入Microsoft.Extensions.DependencyInjection这个命名空间。不过这个命名空间并不属于.net标准库内所包含的内容,所以,这个部分的代码需要我们使用nuget这个工具来下载、添加相关的库文件。
- 打开文件浏览器,右键点击项目名称,点击“管理nuget程序包”。
- 选择nuget界面最上面的浏览,然后在搜索栏中输入Microsoft.Extensions.DependencyInjection
- 在安装的时候注意选择自己 .net 版本所对应的版本

说明添加成功,这个类库所定义的所有代码我们都能够在项目中直接使用了。
- 用来管理库文件的版本依赖关系
大同小异:


新建类库中的projects是当前项目对其他项目源代码级别的直接引用
7. 多重继承和多重实现
- c#中不存在多重继承
- 可以使用接口模仿多重继承的实现方式
- 使用接口的目的不是为了代码的复用,而是构建低耦合,可拓展,可测试的模块化应用。
- 接口实现了类的多态现象
- 接口与继承没有关系
public class UIText : UIBase, IDragable, ICopyable
{
public void Copy()
{
throw new NotImplementedException();
}
public void Drag()
{
throw new NotImplementedException();
}
public void Paste()
{
throw new NotImplementedException();
}
}
public interface IDragable
{
void Drag();
}
public interface ICopyable
{
void Copy();
void Paste();
}
public class UIBase
{
public int Size { get; set; }
public int Position { get; set; }
public void Draw()
{
Console.WriteLine("绘制UI");
}
}
8. 接口与多态



浙公网安备 33010602011771号