接口

为什么要用接口?好处在哪里?

如果你的工作是一个修水管的,一天客户找上你让你帮装水管,但是有个要求,就是客户喜欢管子是三角形的。

你立马买了三角形的水管回来,在墙上弄个三角形的口子,客户付了钱,你很开心今天有了收入,如下图,很好:

但是好景不长,客户过了一个星期又来找,因为他觉得三角形不好看,要让你换成正方形的水管,你不得不换,因为顾客就是上帝。好吧,继续在墙上弄个正方形的口子,然后又换成正方形的管子来接上。好了,如下图:(但是可能觉得为什么一开始不说要正方形的?因为需求总在变化。。。)

你累得满头大汗,但是还是完成了。可惜不久,客户又来找你,因为他想换成椭圆形的管子了。虽然你很无奈,但是你还是不得不花了几个小时完成。如下图:

安装完成,这时你可能在考虑,为什么换不同形状的水管,我都要大动干戈一番呢?于是你想到一个好方法,那就是墙上设计一个固定的水管并且是圆形的,当客户喜欢什么形状的水管,那么我只需要把客户喜欢的水管的一头做成圆形的,这样,以后都不需要去动墙上的水管了。这是一个好办法。就先在墙上弄个圆形的口,这个口就叫做接口。如下图:

如你所见,墙上有个圆形的口,但是按照原本的:

三角形水管两端是三角形
正方形水管两端是正方形
椭圆形水管两端是椭圆形

那是肯定接不上的,因为三角形、正方形、椭圆形的口怎么和墙壁上圆形的口对接呢?

所以先要实现接口,把:

三角形水管一端做成圆形
正方形水管一端做成圆形
椭圆形水管一端做成圆形

如图所以,圆形接口做出来了,具体实现是客户去安装,接口本身并不会安装其他形状的水管,换句话说就是接口没有具体实现,只是告诉你,你的水管要接入,必须有一端是圆形的(接口的约束),因为我只留这个圆形的接口,你要装别的形状的管子,先把一个弄成圆形的就好了(子类去实现接口的方法),不管什么形状,都要一个必须做成圆形才能对接得上,它必须要你按照我的规范来做。这就是为什么新手会觉得接口什么都不做,只定义接口,没有任何实现,那不是多此一举吗?因为它的实现是子类去完成。这样只要客户喜欢什么形状的水管,只要实现了我的接口(圆形),都能对接得上,而且改变起来也很方便,只要把水管扭上去就行了,不用在去给墙壁挖洞了

下面我们在代码里体现接口的作用,以下例子的场景不讨论是否合理,由于以下代码是在txt编写,不保证运行无误。

需求:公司有两个人分别写了2个动物类,让你写一个类来输出它们各自喜欢的食物。

//A写的Dog类,里面有个likeFood方法,如下:
class Dog
{

    public void likeFood()
    {
        Console.WriteLine("我是小狗,我喜欢吃肉");
    }

}
//B写的Cat类,里面有个likeFood方法,如下:
class Cat
{

    public void likeFood()
    {
        Console.WriteLine("我是小猫,我喜欢吃鱼");
    }

}

你写的Zoo类如下:

//动物园类
class Zoo {

    public void show(Dog dog){
        dog.likeFood();
    }

    public void show(Cat cat){
        cat.likeFood();
    }

}

在输出的时候使用如下:

public static void Main(string[] args)
{
    Zoo zoo = new Zoo();
    zoo.show(new Dog()); //"我是小狗,我喜欢吃肉"
    zoo.show(new Cat()); //"我是小猫,我喜欢吃鱼"

}

这一切工作良好,但好景不长,公司又需要给动物园增加一个猴子,让C去写这个Monkey类,并能输出它喜欢的食物。

class Dog
{

    public void likeFood()
    {
        Console.WriteLine("我是小狗,我喜欢吃肉");
    }

}
//B写的Cat类,里面有个likeFood方法,如下:
class Cat
{

    public void likeFood()
    {
        Console.WriteLine("我是小猫,我喜欢吃鱼");
    }

}
//C写的Monkey类,里面有个likeFood方法,如下:
class Monkey
{

    public void likeFood()
    {
        Console.WriteLine("我是猴子,我喜欢吃桃子");
    }

}

于是你的Zoo类就变成了这样,仅仅多了一个重载方法:

class Zoo
{

    public void show(Dog dog)
    {
        dog.likeFood();
    }

    public void show(Cat cat)
    {
        cat.likeFood();
    }

    public void show(Monkey money)
    {
        money.likeFood();
    }
}
public static void Main(string[] args)
{

    Zoo zoo = new Zoo();
    zoo.show(new Dog()); //"我是小狗,我喜欢吃肉"
    zoo.show(new Cat()); //"我是小猫,我喜欢吃鱼"
    zoo.show(new Monkey()); //"我是猴子,我喜欢吃桃子"

}

