C# 接口2.0 (028 课程)

news2024/10/6 2:30:43

参考视频教程:B站 刘铁猛 028 接口 依赖反转 单元测试

接口是一种供方和需求方都遵守的契约 — 即规则。

文章目录

  • 原始代码
  • 利用用接口简化函数重载
  • 紧耦合程序实体
  • 松耦合代码实体
  • 设计一个紧耦合 -- 接口松耦合 -- 单元测试
    • 紧耦合程序实例
    • 松耦合程序实体 -- 引入接口
    • 单元测试
      • 待测试的代码:
      • 第一个测试案例的书写:
      • 第二个测试案例,
      • 测试用例升级版
        • 利用Moq写的测试用例代码:

接口在代码中有什么优化作用?
接口提供解决代码重复的问题:
下述代码展示计算整形数组和ArrayList集合的 和 与 平均值。由于两个的类型不同,需要分别写出就算和与平均值的函数,即函数重载的实现方式。通过观察 int[] 和 ArrayList ,两者的求和和求平均值,均是迭代操作,并且两者的基类都是Array。---- 那么Array如果继承了某个可以迭代的接口,那么是不是就可以利用这个接口解决函数重载的多余代码了呢?

原始代码

using System;
using System.Collections;

namespace InterfaceAppl
{
    class Executer
    {
        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(int[] nums)
        {
            int sum = 0;
            foreach(var a in nums)
            {
                sum += a;
            }
            return sum;
        }
        static double Avg(int[] nums)
        {
            int sum = 0;double count = 0;
            foreach (var a in nums)
            {
                sum += a; count++;
            }
            return sum/count;
        }

        static int Sum(ArrayList nums)
        {
            int sum = 0;
            foreach (var a in nums)
            {
                sum += (int)a;
            }
            return sum;
        }
        static double Avg(ArrayList nums)
        {
            int sum = 0; double count = 0;
            foreach (var a in nums)
            {
                sum += (int)a; count++;
            }
            return sum / count;
        }

    }
}

ArrayList转到定义,可以看到实现了可以迭代的IEnumrable接口
在这里插入图片描述](https://img-blog.csdnimg.cn/412a98808ec74f8aa95ac7a8bc63ac1e.png)

利用用接口简化函数重载

using System;
using System.Collections;

namespace InterfaceAppl
{
    class Executer
    {
        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 a in nums)
            {
                sum += (int)a;
            }
            return sum;
        }
        static double Avg(IEnumerable nums)
        {
            int sum = 0; double count = 0;
            foreach (var a in nums)
            {
                sum += (int)a; count++;
            }
            return sum / count;
        }

    }
}

紧耦合程序实体

具体观察Car的运行,是非常依赖 Engine 中 RPM 属性的值的,当Car 运行不起来的时候,不仅要检查 Car类 的程序实体,还要检查依赖的Engine类的程序实体。

using System;

namespace InterfaceAppl
{
    class Executer
    {
        static void Main(string[] args)
        {
            var engine = new Engine();
            var car = new Car(engine);
            car.Run(30); // 加油门
            Console.WriteLine(car.Speed);
        }
    }

    class Engine
    {
        public double RPM { get;private set; } // 属性 注意 set是private的
        public void Work(double gas)
        {
            this.RPM = 1000 * gas; 
        }
    }

    class Car
    {
        private Engine _engine;
        public Car(Engine engine)
        {
            _engine = engine;
        }
        public double Speed { get;private set; }
        public void Run(int gas)
        {
            this._engine.Work(gas); // 加油门
            this.Speed = this._engine.RPM / 100; // 跑起来的速度
        }
    }
}

松耦合代码实体

更换手机,只需要考虑到手机的型号,不需要考虑到解决手机的内部功能,将 new HuaWei() 换成 new XiaoMi()即可。(后续更厉害的其他知识点 – 反射,可以解决不要更改手机型号名,通过读取配置文件的形式解决)
在这里插入图片描述

using System;

namespace InterfaceAppl
{
    class Executer
    {
        static void Main(string[] args)
        {
            var phone = new PhoneUser(new HuaWei());
            phone.UserPhone();
            Console.ReadKey();
        }
    }

    class PhoneUser
    {
        private IPhone _phone; // 定义一个接口类型的成员
        public PhoneUser(IPhone phone)
        {
            _phone = phone;
        }
        public void UserPhone()
        {
            _phone.Dail();
            _phone.PickUp();
            _phone.Send();
            _phone.Recive();
        }
    }

    interface IPhone
    {
        void Dail();
        void PickUp();
        void Send();
        void Recive();
    }
    class XiaoMi : IPhone
    {
        public void Dail()
        {
            Console.WriteLine("Xiao mi is Dail");
        }

