在Jacquie Barker和Grant Palmer合著的《Beginning C# Objects From Concepts to Code》里面阐述了面向对象编程语言的三大要素——用户自定义类型(也就是类)、继承(Inheritance)、多态(Polymorphism)。而其它书籍对面向对象特征的表述也和前面的大同小异,概念其实不难,但是实际应用起来却总有点无从下手的感觉。
类和多态到也还好,但关于“继承”我却在很长一段时间根本没有丝毫感念到底该如何应用到我自己的代码中。很多书上都说“继承”可以减少代码的重复,让代码可以不断地重用,减少开发时间之类的优点。但实际上很多东西是不可以也不可能就一个extends就可以解决的。
举个例子,就例如有两个系统,一个是学生管理的,一个是教师管理的。前一个系统里面有一个类——studentIfo是关于学生信息的,里面可能有姓名、性别、班级之类的一些属性,可能还有一些showInfo之类的显示学生信息的方法。而后面教师管理系统里面可能也有一个关于教师信息的类——teacherInfo,里面的属性和方法实际上和studentInfo里面的差不多,可能只是多了一些工资啊、计算年收入之类的属性、方法而已。
如果从减少代码重复的角度上看,也许我们完全可以让teacherInfo extends studentIfo,让teacherInfo拥有一些这两个类可能雷同的属性和方法,然后再添加自己特有的属性和方法。我觉得只要有点OO感念的人也许都不会这样做,但实际上我看到有些同学写的代码实际上就是这样子来“糟蹋”Inheritance的。实际情况并没有我举这个例子这么离谱,但本质上他们有时候写的代码给我这种乱认祖宗的感觉。
上面这个问题实际上有一个简单的解决方案,就上让上面那两个类同时都扩展一个person类,然后再根据需要添加自己的属性和方法。但我总这样子硬继承下来很别扭,而且很不灵活。例如,假设我设计的这个A学生类是需要有showInfo这个方法的,但是我设计的另外一个B学生类可能不需要showInfo这个方法而需要一个showScore的方法,我这两个学生类按道理都是要继承person类的,那么这个person是不是需要把这两个方法都包含在里面呢?再来如果我设计一个教师类又继承了这个包含了这两个方法的person类,那么作为一个教师要这两个方法来干嘛呢?的确,在person这样的大类里面放这样一些方法很傻,但如果不这样子的话,让子类可以任意的继承然后毫无约束的扩展,这样就必然导致结构上的混乱,管理上的麻烦,更不要说代码重用、提高效率了。
至此导致出现这么多问题的原因就只有一个,就是我忽略了一个在OOP里面,特别是像java这样不能实现多继承的语言里面的一个重要概念——接口(Interface)。有了接口这个东西以后就可以很好的解决上面那种结构上的逻辑上混乱了。
现在我可以重新设计我的person类和student类了。第一步,我可以把person设计为一个抽象类(抽象类也就是有部分已经实现了的方法的接口),或者干脆设计为一个接口。第二步,设计我的学生类和教师类,把它们都设计为抽象类,继承或者实现person这个抽象类或接口,在这两个抽象类里面可以有最通用的一些关于学生和教师的一些方法,例如上面计算成绩、计算工资之类的方法。第三步也是最关键的一步就是设计一大堆可能被相关类能用上的接口,例如有的学生类可能需要showInfo这个方法而不需要showScore这个方法,那我就可以只实现showInfo这个方法的接口就行了。
因此,当我要写一个学生类的时候我只需要要继承一个学生的抽象类然后再根据我可能需要的方法再来实现对应的接口,如果没有对应的再自己亲自写;同理我要写第二个学生类,或者教师类也同样用类似的方法来实现,这样从结构上就很一目了然这些类和接口到底是什么关系和有什么用。
而实际上更方便的就是可以把学生类可能需要的方法都写在一个接口里,然后再写一个适配器类用空方法来实现这个接口同时再继承对应的那个学生抽象类,那么当我要写一个具体的学生类的时候就可以直接继承这个设配器类就行了。
而利用Interface还可以真正的达到代码重用的目的。例如一个学生类可能有一个showCourse的方法,而一个教师类可能也需要这样的一个方法,这两个方法的本质也许不一样,但从结果上看却是几乎一样的,实现的方法可能都是把一个叫course 的Collection里面的元素遍历一遍并打印出来而已。而这个方法,可能是所以得具体的学生类和教师类都希望能有的方法,虽然可以在学生抽象类和教师抽象类里分别再加上这个以实现的方法,但是这样这个方法还是被写了两次,还不是不够效率。因此,我觉得可以在学生抽象类和教师抽象类和person抽象类间再加一个抽象类,例如叫personInSchool的抽象类,让它继承person类,并且实现这个方法,然后学生抽象类和教师抽象类再去继承这个personInSchool这个类,这样,代码就可以达到重用的目的了。至于不同的抽象类实现同一个方法是可能需要的参数不同,这个实际上也可以用适配器这个技术解决。
这样,Class、Abstract Class、Interface互相配合,就可以组成一个完整又灵活的体系,让OO的精髓发挥到极致。而事实上我上面的大部分想法都是从JAVA的API文档里面学来的,在java的标准类库中有大量的接口和抽象类就证明了这两样东西对OO重要性。而实际上我认为,从某种意义上说,可以把所以得抽象类和类都理解为接口,抽象类正如前面所说的是有部分已经实现了的方法的接口,而类就是所以方法都已经实现的接口而已。
接口这个东西看起来不难,但用起来却很有学问,很多初学者都会忽略掉,包括我,记得以前和朋友、同学交流的时候都经常会说虽然知道接口是什么,但就是不知道什么时候用和怎么用。但其实原来答案就一直在我们的身边——API文档。只要好好观察那里面每个类之间的关系,想想为什么,为什么这里非得要用接口,而那里怎么又会用到抽象类,就不难找到答案了。虽然实际要灵活应用起来还是要花一段苦功才行。
因此,我觉得要更好的理解OOP,除了最开头讲的三大要素之外,还有一个可能是更关键但又容易被忽略的要素——接口。类是具体对象共同性质的抽象集合,而接口就是搭建类的一个个框架,接口让类能描述的东西变更多了,而且让类与类之间的关系更加清晰了。所以我觉得如果称接口是OOP的第四大元素也一点不为过吧。