接口:接口是指定了一组函数成员,但却没有实现它们的一种引用类型。
如何定义和使用接口:
- interface关键字修饰 接口类型名以大写I开头(如:IList)
- 接口侧重"我能干什么...",微软给我们预定义的接口就多以xxxable结尾。
namespace InterfaceDemo
{
class Program
{
static void Main(string[] args)
{
IPrintable blackPrinter = new BlackPrinter();//接口和抽象类一样也不能new哦
blackPrinter.Print();
IPrintable multiColorPrinter = new MultiColourPrinter();
multiColorPrinter.Print();
Console.ReadLine();
}
}
interface IPrintable
{
void Print();//不能用public修饰、也不能用abstract修饰
}
class BlackPrinter : IPrintable
{
//不要用override修饰
public void Print()
{
Console.WriteLine("打印出黑白的文档");
}
}
class MultiColourPrinter : IPrintable
{
public void Print()
{
Console.WriteLine("打印出彩色的文档");
}
}
}
/**输出:
打印出黑白的文档
打印出彩色的文档
**/
以上我们演示了如何定义接口;如何定义类类型来实现接口;并且演示了如何使用接口。
使用接口的时候需要注意:
- 接口中的函数成员默认是公开的;且不能被public修饰
- 接口中的函数成员也不可以被abstract修饰;因为接口中成员是"完全抽象的",它只是定义了某种能力或行为;而具体的能力或行为就交给子类去实现;
- 若普通类型继承了接口,那么接口中的所有成员,都要被实现;但是在子类中这些被实现的函数成员不需要加override关键修饰;
- 若抽象类继承了接口,则函数成员可以不被实现;如果在抽象类类中不实现该函数成员,则函数成员必须被标记为 public abstract类型--public是因为该函数成员从接口中继承过来,在子类中无法修改访问修饰符;abstract是因为,只有abstract才能使函数成员有"延迟到子类实现"的功能。
根据接口的以上特点,我们演示一下以抽象类继承接口:
interface IPrintable
{
void Print();//不能用public修饰、也不能用abstract修饰
}
abstract class AbstractPrinter : IPrintable
{
public abstract void Print();
}
class MordenPrinter : AbstractPrinter
{
public override void Print()
{
Console.WriteLine("激光打印,全业务支持");
}
}
下面将展示如何用接口解决实际问题:
问题:借助Array类型的Sort()方法,我们可以对数组中方便地进行排序(如下代码所示:)
class Program
{
static void Main(string[] args)
{
int[] intArr = new int[] { 2, 5, 1, 3, 4, 9 };
Array.Sort(intArr);
foreach (var item in intArr)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
/*输出:
1
2
3
4
5
9*/
但是,如果数组中的元素是我们自定义的类型时,Sort()方法就不那么好使了:
class Program
{
static void Main(string[] args)
{
Student tangsan = new Student() { StuName = "唐三", StuNumber = 003 };
Student xiaowu = new Student() { StuName = "小舞", StuNumber = 004 };
Student pangzi = new Student() { StuName = "胖子", StuNumber = 002 };
Student daimubai = new Student() { StuName = "戴沐白", StuNumber = 001 };
Student[] students = new Student[] { tangsan, xiaowu, pangzi, daimubai };
Array.Sort(students);
foreach (var item in students)
{
Console.WriteLine(item.StuNumber+"--"+item.StuName);
}
Console.ReadLine();
}
}
class Student
{
public int StuNumber { get; set; }
public string StuName { get; set; }
}
/*运行报错--未经处理的异常:System.InvalidOperationException:“未能比较数组中的两个元素。”*/
打开错误详细信息得知,原来是因为:我们自定义的类型没有实现ICompare接口,所以数组中元素之间不具有可比性。
我们要实现这个接口,首先得了解这个接口吧?
查找Icompare接口的定义可知:
Icopmare接口中只定义了一个函数成员;该函数接受一个object类型的引用;若当前实例和obj比较结果小于零 则当前实例排在obj之前;大于零 则当前实例排在obj之后...
public interface IComparable
{
int CompareTo(object obj);
}
知道了接口的定义,我们就可以动手了:继承ICompare接口,实现CompareTo方法:
class Student:IComparable
{
public int StuNumber { get; set; }
public string StuName { get; set; }
public int CompareTo(object obj)
{
Student student = obj as Student;
if (this.StuNumber>student.StuNumber)
{
return 1;
}
if (this.StuNumber<student.StuNumber)
{
return -1;
}
return 0;
}
}
/*运行结果:
1--戴沐白
2--胖子
3--唐三
4--小舞
*/
通过将我们自定义的类型实现ICompare接口,并在CompareTo方法中自定义了比较规则,便可以是我们自定义的类型可以排序。
//当然你也可以通过稍微改变规则,得到降序结果
class Student : IComparable
{
public int StuNumber { get; set; }
public string StuName { get; set; }
public int CompareTo(object obj)
{
Student student = obj as Student;
if (this.StuNumber > student.StuNumber)
{
//一改常态,当前实例大于obj时,返回一个大于0的值,这样可以让当前实例排在前面~~~
return -1;
}
if (this.StuNumber < student.StuNumber)
{
return 1;
}
return 0;
}
}
/*输出结果:
4--小舞
3--唐三
2--胖子
1--戴沐白
*/
倒过来想一下,为什么一开始的时候int[] 数组就可以支持Sort()方法?通过查看int类型的定义,就明白了:
原来C#给我们提供的Int32结构体类型,已然实现了ICompare接口,并且重写了CompareTo方法。
在此之前我们有一个默认共识:每种类型(除object外)都只有一个基类--即类的继承具有单根性;但是对于接口而言,一个类却可以实现多个接口。
比如:有一天你在做一道计算题,这道计算题需要计算器;但是你手边没有计算器,同桌莉哥借给你一个MAC笔记本电脑,并告诉你:用这个,它也可以当计算器还可以上网看我直播呢!!!
我们将上面的场景 转化成下面的代码:
namespace InterfaceDemo
{
class Program
{
static void Main(string[] args)
{
//当你有计算器时
ICaculatable caculator = new CaculatorMachine();
caculator.Caculator();
//当你没有计算器,同桌借给你一个苹果笔记本
IComputer macBook = new MacBook();
macBook.Caculator();
Console.ReadLine();
}
}
interface ICaculatable
{
void Caculator();//计算题
}
class CaculatorMachine : ICaculatable
{
public void Caculator()
{
Console.WriteLine("做计算题...");
}
}
interface IComputer
{
void Caculator();//计算题
void SeeLive();//看直播
void PalyGame();//玩游戏
}
class MacBook : IComputer
{
public void Caculator()
{
Console.WriteLine("用苹果笔记本,做计算题...");
}
public void PalyGame()
{
Console.WriteLine("玩CSGO.....");
}
public void SeeLive()
{
Console.WriteLine("看莉哥直播.....");
}
}
}
/*输出结果:
做计算题...
用苹果笔记本,做计算题...
*/
看到用上了苹果笔记本,你会不会大呼奢侈~~~;回到正题,我们不是要讲一个类继承多个接口嘛?
通过观察上面的代码,我们发现;在IComputer、ICaculator中都声明了Caculator();假设我们现在买电脑都是为了追剧、看直播、玩吃鸡。。。那么对于IComputer接口来说,Caculator()就显得多余;我们说这样的现象:其实是接口定义的过"胖"了;
- 将接口IComputer中的Caculator()成员移除、留下"更有意义"的成员(如:玩游戏、看直播等等...)
- 让我们MacBook类既实现ICaculator接口、也实现IComputer 接口
- 这样 MacBook 既可以计算又不耽误玩游戏、看直播
namespace InterfaceDemo
{
class Program
{
static void Main(string[] args)
{
//用同桌借给你的苹果笔记本 做计算题
ICaculatable macCaculator = new MacBook();
macCaculator.Caculator();
//用同桌借给你的苹果笔记本 玩游戏 看直播
IComputer macBook = new MacBook();
macBook.PalyGame();
macBook.SeeLive();
Console.ReadLine();
}
}
interface ICaculatable
{
void Caculator();//计算题
}
class CaculatorMachine : ICaculatable
{
public void Caculator()
{
Console.WriteLine("做计算题...");
}
}
interface IComputer
{
void SeeLive();//看直播
void PalyGame();//玩游戏
}
class MacBook : IComputer, ICaculatable
{
public void Caculator()
{
Console.WriteLine("用苹果笔记本,做计算题...");
}
public void PalyGame()
{
Console.WriteLine("玩CSGO.....");
}
public void SeeLive()
{
Console.WriteLine("看莉哥直播.....");
}
}
}
/*输出结果:
用苹果笔记本,做计算题...
玩CSGO.....
看莉哥直播.....
*/
通过以上例子,我们得知:
- 类可以实现多个接口;
- 通过将接口的功能细化,避免设计出胖接口,可以使我们功能设计更合理--这其实就是设计模式六大原则(SOLID)中的接口隔离原则(ISP)。
以上便是对接口类型的总结,记录下来以便以后查阅。