首页 > 编程语言 >C#:深入理解接口及低耦合等周边知识

C#:深入理解接口及低耦合等周边知识

时间:2024-08-06 14:52:22浏览次数:8  
标签:Console C# void 接口 class int 耦合 public

接口是完全未实现逻辑的类,纯虚类,只有函数成员,且都为public.换句话说:接口是函数成员全都是abstract public类型的抽象类.

文章目录


接口==契约

定义两个不同类型的集合int和ArrayList.

        static void Main(string[] args)
        {
            int[] num1 = new int[] { 1, 2, 3 };
            ArrayList num2 = new ArrayList() { 1, 2, 3 }; //非泛型集合,元素都被视为object
            Console.WriteLine(Sum(num1));  //和
            Console.WriteLine(Avg(num1));  //平均值
            Console.WriteLine(Sum(num2));
            Console.WriteLine(Avg(num2));
        }

然后我们想遍历这个集合计算所有元素的和与平均值.按道理来说,我们得实现两份,一份int,一份ArrayList的.
有的同学可能会想到强转.但问题是,我们传入的类型与int类型结构不同,其字段的名称和类型与int不同,而Sum和Avg对此一无所知.可能在运行时会出现问题.
我们能不能创建一个能够成功传入Sum的类,不管该类是什么类型,Sum都能很好地进行处理呢?接口应运而生.
我们计算Sum和Avg要的功能是什么?迭代.而int数组和ArrayList都有一个支持迭代的接口IEnumerable.在这里插入图片描述

		//不采用泛型编程的话,得写两份.但是接口IEnumerable相当于应该契约,确认供方(Main)提供的是可迭代的
        static int Sum(IEnumerable nums)
        {
            int ret = 0;
            foreach (var num in nums) ret += (int)num;
            return ret;
        }

        static double Avg(IEnumerable nums)
        {
            double ret = 0;
            int cnt = 0;
            foreach (var num in nums)
            {
                ret += (int)num;
                cnt++;
            }
            return ret / cnt;
        }

int[]和ArrayList都实现了IEnumerable这个接口,确保它能够迭代.因为接口是引用类型,我们传参给Sum和Avg都会隐式类型转化为IEnumerable接口.结构一样

声明接口

1.因为接口是由抽象类进化过来的,接口中也不能包括数据成员和字段.类实现接口就必须实现接口说有成员.
2.按照惯例,接口必须是I开头

interface IVehicle
{
    void Run();
}

3.基类只有一个,而接口可以有多个.基类必须在接口前面.且实现接口的成员不需要写override
同时,接口也可以继承接口.多个接口间用逗号分隔.

    interface IVehicle
    {
        void Run();
    }

    interface IWeapon
    {
        void Fire();
    }
    interface ITank : IVehicle, IWeapon
    { }
    class VehicleBase
	{
	    //...
	}
	class Vehicle : VehicleBase, IVehicle  //基类必须在接口前面
	{
	    public void Run()  //实现接口的方法不需要写override
	    {
	        //...
	    }
	}

例如:坦克可以驾驶也可以当武器.

4.具有重复成员的接口只需要实现一份

namespace shh
{
    interface Inf1
    {
        void PrintOut(string s);
    }
    interface Inf2
    {
        void PrintOut(string s);
    }
    class Myclass : Inf1, Inf2
    {
        public void PrintOut(string s)
        {
            Console.WriteLine(s);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Myclass myclass = new Myclass();
            myclass.PrintOut("sss");
        }
    }
}

Inf1和Inf2有相同的返回类型,函数名,参数.我们在继承这两个接口时只需要实现一次.
5.接口的声明可以用修饰符(public,internal),接口成员默认都是public的.

接口是引用类型

我们不能直接访问接口,但是我们可以通过类对象引用强制转化为接口类型,来使用这个接口.

class Program
{
    static void Main(string[] args)
    {
        HuaWei huaWei = new HuaWei();
        IPhone phone = (IPhone)huaWei;
        Func(phone);
    }

    public static void Func(IPhone phone)
    {
        phone.Call();
    }
}
interface IPhone
{
    void Call();
}
class HuaWei : IPhone
{
    public void Call()
    {
        Console.WriteLine("HuaWei...");
    }
    public void Receive()
    {
        Console.WriteLine("Rolling");
    }
}

我们把华为这个具体的类转成IPhone这个接口,然后去调用Func方法.

实践价值

**如果一个函数里面需要有什么功能,就只给它传什么功能.例如我们只想用Call这个功能,传HuaWei整个类既显得臃肿,又会降低代码的可维护性.**这个又从何说起呢?
如果Func类型传的参数是HuaWei类型的,但是这个时候我又想传进去一个实现IPhone接口的vivo又会因为结构不一样传不进去.导致我们经常要修改类.这不就降低代码的维护性了.