输出结果也良好,你不禁暗暗得意,一个变化而已,我只需要再Zoo类里增加一个重载方法就好了。
但你仔细一想:“如果后面还有更多动物要输出它们喜欢的食物,我的Zoo类都要修改,这对我来说不是一件好事。”
然后你再仔细观察Zoo类,发现不变的是show方法,变化的是show方法是参数。因为每个动物都不一样,所以参数也就不一样。所以原来就需要重载多个方法。
如果有一个类,能接收所有动物,那不就解决了?没错,于是你想到了定义一个父类叫Animal,里面有个likeFood方法,让所有动物类去继承Animal。
最后你的Zoo类和Animal类代码如下:

class Zoo
{

    public void show(Animal animal)
    {
        animal.likeFood();
    }

}

class Animal
{

    public void likeFood()
    {
        Console.WriteLine("我是Animal类");
    }

}

你告诉原来的A和B两人,让它们写的动物类都去继承Animal,并且里面有个输出动物喜欢食物的方法。
A、B、C写的类修改后如下:

class Dog : Animal
{

    public void likeFood()
    {
        Console.WriteLine("我是小狗,我喜欢吃肉");
    }

}

class Cat : Animal
{

    public void likeFood()
    {
        Console.WriteLine("我是小猫,我喜欢吃鱼");
    }

}

class Monkey : Animal
{

    public void likeFood()
    {
        Console.WriteLine("我是猴子,我喜欢吃桃子");
    }

}

Zoo也需要修改,最后代码如下:

class Zoo
{

    public void show(Animal animal)
    {
        animal.likeFood();
    }

}

public static void Main(string[] args)
{

    Zoo zoo = new Zoo();
    zoo.show(new Dog()); //"我是小狗,我喜欢吃肉"
    zoo.show(new Cat()); //"我是小狗,我喜欢吃肉"
    zoo.show(new Monkey()); //"我是猴子,我喜欢吃桃子"

}

运行也一切良好,不管你以后还有什么类,只要让需要添加的动物类,继承Animal,并有个likeFood方法,那么你无需修改Zoo,只需要再main方法里传入动物类的实例就能正常工作。
你大赞你聪明绝顶,这样一来,你的Zoo根本不需要改变了。
有一天,公司新来一个人,暂时叫D吧,公司让D写个兔子类,你告诉D,你写的Rabbit类必须继承Animal,并且有一个它喜欢的食物的方法,。
D写的兔子类如下:

class Rabbit : Animal
{

    public void favoriteFood()
    {
        Console.WriteLine("我是兔子,我喜欢吃萝卜");
    }

}
public static void Main(string[] args)
{

    Zoo zoo = new Zoo();
    zoo.show(new Dog()); //"我是小狗,我喜欢吃肉"
    zoo.show(new Cat()); //"我是小猫,我喜欢吃鱼"
    zoo.show(new Monkey()); //"我是猴子,我喜欢吃桃子"
    zoo.show(new Rabbit()); //"我是Animal类"

}

Raabit并没有输出预想的结果,你不得不花了点时间去排查原因,最后你发现这不是什么大问题,因为新来的D虽然写了Rabbit类,但是他并不知道你们之前约定的动物喜欢的食物命名为:likeFood()
D写的Rabbit类,里面的方法叫:favoriteFood()
虽然最后D修改他的Rabbit类的方法为likeFood(),但你还是对这个问题做了一番思考,为什么会导致这个问题?
那是因为没有一种约束,使得子类继承父类的时候必须实现父类的方法。有没有一个类,能让它的子类必须实现它定义的方法?有,那就是接口。
于是你修改Animal为接口,代码如下:

interface Animal {

    public void likeFood();

}

由于Animal接口有个likeFood()方法,那么Rabbit子类去实现Animal接口必须实现likeFood(),否则程序不能通过。

代码正常工作,因为Animal是接口,里面有个likeFood()方法,以后再添加各种动物进来,只需要实现Animal接口,并且也不会出现有的人会因为子类的方法命名问题而导致出错了。
这时你再想,虽然用继承一个普通父类也可以满足要求,但是一个普通父类根本没有约束力
而用了接口就不一样了,子类必须实现父类的所有方法,因为Zoo类里调用的是likeFood(),由于子类必须实现父类,那么所有子类都会有likeFood(),你根本不用担心子类有没有这个方法。
所以接口能在多人协作下,定义一系列方法,让子类必须存在接口定义的类,防止在另外的类里调用一个人写的接口的子类时,找不到方法的问题。

开放闭关原则:

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
 
这两句话是概念性的东西。通俗的意思就是:
对于上面的图片来说,墙上的接口做好了,不能去改变了,但是可以扩展,你要什么形状的水管就去实现它的接口。