        public void PickUp()
        {
            Console.WriteLine("Xiao mi is PickUP");
        }

        public void Recive()
        {
            Console.WriteLine("Xiao mi is Recive");
        }

        public void Send()
        {
            Console.WriteLine("Xiao mi is Send");
        }
    }

    class HuaWei : IPhone
    {
        public void Dail()
        {
            Console.WriteLine("huaWei is Dail");
        }

        public void PickUp()
        {
            Console.WriteLine("HuaWei is PickUP");
        }

        public void Recive()
        {
            Console.WriteLine("HuaWei is Recive");
        }

        public void Send()
        {
            Console.WriteLine("HuaWei is Send"); 
        }
    }
}

在这里插入图片描述

设计一个紧耦合 – 接口松耦合 – 单元测试

紧耦合程序实例

DeskFan的运行依赖PowerSupply中对电源的设置的值,当需要对DeakFan的运行状态进行测试的时候,需要对PowerSupply进行修改,这样的操作的不合理之处:破坏的程序的封闭性,并且直接修改很不方便;若有其他的类从Deskfan继承,则会影响其他类的对象的运行情况。

using System;

namespace InterfaceAppl
{
    public class Executer
    {
        static void Main(string[] args)
        {
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }

    class PowerSupply
    {
        public int getPower() 
        {
            return 100;
        }
    }
    
    class DeskFan
    {
        private PowerSupply _powersupply;
        public DeskFan(PowerSupply 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 "Warining!";
            }
        }
    }
}


在这里插入图片描述

松耦合程序实体 – 引入接口

抽象出 IPowerSupply接口,将PowerSupply类实现IPowerSupply,紧耦合实体中的代码中的PowerSupply改为IPowerSupply。不同的代码如下图所示,完整代码见图下的代码实体。
注意看:PowerSupply类的引用次数从3次,变成了一次,原本对类的这3处引用,被接口替代— 下图代码中显示几次引用,需要在 VS 的编辑区才可以看到,点开引用,可以看到具体在哪一行的什么代码进行了引用)
在这里插入图片描述

using System;

namespace InterfaceAppl
{
    public class Executer
    {
        static void Main(string[] args)
        {
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }

    interface IPowerSupply
    {
        int getPower();
    }

    class PowerSupply:IPowerSupply
    {
        public int getPower() 
        {
            return 100;
        }
    }
    
    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 "Warining!";
            }
        }
    }
}


单元测试

测试不应该在Main函数中完成,应该在单元测试中完成。
新建测试单元之后,在测试单元中对待测试程序实体的逻辑进行测试,以下的实例是为了进行 电源的电压的值对风扇的运行状态进行测试。需要有一个风扇测试类,其中可以包含多个测试实例,即对if else 的多个状态进行测试。
测试流程:新建单元测试 – 新建一个实现IPowerSupply接口的类,类似PowerSupply的类中的实现,就是电源的值的设定不同 — 测试函数的函数体书写([Fact]下面的函数),即测试用例的书写 — 运行测试。

新建单元测试步骤;
在解决方案上右击,然后选择添加 – 新建项目
在这里插入图片描述
选择测试选项,然后选择 xUnit测试项目:
在这里插入图片描述
然后测试项目的命名,待测试项目名 + “.Tests”
在这里插入图片描述
生成的界面如下:
在这里插入图片描述

其中最左侧展示测试的结果,打开方式:测试 – 测试资源管理器(然后通过拖拽,可以将窗口固定在最左侧的位置)
在这里插入图片描述
在这里插入图片描述

然后将UnitTest.cs 的名称修改为 DeskFanTests.cs。选择是
在这里插入图片描述
代码编辑区更新为:

namespace Caoniao.Tests
{
    public class DeskFanTest
    {
        [Fact]
        public void Test1()
        {

        }
    }
}

针对项目进行相关的测试引用,右击依赖项,然后添加项目引用。
在这里插入图片描述
引用需要测试的项目。
在这里插入图片描述

待测试的代码:

将接口和类设置为 public 的访问属性。

using System;

namespace InterfaceAppl
{
    public class Executer
    {
        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 110;
        }
    }

    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 "Warining!";
            }
        }
    }
}

第一个测试案例的书写:

using InterfaceAppl;

namespace Caoniao.Tests
{
    public class DeskFanTest
    {
        [Fact]
        public void PowerLowerThanZero_OK()
        {
            var fan = new DeskFan(new PowerSupplyLowerThanZero());
            var expected = "Won't Work";
            var actual = fan.Work();
            Assert.Equal(expected, actual);
        }
    }