接口与as运算符

看起来as运算符虽然和强转所达成的效果差不多.但实际可差远了.

        static void Main(string[] args)
        {
            HuaWei huaWei = new HuaWei();
            IPhone phone = huaWei as IPhone;
            if (phone != null )
            {
                Func(phone);
            }
        }

强制转化失败会抛异常,代码会直接挂掉.as运算符转化失败则是返回null.
异常会严重影响代码的执行速度,我们在使用时能用as就用as

显示接口成员实现

不知道大家看没看过这个杀手不太冷这一部电影.主角既是一个绅士,也是一个杀手.我们以主角为例子展开下面的学习.
我们先来看普通的接口实现.定义对象wk是一个温柔的杀手,能爱人(Love)也能杀人(Kill).

namespace shh
{
    class Program
    {
        static void Main(string[] args)
        {
            var wk = new WarmKiller();
            wk.Love();
            wk.Kill();
        }
    }
    interface IGentle
    {
        void Love();
    }
    interface IKiller
    {
        void Kill();
    }

    class WarmKiller : IGentle, IKiller
    {
        public void Love()
        {
            Console.WriteLine("Love");
        }
        public void Kill()
        {
            Console.WriteLine("Kill you");
        }
    }
}

但是问题来了,杀手能让你这么直接就认出来吗?不得有些隐藏手段什么的.

    class WarmKiller : IGentle, IKiller
    {
        public void Love()
        {
            Console.WriteLine("Love");
        }

        void IKiller.Kill()
        {
            Console.WriteLine("Kill you");
        }
    }

IKiller.Kill()就是显示接口实例化,可以把杀手这个身份隐藏起来.此时用wk这个对象调用不了Kill这个函数.
在这里插入图片描述
我们只能把它转化成Ikiller这个接口才能调用它.
你得是专业的才能看出别人杀手的身份.

 static void Main(string[] args)
 {
     var wk = new WarmKiller();
     //wk.Kill(); 找不到Kill,杀手怎么可能让你直接看出来
     var t = wk as IKiller;  //得转化成特定对象才能看到
     if (t != null)
     {
         t.Kill();
     }
 }

显示接口成员实现之后,这个成员相当于只属于这个接口,类调用不了.

紧耦合及解决方法

接口天生就是用来解决紧耦合这个问题的.
我们先来认识紧耦合的危害.大家不需要完全理解下面的代码,只需要知道紧耦合的危害.

namespace shh
{
    class Program
    {
        static void Main(string[] args)
        {
            Engine engine = new Engine();
            Car car = new Car(engine);
            car.Run(3);
            Console.WriteLine(car.speed);
        }
    }
    //引擎和汽车高度耦合,引擎出现一点问题,就算汽车类本身的实现没问题也会报错
    class Engine
    {
        public int RPM { get; private set; }
        public void Work(int gas)
        {
            RPM = gas * 1000;
        }
    }
    class Car
    {
        private Engine _engine; //高度耦合
        public Car(Engine engine)
        {
            _engine = engine;
        }
        public int speed { get; private set; }
        public void Run(int gas)
        {
            _engine.Work(gas);
            speed = _engine.RPM / 100;
        }
    }
}

在上面的代码中,引擎和汽车高度耦合,引擎出现一点问题,就算汽车类本身的实现没问题也会报错.
Car这个类实现是没问题的,结果却因为_engine坏了导致报错.要是_engine生成的类再多点,会导致错误极难排除.这会大大降低代码的可维护性.

解决方法:接口隔离

在日常生活中,人和手机是解耦的.从vivo换到oppo也能迅速上手,没什么门槛.
声明一个Iphone的接口,具有发消息和拨号两个功能,诺基亚和华为实现这个接口.

    interface IPhone
    {
        void Send();
        void Dial();
    }
    class Nokia : IPhone
    {
        public void Send()
        {
            Console.WriteLine("Nokia sending...");
        }
        public void Dial()
        {
            Console.WriteLine("Nokia Dailing...");
        }
    }
    class Huawei : IPhone
    {
        public void Send()
        {
            Console.WriteLine("Huawei sending...");
        }
        public void Dial()
        {
            Console.WriteLine("Huawei Dailing...");
        }
    }

