接口 interface 规定了具备某种行为能力的对象,为了系统更容易扩展和维护,常常需要面向接口编程,接口是抽象的一种表达形式。比如作为 Animal 动物这个抽象类型,应该有吃饭和睡觉的行为,同时都作为宠物的属性都能和人一起玩耍,没问题吧?
type Animal interface { eat(food string) sleep(hours uint) playAsPet() }
如果一个对象拥有了接口所定义的行为,那么就说这个对象实现了该接口,也可以安全的转换为对应的接口对象。比如猫和狗都具备了 Animal 的行为,它们就可以被安全的转换为 Animal 这个类型。
// 猫type Cat struct {}func (cat *Cat) eat(food string) {...}func (cat *Cat) sleep(hours uint) {...}func (cat *Cat) playAsPet() {...}// 狗type Dog struct {}func (dog *Dog) eat(food string) {...}func (dog *Dog) sleep(hours uint) {...}func (dog *Dog) playAsPet() {...}
为什么猫和狗不直接使用 Animal 来表达呢? 因为猫和狗虽然具备了一些相同的行为,并同时也具备了很多不同的行为,当你想收拾一只老鼠的时候,肯定是派一只猫去更合适,对吧(有人不这么想)? 当你想留一个看家的时候,肯定是狗要合适些,对吧?
// 猫捉老鼠func (cat *Cat) catchMouse() {...}// 狗来看家func (dog *Dog) defendHouse() {...}
可见接口用来抽象共同的行为,当你寂寞想找个宠物放松一下时,去找 cat 和 dog 都可以满足你,但当你需要捉老鼠或者看家的时候,你就不能随便选一个了。
回到 playAsPet() 上来,很显然和猫玩耍的方式与狗不同,猫可能喜欢陪你在家看书,而狗更合适陪你去户外遛弯,虽然都能陪你打发时间,但是你的体验会不一样,这就是多态! 来看看多态教科书的表达: 多态是同一个行为(playAsPet)具有多个不同表现形式或形态的能力(看书、遛弯);多态就是同一个接口(Animal),使用不同的实例(Cat、Dog)而执行不同操作(catchMouse、defendHouse)。
那么继承又有什么用呢? 拿 Dog 来举例子,狗还有很多种,对吧? 比如警犬,不仅能吃、能睡、陪你玩,还能帮你抓到犯罪嫌疑人,当你的系统要表达一只警犬的时候,你不想把 eat、sleep、playAsPet 重新都写一遍对吧? 如果你想对 playAsPet 方法做一些修改,作为宠物来讲,它也应该同时被修改对吧? 所以最好的方式就是继承 Animal 的行为,定一个能辅助警察工作的类型,而警犬是其中一个特例,比如它可能是德国牧羊犬,有可能是昆明犬,对吧?
// 能帮警察搞事情的动物type PoliceAnimal interface { Animal arrestBadMan()}// 牧羊犬type HuntawayDog struct { Dog}func (huntawayDog *HuntawayDog) arrestBadMan() {...}
你现在想让 HuntawayDog 或者 KunmingDog 陪你散散步,是没有问题,对吧? 所以你现在可以一个能吃、能睡、能陪你玩,还能抓捕坏人的朋友,你可能需要的是 PoliceAnimal,当你真的要执行抓捕任务的时候,不管是 HuntawayDog 还是 KunMingDog,都能完成任务。
func atPeace(animal Animal) { animal.eat("meat") animal.sleep(10) animal.playAsPet()}func doTask(animal PoliceAnimal) { animal.arrestBadMan()}func main() { huntawayDog := &HuntawayDog{} atPeace(huntawayDog) doTask(huntawayDog)}
不同的语言对接口、多态、集成的支持力度不同,但核心思想如此,剩下的只是语法的表达。
源代码参考
https://github.com/developdeveloper/go-demo/tree/master/30-oop-aifb/02-oop
S.O.L.I.D Design - oop 经典设计原则
https://github.com/developdeveloper/go-demo/tree/master/31-solid-design