本来想告一段落别写编程范型的东西,但是这个话题最近发现很有意思,就拣出来唠一唠。从中除了能看出很多有趣的语言特性,观察不同语言的设计,还可以发现程序语言的发展过程。这里谈到的语言特性,都是从 C++的多重继承演变而来的,都没法完整地实现和代替多重继承本身,但是有了改进和变通,大部分功能保留了下来,又避免了多重继承本身的问题。

C++的多重继承

这个问题我觉得需要从老祖宗 C++谈起,我记得刚开始学 C++的时候老师就反复教育我们,多重继承的问题。比如说二义性问题,也就是说,两个父类如果定义了同名的方法,调用它的时候编译器就不知道怎么办了。

但是需要说清楚的是,多重继承确实是有其使用场景的,继承表示的是“is a” 的关系,比如人、马,都是切实存在的实体类,而非某一种抽象,有一种动物叫做人马兽,既为人,也为马,那么不使用多重继承就无法表现这种关系。

就上面的问题,针对人(Human)、马(Horse)和半人马兽(Centaur),其中一种解决的办法是引入虚基类动物(Animal),作为 Human 和 Horse 的共同基类,Centaur 同为 Human 和 Horse 的子类,这样只要:

Animal 虚基类里面定义的纯虚方法被 Human、Horse 之任一实现,不实现的一侧继续声明其为纯虚函数,

或者无论 Human、Horse 中是否实现,Centaur 中实现即可。

具体来说,哭(cry)这个行为应该是人、马和半人马都应当具备的行为,只是实现不同:

class Animal {

public:

Animal(){...}

virtual void cry() = 0;

};

class Human : virtual public Animal {

public Human() : Animal() {}

void cry() {

// human impl

}

};

class Horse : virtual public Animal {

public Horse() : Animal() {}

void cry() {

// horse impl

}

};

class Centaur : public Human, public Horse {

public Centaur() : Human(), Horse() {}

void cry() {

// centaur impl

}

};

Java 中实现多个接口

首先,必须说明的是,在 Java 倡导使用实现多接口来代替多重继承的功能,实际是不合理的,真正的多重继承场景是难以使用实现多接口来代替的。确实多重继承有其问题,但是因为这个问题,就把多重继承粗暴地从语言特性中抹去,是有些因噎废食了。

public interface Cryable {

public void cry();

}

public interface Speak {

public void speak();

}

class Human implements Cryable, Speak {...}

class Horse implements Cryable {...}

class Centaur implements Cryable, Speak {...}

以上是 Java 中的一个例子,人能哭,人也能说;但是马只能哭,不能说;而半人马呢,和人一样,会哭也会说。代码很容易理解,但是从中非常容易看出,半人马和人、马之间的紧密的“is a” 的关联关系就丢失了。就这个问题,Java 中还有一些其它的实现方式,比如把 Human 和 Horse 都变成接口:

public interface Cryable {

public void cry();

}

public interface Speak {

public void speak();

}

public interface Human extends Cryable, Speak {}

public interface Horse extends Cryable {}

public class Centaur implements Human, Horse {...}

这个方法倒是近似保留了半人马和人、马之间的联系,但是为此强行把本该属于实体类的人和马都变成了接口,也不甚合理。

值得一提的是,Java 中实现多个接口的做法是介于多重继承和鸭子类型(Duck Typing)中间的方案,即既没有多重继承“is a” 的明确定义,又不像常规鸭子类型那样在编译期缺少任何方法接口定义的约束,下面我还会介绍其它几种语言对多重继承的改进和变异。

JavaScript 的构造继承和拷贝继承

JavaScript 彻底从语言层面丢掉了接口约束,变成了真真正正的鸭子类型,使用构造继承和拷贝继承可以模拟多重继承。

var Human = function() {

this.name = function(name){...};

};

var Horse = function() {

this.jump = function(){...};

};

var Centaur = function() {

Human.call(this);

Horse.call(this);

};

这就是构造继承,Centaur 同时具备了 Human 和 Horse 的成员方法。