声明users这个类.类里面声明可以接口的字段,我们不需要传具体的哪一款手机,只需要传实现了拨号和发消息这两个功能的手机就行,没有型号要求.

    class Users
    {
        //类里面声明可以接口的字段,相当于泛化了,不再确切地指定特定内容(如某一款特定的手机)
        private IPhone _phone;
        public Users(IPhone phone)
        {
            _phone = phone;
        }
        public void Work()
        {
            _phone.Send();
            _phone.Dial();
        }
    }

main函数调用:

    class Program
    {
        static void Main(string[] args)
        {
            Users Danel = new Users(new Nokia());
            Danel.Work();
        }
    }

在这里插入图片描述
这就叫低耦合,不管你传进来是什么手机,我都能用,不具体依赖于哪一款手机.

标签:Console,C#,void,接口,class,int,耦合,public
From: https://blog.csdn.net/2301_76886465/article/details/140955895

相关文章

  • C#常用加密解密方法(MD5加密、解密、签名)
    在日常开发过程中,总会遇到需要加密解密的需求,这里我整理了C#常用的加密解密方法分享给大家。先看看加密的基本概念:"加密",是一种限制对网络上传输数据的访问权的技术。原始数据(也称为明文,plaintext)被加密设备(硬件或软件)和密钥加密而产生的经过编码的数据称为密文(ciphertext)......
  • HTML5 WebSocket 详解及使用
    1.WebSocket是什么?WebSocket是HTML5提供的一种在单个TCP连接上进行全双工通讯的协议。(双向通信协议)2.WebSocket的作用?实现客户端与服务器之间的双向通信,允许服务端主动向客户端推送数据。在WebSocketAPI中,浏览器和服务器只需要完成一次握手,两者之间就直......
  • 暑假集训CSP提高模拟14
    暑假集训CSP提高模拟14组题人:@H_Kaguya|@LYinMX\(T1\)P209.BA\(30pts\)部分分\(30pts\):输出\(\left\lceil\dfrac{\sum\limits_{i=1}^{m}a_{i}}{n}\right\rceil\)。数形结合,将\(\{a\}\)抽象成矩形,烙饼抽象成海报覆盖,最终有\(\max(\max\limits_{i=1}^{m}......
  • Mac开发基础08-NSWindow(二)
    NSWindow其他使用和技巧NSWindow是macOS应用程序中用于显示和管理窗口的核心类。可用于创建、编辑和管理应用程序的窗口。1.自定义窗口的内容视图层级替换默认的内容视图NSWindow默认包含一个内容视图,你可以使用自定义内容视图来替换它。Objective-CNSView*customVie......
  • css 实现弹窗缩放出现,从小放大
    在CSS中,实现弹窗从小放大的动画效果,可以使用transform属性和transition属性或者@keyframes动画。以下是几种实现方式:使用transition当弹窗元素的类被添加或移除时,可以使用transition来实现平滑的缩放效果。.modal{opacity:0;transform:scale(0.5);/*初始缩放比例较......
  • 一个定时器的轮询,页面卸载清除轮询的定时器 ,js 接口5s轮询 轮询查询应用安装状态
    在JavaScript中,如果您使用setInterval创建了一个定时器来进行轮询,并希望在页面卸载时清除这个定时器,您可以按照以下步骤实现:示例代码://假设这是查询应用安装状态的函数functioncheckInstallationStatus(){//这里应该是发起网络请求的逻辑//例如使用fetchAPI获取......
  • python安装torch-cluster、torch-scatter、torch-sparse和torch-geometric | torch_ge
    1.检查CUDA版本【方法1】用nvidia-smi已装cuda,用nvidia-smi或nvcc-V查看【方法2】用torch已装torch,用torch代码打印importtorchprint(torch.__version__)#查看pytorch安装的版本号print(torch.cuda.is_available())#查看cuda是否可......
  • 从零开始学嵌入式技术之C语言11:指针
    一:指针的理解(1)变量的访问方式        内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 ,为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元通常占用1个字节。变量在内存中分配空间,不同类型的变量占用不同大小的空间,那如何访问内......
  • Mac开发基础06-NSView(二)
    要理解NSView更深层的知识,涉及到其渲染机制、事件处理流程、与CALayer的关系及性能优化等方面。1.NSView绘制和渲染机制NSView的绘制过程主要依赖于drawRect:(Objective-C)或draw(_:)(Swift)方法。这个方法被调用是由系统驱动的,通常发生在需要重新绘制视图的时候,如窗口首次......
  • uniapp中的websocket的研究,以及相关的封装
    官方文档---官方文档写的跟屎一样https://uniapp.dcloud.net.cn/api/request/websocket.html相关博客https://www.cnblogs.com/sunnyeve/p/16757633.html还是这个博客清晰https://blog.csdn.net/lyandgh/article/details/100642941......