C# 接口《通俗解释》的更多相关文章

  1. C#类型的转换:Converter<TInput, TOutput> 委托的使用

    Converter<TInput, TOutput> 委托 表示将对象从一种类型转换为另一种类型的方法. 此委托由 Array 类的 ConvertAll<TInput, TOutp ...

  2. Array.ConvertAll<TInput, TOutput> 数组相互转化方法

    有个需求,把char数组转换为int数组,然后噼里啪啦就弄了这样一堆代码: public static int[] CharArrToIntArr(char[] charArr) { int[] in ...

  3. C#高级编程笔记 Day 5, 2016年9月 13日 (泛型)

    [重点]泛型:有了泛型,就可以创建独立于被包含类型的类和方法了.我们不必给不同的类型编写功能相同的许多方法和类,只创建一个方法或类即可,以下是泛型的特点:性能.类型安全性.二进制代码重用.代码的扩展. ...

  4. 最近面试的题目(WEB、Service、SQL、JavaScript)

    整理一下最近面试被问到的主要题目.由于本人主要是做WEB及WEB SERVICE这块,使用的语言主要是C#,数据库主要用到的也是MSSQL.所以就分成这些块来整理(有些是在面试之后才意识到回答不对), ...

  5. C#集合--数组

    Array类是所有一维和多维数组的隐式基类,同时也是实现标准集合接口的最基本的类型.Array类实现了类型统一,因此它为所有数组提供了一组通用的方法,不论这些数组元素的类型,这些通用的方法均适用. 正 ...

  6. 使用 IL 实现类型转换

    在之前的文章中,我大致介绍过一些类型间的隐式和显式类型转换规则.但当时并未很仔细的研究过<CSharp Language Specification>,因此实现并不完整.而且只部分解决了类 ...

  7. C#泛型(C#_编程指南)CSDN学习整理笔记

    1.1. 泛型概述 2.0版C#语言和公共语言运行时(CLR)中增加了泛型.泛型将类型参数的概念引入.NETFramework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定 ...

  8. 关于C#泛型列表List<T>的基本用法总结

    //示例代码如下:using System;using System.Collections.Generic;using System.Collections.ObjectModel;namespac ...

  9. Linq/List/Array/IEnumerable等集合操作

    来源:http://www.cnblogs.com/liushanshan/archive/2011/01/05/1926263.html 目录 1    LINQ查询结果集    1 2    Sy ...

  10. C#集合基础与运用

    C#集合基础与运用   C#集合基础与运用 1. 集合接口与集合类型............................................... 1 (1) 集合的命名空间..... ...

随机推荐

  1. Nginx虚拟主机

    [root@Nginx-server ~]# tar zxvf nginx-1.11.2.tar.gz [root@Nginx-server ~]# useradd -M -s /sbin/nolog ...

  2. DOS系统常用命令

    前言: DOS命令是DOS操作系统使用的命令.DOS操作系统是一种磁盘操作系统,从Windows95.98到今天的Windows10都内置有DOS操作系统.可以通过"win+R", ...

  3. springmvc配置详解 教程

    https://www.cnblogs.com/sunniest/p/4555801.html

  4. adb常用操作命令

    1.adb简介:    adb,即 Android Debug Bridge.通过这个工具和android进行交互操作 2.adb命令格式:    adb [-d|-e|-s <serialNu ...

  5. Python logger /logging

    # !/user/bin/python # -*- coding: utf-8 -*- ''' subprocess : 需要在linux平台上测试 shell logging ''' import ...

  6. Dubbo中编码和解码的解析

    (这里做的解析不是很详细,等到走完整个流程再来解析)Dubbo中编解码的工作由Codec2接口的实现来处理,回想一下第一次接触到Codec2相关的内容是在服务端暴露服务的时候,根据具体的协议去暴露服务 ...

  7. bzoj 2005 能量采集 莫比乌斯反演

    我们要求的是∑ni=1∑mj=1(2×gcd(i,j)−1) 化简得2×∑ni=1∑mj=1gcd(i,j)−n×m 所以我们现在只需要求出∑ni=1∑mj=1gcd(i,j)即可 ∑ni=1∑mj= ...

  8. 【源码】otter工程结构

    最近在搞数据同步相关的内容,需要对otter的代码进行扩展,所以需要先熟悉一下otter的源码.首先我们整体来看下otter的工程结构.otter的工程结构比较复杂,需要花费一定的时间来理解各个部分的 ...

  9. BZOJ_3438_小M的作物_最小割

    BZOJ_3438_小M的作物_最小割 Description 小M在MC里开辟了两块巨大的耕地A和B(你可以认为容量是无穷),现在,小P有n中作物的种子,每种作物的种子 有1个(就是可以种一棵作物) ...

  10. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...