前言
网上随处可见 “接口和抽象类的区别”、“抽象方法和虚方法的区别”、“接口与虚方法的区别” 等等类似的文章,我也曾经为之困惑,看了不少这样的文章,但是前面看后面忘,没有作用。
也是巧合,有一天忽然明白了语言设计者的设计思路,对这些问题的理解就很清晰了。
我的看法是,不要说接口、抽象类、抽象方法、虚方法有啥区别,理解了 interface、abstract、virtual 的作用,他们的区别自然就浮出水面了。
interface
这个关键字,放在类的名字前面作修饰符,表示声明一个 “接口(类)”,因为只能修饰 “类”,所以常常在说 “接口类” 的时候 “类” 字就不提了,直接叫 “接口”。如下面的代码所示,声明了一个 “IPhone” 接口:
namespace Interface_test.Interface { interface IPhone { void Call(string PhoneNumber); void SendMessage(string message,string PhoneNumber); } }
这个接口制定了一个规范,与此同时表示一种能力,只要是手机,就必须可以打电话、发短信,而且制定了相应方法的返回值、方法名、参数类型和个数。
这就是接口,因为是制定规范,所以不能参与任何实质性的活动。即不允许写函数的具体方法体、不允许有字段等。就像银监会,制定开银行的规范,但是银监会自己不能开银行。
总之目的就是:1,制定规范 ;2,是类与类之间的桥梁,让类之间产生弱耦合。
相关文章可以参考:C# 接口的正确使用方式
abstract
抽象类的出现,是为了解决代码的冗余,实现代码复用。
接口里面,是不允许包含任何方法体的,也就是说,每一个继承此接口的实现,都要编写相应方法的方法体,然而,很多实现的方法体都是一模一样的,这就导致了大量代码的重复。
尤其是在游戏开发中,有大量的同种类的角色、物资、地形等类,只用接口的话,将产生大量的重复代码,于是抽象类产生了。
抽象类用 “sbstract” 修饰,跟接口一样,不可以被实例化,但是抽象类里不仅可以像接口一样写方法名,还能写字段,还能写属性,还能写方法体。
抽象类里的方法体可以有三种:
1,普通方法,此类方法子类可以直接拿出来用,但是不能重写。所以只要继承了抽象类,就直接干掉了这种共有的代码的冗余。
2,抽象方法,用 “abstract” 关键字修饰的方法,称为 “抽象方法”,不能包含方法体,所以子类必须重写该方法,作用类似于接口。包含抽象方法的类,必须声明为抽象方法。
3,虚方法,用 “virtual” 关键字修饰的方法,称为虚方法。此类方法,子类可以直接拿出来用,而且还能重写,相当于该抽象类给子类提供了一定的发散空间。
既然抽象类这么强大、这么灵活 ,那还要接口类干嘛,所有类一律声明为抽象类不就完事了嘛。这里要注意,抽象类和接口都不能实例化,多个接口可以被同时继承,而抽象类,只能被单继承。
virtual
“virtual” 关键字只能用来修饰方法,虚方法可以出现在任何类里,表示子类可以直接用该方法体,也可以 “override” 该方法体,相当灵活。
但是再灵活,也不能所有方法都整成虚方法,那子类不就想干嘛就干嘛,那老鼠的儿子不仅会打洞,还会变成蝙蝠飞了呢。
综上
接口类,更多的是用来制定规范、表示一定的能力。可以看到 .Net 库中很多接口的接口名,都是以 “able” 结尾的:
抽象类,使用目的是解决代码冗余,虽然比接口强大,但是作为父类,子类只能继承他一个,粒度比接口大一些。
抽象方法,为子类的特异性功能而准备,为了让子类都必须重写该方法,毕竟自己的路自己选。有人会说,子类可以同时继承抽象类和接口,那还要这个抽象方法干嘛?没错,子类确实可以同时继承抽象类和接口,但是我们不能保证,子类不会在继承的时候,不漏掉每一个需要继承的接口。
虚方法,灵活、好用,但是对子类过于 “溺爱”,啥都给子类准备好,还允许子类为所欲为,容易管不住熊孩子。
后记
后来再想想关键词,其实是程序员对现实世界理解和抽象后,将之程序化的辅助手段。