    class PowerSupplyLowerThanZero : IPowerSupply
    {
        public int getPower()
        {
            return 0;
        }
    }
}

在这里插入图片描述
点击测试,第一个测试案例展示,全绿则通过;

在这里插入图片描述

第二个测试案例,

将 原代码,return “Warining!”; 修改为 return “Explosion!”;
添加测试用例:
增加的代码:
在这里插入图片描述
包含两个测试用例的完整代码:

using InterfaceAppl;

namespace Caoniao.Tests
{
    public class DeskFanTest
    {
        [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_OK()
        {
            var fan = new DeskFan(new PowerSuppluHigherThan200_Warning());
            var expected = "Warining";
            var actual = fan.Work();
            Assert.Equal(expected, actual);

        }
    }

    class PowerSupplyLowerThanZero : IPowerSupply
    {
        public int getPower()
        {
            return 0;
        }
    }
    class PowerSuppluHigherThan200_Warning : IPowerSupply
    {
        public int getPower()
        {
            return 220;
        }
    }
}

第二个测试案例失败:
在这里插入图片描述
这个时候,如何查看被测试代码的问题呢?
在测试用例的代码上打断点,然后右击测试用例进行调试。按住F11,查看代码的实际运行情况,代码会跳到待测试程序的实体中。
在这里插入图片描述

测试用例升级版

上述在测试的时候,每次测试一个功能,都需要写一个类,功能繁杂的时候,会需要书写大量为了测试的类,工作量大切代码丑。 — 利用包Moq进行改进。

NuGet 包管理器,添加Moq包
在这里插入图片描述

利用Moq写的测试用例代码:

var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.getPower()).Returns(()=>0);
var fan = new DeskFan(mock.Object);

完整的两个测试用例的修改后的代码,引用命名空间 Moq.

using InterfaceAppl;
using Moq;