拷贝继承的示例代码就不写了,Centaur 的定义中,分别遍历 Human 和 Horse,把 Human 和 Horse 的成员方法和属性一一取下来按到 Centaur 自己身上。

完成这样的行为以后,Centaur 能够命名也能够跳跃,具备了二者的共性,它即可以被当做人,也可以被当做马。那么 Centaur 就是人、也就是马,这就是鸭子类型(只要会嘎嘎叫,就可以视作鸭子来调用);但是,在使用 instanceof 判断 Centaur 的实例是否是 Human 或者 Horse 时:

var centaur = new Centaur();

console.log(centaur instanceof Horse);

console.log(centaur instanceof Human);

都会打印 false,所以,JavaScript 对多重继承的模拟也只是模拟而已,根本不是真正的多重继承。JavaScript 本质上是不存在多重继承的,就连继承的实现,也没有一种方法是完美的—— 详情请阅读 《JavaScript 实现继承的几种方式》。

Go 语言的 Structural Typing

Structural Type,结构类型,本质上来说,它就是静态语言中的鸭子类型。不用显式声明某实体类实现自某一个接口,只要这个实体类具备了这个接口的方法,那么它就是这个接口的实现。

type Human struct {...}

type Horse struct {...}

type Centaur struct {

Human

Horse

}

type Speakable interface {

speak()

}

type Jumpable interface {

jump()

}

func (this *Human) speak() {...}

func (this *Horse) jump() {...}

Centaur 里面包含了 Human 和 Horse,这使得 Centaur 同时具备了 Human 和 Horse 的成员方法。很显然,这也不算多重继承,但是实现了类似的功能。类之间的层次关系部分丢失:没有丢的是 Human 和 Horse 与 Centaur 之间存在联系,但是变成了关联关系,并非继承关系。

Scala 的 Trait

Trait,直译叫做特征,Scala 不能实现多重继承,但是类似地,也通过一种特定的语义语法引入其它类的功能:

class Human() {

def speak() = ...

}

trait Horse {

def jump() = ...

}

class Centaur() extends Person with Horse

如上,Centaur 真正的父类其实是 Human,这才是实实在在的继承关系,但是一样具备了 Horse 的功能。Trait 的功能还是要略比真正的继承弱一些,这个例子中在实现某特征的时候,就没有办法调用该特征类的构造器(创建特征实例)。

Ruby 的 Mixin

Mixin,混入,可以让目标对象获得某一个模块的功能,在 Groovy 里面也有类似的特性。

class Human

def speak

end

end

module Horse

def jump

end

end

class Centaur < Human

include Horse

end

和前面说的 Trait 非常类似,只有 Human 是 Centare 真真正正的父类,Horse 甚至连类都不是,这是它局限的地方。

=======================================================================================

【2014-3-23】昨天和一位朋友讨论了这篇文章中提到的多重继承的问题,他的观点是,文中除了 Java 以外,剩下的几种,JavaScript 的构造/拷贝继承、Go 的 Structural Type,或者是 Trait、Mixin,这些都是多重继承,而且大同小异,最多算是不同的实现方式而已;Ruby 的作者松本行弘在他的《松本行弘的程序世界》书中也是这样的观点,因为“ 继承” 最重要的事情是具备父类的“ 特征” 和“ 功能”,这些都做到了。但是文中我没有这样认为的原因是,这些实现形式都丢失了子类和父类之间的联系。

另外一件事是 Ruby 的 Mixin,上面的这个例子举得不是很好,Human 是类,而 Horse 是模块,从这点上来说,这本身就不对等了,合理的办法是让 Human 和 Horse 都变成类,而分别创建名为 Jump 和 Speak 的模块—— 让 Human 引入 Speak 模块,让 Horse 引入 Jump 模块,而 Centaur 引入 Speak 和 Jump 模块。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

