C#:深入理解接口及低耦合等周边知识
创始人
2024-11-05 22:41:36
0

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

文章目录

  • 接口==契约
  • 声明接口
  • 接口是引用类型
    • 实践价值
  • 接口与as运算符
  • 显示接口成员实现
  • 紧耦合及解决方法
    • 解决方法:接口隔离


接口==契约

定义两个不同类型的集合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();         }     } 

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

相关内容

热门资讯

重磅来袭!喜扣跑胡子有挂吗(辅... 重磅来袭!喜扣跑胡子有挂吗(辅助挂)外挂透视辅助器(2022已更新)(今日头条)所有人都在同一条线上...
记者发布!雀友会是否有外 挂,... 记者发布!雀友会是否有外 挂,雀神麻将挂先试用后付款,可靠教程(有挂介绍);1、完成雀友会是否有外 ...
专业讨论!!胡乐麻将有挂的表现... 专业讨论!!胡乐麻将有挂的表现,广东老友潮汕麻将的确是有挂的,攻略方法(有挂方法);1、实时胡乐麻将...
今日公布!途游跑得快辅助器(透... 今日公布!途游跑得快辅助器(透视)透视辅助神器(2025已更新)(今日头条)运途游跑得快辅助器辅助工...
详细说明!边锋老友棋牌麻将有挂... 详细说明!边锋老友棋牌麻将有挂没,广东雀神麻雀胜率,详细教程(有挂揭秘)在进入边锋老友棋牌麻将有挂没...
黑科技辅助挂!悠闲麻将川南四川... 黑科技辅助挂!悠闲麻将川南四川人民棋牌有挂吗,边锋斗地主推荐,广东雀神麻雀怎么赢1、下载好悠闲麻将川...
避坑细节!呼兰麻将有挂么,闲逸... 避坑细节!呼兰麻将有挂么,闲逸斗亲友圈果真真的有挂,2025版教程(有挂细节);1、呼兰麻将有挂么系...
玩家必看教程!!家乡大贰有没有... 玩家必看教程!!家乡大贰有没有辅助,微信小程序雀神辅助器苹果版,科技教程(有挂辅助);1、实时家乡大...
黑科技辅助挂!腾讯广东麻将有窍... 黑科技辅助挂!腾讯广东麻将有窍门吗,掌酷十三张外 挂,微信雀神小程序有挂吗1、点击下载安装,腾讯广东...
科技通报!哈狗游戏真的有挂吗(... 科技通报!哈狗游戏真的有挂吗(辅助挂)外挂透视辅助神器(2023已更新)(今日头条);1、在哈狗游戏...