namespace Caoniao.Tests
{
    public class DeskFanTest
    {
        [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_OK()
        {
            var mock = new Mock<IPowerSupply>();
            mock.Setup(ps => ps.getPower()).Returns(() => 220);
            var fan = new DeskFan(mock.Object);
            var expected = "Warnning!";
            var actual = fan.Work();
            Assert.Equal(expected, actual);

        } 
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/777522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

latex子图无法重新编号问题

问题 想让后四个子图&#xff0c;从a开始编号 解决方法 如上原因是由于全局自动编号机制&#xff0c;在 \begin{figure*}[h] \centering下面添加&#xff1a; \setcounter{subfigure}{0}

SpringBoot+ShardingSphere+Mybatis实现Mysql8读写分离

场景 CentOS7安装Mysql8并进行主从复制配置&#xff1a; CentOS7安装Mysql8并进行主从复制配置_霸道流氓气质的博客-CSDN博客 在上面搭建起来Mysql之间的主从复制的基础上&#xff0c;在SpringBoot项目中实现Mysql数据的 读写分离&#xff0c;即写入操作一个库&#xff0c;…

为什么重写 equals 方法要重写 hashCode 方法

前言 看了挺多关于这个问题相关的文章&#xff0c;发现回答缺少因果逻辑。直到看了一篇文章提到 《Effective Java》中有关于这个问题的答案。于是找了电子书看了一下&#xff0c;查看相关源码理清了因果逻辑&#xff0c;得出怎样回答这个问题比较好。 1 hashCode 的通用约定…

k8s 如何升级应用

如何升级应用 在之前的分享中&#xff0c;我们知道一个程序如何放到容器中&#xff0c;一个镜像如何生成 pod&#xff0c; pod 的创建&#xff0c;运行&#xff0c;管理&#xff0c;删除过程&#xff0c;挂载等等 那么我们有没有想过&#xff0c;在真正的生产环境中&#xff…

golang 验证器库go-playground/validator实践

当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时&#xff0c;此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。 reflect.Value 类型有很多方法&#xff08;Package reflect - The …

【字符流】编码解码问题

字符流中编码解码问题 字符流抽象基类&#xff1a; Reader&#xff1a;字符输入流的抽象类Writer&#xff1a;字符输出流的抽象类 字符流中和编码和解码问题相关的两个类&#xff1a; InputStreamReader&#xff1a;是从字节流到字符流的桥梁&#xff0c;它读取字节并使用指…

Jupyter Notebook 后台启动

进入用户目录 cd 指定目录 激活环境 conda activate 环境名 Note&#xff1a; 注意&#xff1a;无法导入依赖包&#xff0c;查看是否激活对应环境。 后台启动 jupyter notebook&#xff0c;或者服务器上启动 可通过浏览器访问 nohup jupyter notebook --allow-root& …

C语言——通讯录的实现

前面的文章介绍了C语言中的 指针、自定义类型等模块&#xff0c;这篇文章将通过编写实现通讯录的代码对这些模块进行应用和进一步加深理解&#xff1a; 目录 1. 通讯录主要功能设计&#xff1a; 2. 通讯录的实现——主页面&#xff1a; 3. 通讯录的实现——保存个人信息&am…

CS 144 Lab Three-- the TCP sender

CS 144 Lab Three -- the TCP sender TCPSender 功能如何检测丢包TCPSender 要求TCPSender 状态转换图TCPSender 实现测试 对应课程视频: 【计算机网络】 斯坦福大学CS144课程 Lab Three 对应的PDF: Lab Checkpoint 3: the TCP sender TCPSender 功能 TCP Sender 负责将数据以…

27 Deep Belief Network

文章目录 27 Deep Belief Network——深度信念网络27.1 DBN是什么&#xff1f;27.2 为什么要使用DBN27.2.1 DBN的思想是怎么来的&#xff1f;27.2.2 RBM的叠加可以提高ELBO 27.3 训练方式 27 Deep Belief Network——深度信念网络 27.1 DBN是什么&#xff1f; DBN(Deep Belie…

【机器学习】分类算法 - KNN算法(K-近邻算法)KNeighborsClassifier

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;零基础快速入门人工智能《机器学习入门到精通》 K-近邻算法 1、什么是K-近邻算法&#xff1f;2、K-近邻算法API3、…

FastDFS与Springboot集成

&#x1f680; FastDFS与Springboot集成 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风…

139、仿真-基于51单片机一氧化碳(CO)气体检测仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、设计功能 二、Proteus仿真图​编辑 三、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&#xff1…

【K8S系列】深入解析k8s网络插件—Calico

序言 做一件事并不难&#xff0c;难的是在于坚持。坚持一下也不难&#xff0c;难的是坚持到底。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记论点蓝色&#xff1a;用来标记论点 Kubernetes (k8s) 是一个容器编…

视频理解多模态大模型(大模型基础、微调、视频理解基础)

转眼就要博0了&#xff0c;导师开始让我看视频理解多模态方向的内容&#xff0c;重新一遍打基础吧&#xff0c;从Python&#xff0c;到NLP&#xff0c;再到视频理解&#xff0c;最后加上凸优化&#xff0c;一步一步来&#xff0c;疯学一个暑假。写这个博客作为我的笔记以及好文…

代码随想录算法训练营第55天|392 115

392 双指针法很简单 class Solution { public:bool isSubsequence(string s, string t) {int i0;for (int j0; j<t.size() && i<s.size(); j) {if (t[j]s[i]) {i;}}return is.size();} }; 用动态规划来写的话 逻辑其实跟1143 1035是一样的 最后返回看dp[s.size…

Vue element el-input输入框 实现 ’空格+enter’组合键:换行,enter:发送,使用keydown和keyup键盘事件来实现

需求 输入框 &#xff0c;输入内容后 &#xff0c;按enter空格键 换行&#xff0c;按enter键 发送调取接口 思路 jquery的也分为三个过程&#xff0c;在事件名称上有所不同 1、某个键盘的键被松开&#xff1a;keyup 2、某个键被按下&#xff1a;keydown 3、某个键盘的键被按…

基于查找表(lookup table,LUT)方法反演植被参数

LUT指显示查找表&#xff08;Look-Up-Table)&#xff0c;本质上就是一个RAM。它把数据事先写入RAM后&#xff0c;每当输入一个信号就等于输入一个地址进行查表&#xff0c;找出地址对应的内容&#xff0c;然后输出。 LUT的应用范围比较广泛&#xff0c;例如&#xff1a;LUT(Lo…

机器学习:Self-supervised Learning for Speech and image

review : self-supervised learning for text Self-supervised learning for speech 使用Speech版本的bert能比较好的作用于语音任务上&#xff0c;如果没有self-supervised的话&#xff0c;别的模型可能需要上万小时的数据。 Superb ytb课程&#xff1a;MpsVE60iRLM工具&…

vulnhub打靶--lampiao

目录 vulnhub--lampiao1.扫描主机端口&#xff0c;发现1898端口部署web2.打开robots.txt发现CHANGELOG.txt文件3.发现drupal更新日志&#xff0c;drupal这个版本有公开exp。利用msf打下4.执行uname -a 或者上传漏洞suggest脚本&#xff0c;可以发现有脏牛提权5.上传脚本到目标&…