java自行车火多重,多重继承的演变相关推荐

  1. java中是否支持多重继承_java支持多重继承吗 JAVA特性面试题:

    1.简要介绍java程序的健壮性. 答:JAVA程序会在编译和运行的时候自动的检测可能出现的错误,而且它是一种强类型语言,对于类型的检查很严格,而且它的垃圾回收机制也有效的避免了内存的泄漏. 2.为什 ...

  2. 计算机毕业设计Java自行车租借管理系统(源码+系统+mysql数据库+Lw文档)

    计算机毕业设计Java自行车租借管理系统(源码+系统+mysql数据库+Lw文档) 计算机毕业设计Java自行车租借管理系统(源码+系统+mysql数据库+Lw文档) 本源码技术栈: 项目架构:B/S ...

  3. Java基础篇:多重继承的实现

    多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道Java为了保证数据安全,它只允许单继承.有些时候我们会认为如果系统中需要使用多重继承往往都是糟糕的设计,这个时候我们往往需 ...

  4. java while语句打印三角形_java基础之五小节带你走进java流程控制—多重循环

    四.多重循环 在一个循环语句内部再嵌套一个或多个循环,称为多重循环/嵌套循环.while.do-while与for循环可以任意嵌套,可以嵌套任意多层.一般工作中多见的就是两层. 4.1 多重循环 打印 ...

  5. java自行车租凭系统项目包_基于jsp的自行车租赁-JavaEE实现自行车租赁 - java项目源码...

    基于jsp+servlet+pojo+mysql实现一个javaee/javaweb的自行车租赁, 该项目可用各类java课程设计大作业中, 自行车租赁的系统架构分为前后台两部分, 最终实现在线上进行 ...

  6. java这么火,现在学还有前途吗?

    可以说是程序编程里面一个比较难学的项目了,作为一种最流行的网络编程语言之一,java语言在当今信息化社会中发挥了重要的作用.Java语言具有面向对象.跨平台.安全性.多线程等特点,这使得java成为许 ...

  7. java中允许多重继承吗,java允许接口的多重继承吗

    java类是单继承的.classB Extends classA java接口可以多继承.Interface3 Extends Interface0, Interface1, interface-- ...

  8. 2019年JAVA比较火的框架_2019年Java技术中当前流行的三大框架

    对于参加Java培训掌握技术的小伙伴来说,相信或多或少都会听到关于Java编程语言的三大架构.Java作为编程语言界元老级的存在,这么些年来不仅平稳的发展,而且也渐渐成为众多企业首选的编程语言.另外是 ...

  9. java用内部类实现多重继承

    最常见的实现多重继承的方式,是implements interface1,interface2,interface3- 也可以通过多个内部类extends多个抽象类. 示例如下 public clas ...

最新文章

  1. eclipse 关联 Maven本地仓库的配置
  2. 文本每行都应该换行——vi文件末尾自动换行,不会导致php输出空行
  3. CUDA学习(三)之使用GPU进行两个数组相加
  4. POJ2421 Constructing Roads 最小生成树
  5. Web服务器控件和HTML控件的区别与联系
  6. LwIP移植准备工作
  7. 看雪 好文 汇集贴(持续更新)
  8. Spring read-only=true 只读事务的一些概念
  9. D. Bash and a Tough Math Puzzle(区间gcd+思维)
  10. 张宇:7~12月考研数学该如何复习?
  11. 清除chrome浏览器缓存
  12. ubuntu双系统卸载并重新安装
  13. Mac上的python的数据分析与挖掘学习之路(一)
  14. 2015年底学习汇总报告
  15. Power BI中字体使用微软雅黑
  16. C# 经纬度格式化输入控件的简单实现(附html、Qt实现)
  17. java代码随机取名字
  18. 311运动(冰箱与内裤)的由来
  19. 【洛谷】P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布(详细代码)
  20. 当贝D5X和极米Z6XPro画质对比 当贝D5X和极米Z6XPro选哪个

热门文章

  1. 使用JavaScript将图片保存至本地
  2. 验证控件jQuery Validation Engine调用外部函数验证
  3. CSS 框模型( Box module )
  4. 给Editplus去掉.bak文件
  5. 20140213-面向对象技术概论
  6. 关于GCC的理解——On the understanding of the GCC
  7. ubuntu命令查询版本和内核版本
  8. CoreData 从入门到精通(三)关联表的创建
  9. 第一次冲刺阶段(五)
  10. jquery 图片切换插件(初版)