其实接口可以说并不存在的二义性问题。Interface a { int test(); } Interface b { int test(); } class c:a,b { // overrider test(); } c c1 =... c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。 abstract class a { public abstract int test() { consoli.write('a'); } } abstract class b { public abstract int test() { consoli.write('b'); } }
class c:a,b { // overrider test(); } c c1 =... c1.test() //
其实接口可以说并不存在的二义性问题。Interface a { int test(); } Interface b { int test(); } class c:a,b { // overrider test(); } c c1 =... c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。 abstract class a { public abstract int test() { consoli.write('a'); } } abstract class b { public abstract int test() { consoli.write('b'); } }
class c:a,b { // overrider test(); } c c1 =... c1.test() //
其实接口可以说并不存在的二义性问题。Interface a { int test(); } Interface b { int test(); } class c:a,b { // overrider test(); } c c1 =... c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。 abstract class a { public abstract int test() { consoli.write('a'); } } abstract class b { public abstract int test() { consoli.write('b'); } }
class c:a,b { // overrider test(); } c c1 =... c1.test() //
有点点明白了,感觉C#已经很好的解决了二义性,也许真的是为了好管理,而使用了限制更多的interface。ex Interface a { int test(); } Interface b { int test(); } class c:a,b { // overrider test(); } c c1 =... c1.test() a ia = c1; a.test();b ib = c1; b.test();其实他们都是调用的是c里面的test的实现,如果在c中要override接口a,b中的test()方法,要显示指明。
pubilc class a{ public virtual void test(); } public class b:a{ public overrider void test(){} } public class c:a{ public overrider void test(){} } public class d:b,c {}a temp = new c(); temp.test() //????
上面的问题有人知道么,就比如 class A {....} class B:public A {....} 那么B在内存里的布局的话,就是先存放base class A的nonstatic内容,再存放自己的nonstatic内容... 但是如果是Interface A呢,那布局会怎么样呢,会不会就是只有一个vptr存在后面跟B的内容呢?
to sjjf:OO是为了化繁为简而不是反其道行之...现实世界有多复杂不用你说...然而我们谈OO也只是对现实的简单抽象...目前的技术不可能去谈对现实世界完全的抽象...你一定要钻牛角尖别人也没办法...你引用那么多书上的东西并不能表示什么...尽信书不如无书...另外书也是人写出来的... 我不知道儿子和父亲有何本质的区别,请告诉我本质性的区别。 --------- 这个世界上只要是男人就是儿子...但父亲必须要有实现...不是每个男人都一定是父亲也不是每个男人都一定会成为父亲...你不会连这点常识都不知道吧...
不同意别人观点,至少你要理解,别人的观点,书也是如此,你不同意ec++ 43 ,至少要把它读完。 你所说的解决方案,书上早已经给出,但这种方式是非常笨拙的, 1、一个类从另一个类派生除了纯虚函数,没有任何要求子类必须override它的virtual函数 2、如果两个父类同名函数有几十个怎么办 (//这里只是研究语言,不要讲设计不合理,因为有可能父类是不同的人设计的) 以上只是一个小小的问题如果是菱形继承怎么解决 pubilc class a{ public virtual void test(); } public class b:a{ public overrider void test(){} } public class c:a{ public overrider void test(){} } public class d:b,c {}a temp = new d(); temp.test() //???? 所以ec++43 给出的方案就是多重继承下要避免菱形继承
to ifdone:OO重要的是真正理解对对象的抽象而不是套什么理论和概念...男人一定是儿子是由“人”及其性别决定的...并且它是只读的简单数据...所以说它应该只是人的一个属性而已... 父亲则不同...一个人必须是男人而且还要与某个女人通过性行为后由这个女人经历怀孕、生产等过程之后才能够被其子代称为父亲...所以说它应该是一个需要实现的接口...如果将其作为一个可继承的根去理解那简直是滑稽...
exclass a
{
public virtual int test();
}
class b
{
public virtual int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() //
ex
Interface a
{
int test();
}
Interface b
{
int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() 二义性还是存在,并没有消除啊?
就是,像c++这种传统的OO语言,都没能很好控制多继承,
所以java,c#都已经舍弃了多继承,改用更加窄的接口
{
int test();
}
Interface b
{
int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。
abstract class a
{
public abstract int test()
{
consoli.write('a');
}
}
abstract class b
{
public abstract int test()
{
consoli.write('b');
}
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() //
{
int test();
}
Interface b
{
int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。
abstract class a
{
public abstract int test()
{
consoli.write('a');
}
}
abstract class b
{
public abstract int test()
{
consoli.write('b');
}
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() //
{
int test();
}
Interface b
{
int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test()因为接口的函数没有函数体, a.test() 或 b.test() 无所谓。但是类就不一样了,因为它可以定义函数体。
abstract class a
{
public abstract int test()
{
consoli.write('a');
}
}
abstract class b
{
public abstract int test()
{
consoli.write('b');
}
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() //
但.net框架可以通过显示实现接口来避免这样
上面的代码
class c:a,b
{
int test();//这里是a的test
b.test();//这里是b的test
}
Interface a
{
int test();
}
Interface b
{
int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() a ia = c1;
a.test();b ib = c1;
b.test();其实他们都是调用的是c里面的test的实现,如果在c中要override接口a,b中的test()方法,要显示指明。
pubilc class a{
public virtual void test();
}
public class b:a{
public overrider void test(){}
}
public class c:a{
public overrider void test(){}
}
public class d:b,c
{}a temp = new c();
temp.test() //????
a temp = new d();
主要就是为了功能更加明确,接口实现的是一种契约,即要实现哪个功能
继承自接口的类必须实现这个功能并且这个二义性会得到解决,它并没有提供实现代码,所以具体实现哪个是无所谓的(个人想法)
但是如果想具体到接口就得加上接口名了
class A
{....}
class B:public A
{....}
那么B在内存里的布局的话,就是先存放base class A的nonstatic内容,再存放自己的nonstatic内容...
但是如果是Interface A呢,那布局会怎么样呢,会不会就是只有一个vptr存在后面跟B的内容呢?
20楼自己运行过么?
class d 不能有两个base class 吧?
夏历 是 汽车
三蹦子 不是 汽车
世界从来不是单一的继承,如果是这样,那么这世界就不会像现在那么精彩了,
在自然界如此,在社会上也如此。
自然界的例子:你肯定有你父亲和你母亲的某些特征。
如果是单根继承,我很难想象你是怎么样从一个母体继承下来,然后再实现了谁的接口...
物种不是原样拷贝下来之后,再按照某个接口进行改造实现的,
而是同时兼有两个父类的特征然后成长的,
如果单根继承+接口无法解释,那么就可以认为这个理论模型解释现实是乏力的。你所谓的骡子,狮虎兽的怪物存在,不是人违背自然规律,没有人工的协助下也是可能发生的,
你不能保证一头驴不会对马发情,一头狮子不会对母老虎产生性冲动,从而作了不该做的事情,然后居然还有了结果,
金刚都可以为了美女在摩天大厦打飞机,还有什么不可能的呢?
再看看uml参考手册是是怎么评价多重继承的吧
4.5.3 单分类和多重分类
在最简单的形式中,一个对象仅属于一个类,许多面向对象的语言有这种限制。一个对象仅属于一个类并没有逻辑上的必要性,我们只要从多个角度同时观察一下真实世界的对象就可以发现这一点。在UML更概括的形式中,一个对象可以有一个或多个类。对象看起来就好像它属于一个隐式类,而这个类是每个直接父类的子类—多重继承可以免去再声明一个新类,这可提高效率。
关于一个人多种身份的那个例子。
我想,你并没有看过业界那本经典的《面向对象设计与分析》,这是里面关于多重继承存在重要性的一个精辟的例子。
况且关于
------------------------
儿子...父亲、教师、老公、情人等等多种角色并不是娘胎里带来的
------------------------
我不知道你知道"概念","抽象概念","概念体系"这三个东西不。
然后他们怎么样映射到类的设计的。
如果不知道,读读我推荐的那本书吧。
况且,在我们现实中的概念,
在我们不同的问题域中充当的角色有可能是不一样的,里面举了桌子以及组成部分的关系为例。
------------------------
另外从OO的角度看...儿子仅仅是男性属性决定的一个属性...与父亲、教师、老公、情人等等有本质的区别...
------------------------
我不知道儿子和父亲有何本质的区别,请告诉我本质性的区别。接口,不过是一个对物体的view而已,在程序分析和设计阶段可以允许我们遵照契约来调用,
解除了上层设计和下层设计的互相依赖的约束,让两者能够并行开发,
是一种更细致的分工机制的体现。
因而在分水岭产生了一个叫接口的概念,
用以描述与实现无关的动作和行为,这是接口的广义定义,
这个概念在很早以前就出现了,c头文件里面包含的东西都是接口,
后来出现了oo,需要描述一些多态这种特征,
cpp的接口的概念的支撑才包含了抽象类,
其他采用了单一继承的oo语言,没有办法实现多重继承只好采用接口这个关键字,
这可能是属于最初设计的缺陷,不得已引入叫interface这个混淆视听的畸形的东西作为代替方案,
而不是最好的解决方案,也可能是在开发的复杂度和设计的理性之间的一种折中方案。
在商业的鼓吹下居然也红了起来。
使用的门槛倒是很低的,但是和多重继承是两码事。
继承最初的目的是为了复用,多重继承也是,可是java的单根+接口方式并没有达到这个效果。
转而在单继承+接口的机制下以聚集的方式,或者可以称之为委托的方式来实现复用,
采用这样的方式会带来很多的缺点,比如组合爆炸,
比如沟通障碍迫使你会为被委托类类中打开一个回调的窗口,
如果不这样将迫使你为协调这两者的沟通问题重新编码,
而且菱形问题下放给程序员去手工指定实现,这样会更糟,
而且它是对抽象的一种破坏.
如果你写过这样的程序就知道累了。
单根继承+接口的方式下的继承类对上层使用勉强达到了复用接口的要求,但是对下层的实现的支持,
糟糕的很,但适用抽象能力很差的人。
如果你对此有异议,可以举个例子出来告诉我java或者
采用单根继承+接口的语言是怎么实现复用的。
如何避免我提到的这些缺点。
关于多重继承的二义性,是在编译期就可以检查出来的,也有解决方案,不能算是一个缺陷,
只不过是复杂一点(主要集中在编译器的实现上,而不是设计和使用上),
不过现实就是这么复杂的,他只是忠实的描述了现实而已。
就象键盘的布局,也是约定俗成的
C#语法就是这样规定,编译器也这样识别
无论道理讲不讲都得按语法写代码不是?
这个问题得问Anders Hejlsberg,我就不瞎猜了。
我也赞同一下吧。哈哈。存在即道理,我们可以清清楚楚的说出什么好处和坏处,但取舍时却会根据不同情况来干。再说接口和类在某种使用上真的是很接近。
就像我接触语言时大家就都说goto是邪恶的。为什么,我不知道,因为我从来没用过
我不知道儿子和父亲有何本质的区别,请告诉我本质性的区别。
---------
这个世界上只要是男人就是儿子...但父亲必须要有实现...不是每个男人都一定是父亲也不是每个男人都一定会成为父亲...你不会连这点常识都不知道吧...
还有就是单根继承的问题,C#所有类型都是从object继承而来,这样的单根继承在很多方面有好处,例如垃圾回收……
我不知道儿子和父亲有何本质的区别,请告诉我本质性的区别。
---------
这个世界上只要是男人就是儿子...但父亲必须要有实现...不是每个男人都一定是父亲也不是每个男人都一定会成为父亲...你不会连这点常识都不知道吧...================================================
我不明白你说的反其道而行之是什么意思?
oo只是一种工具,是否能化繁为简,需要看设计者能达到什么样的抽象高度。
就像有些人写一个计算器洋洋洒洒百余行c/cpp程序,
而有的人只需要通过寥寥几行yacc/antlr的语句产生百余行的c/cpp程序就ok了。
单从程序行数,我们很难区分两者的抽象程度,但从经验上,我可以很负责的推断:
前者是很难理解后者所达到的抽象程度的,没人说一个技术能够对世界进行完全的抽象,就像人无法全部认识世界一样,
但是总有认识的深浅,如果一个技术在一个封闭才的问题域里面能够以最简捷的方式提供
描述的方式和解决方案的描述方式,那么这个技术就是最好的,或者说这个技术优于其它技术。在你表示对书中的内容不屑的时候,你有这种权利,但请确认你读懂书中的思想再说。我引用了uml标准。
如果认为uml不怎么样,那么我也可以认为你不怎么样,除非你能拿出什么能让我信服的高见。
没读过uml,别跟人(至少别跟我)谈oo分析和设计,基本上我可以认为你不懂oo。
我还引用了 面向对象分析与设计 一书的例子,国外的经典版本,
在国外曾被认为oo分析与设计的入门必读教材,
我也觉得很经典,并能给我以前的实际设计工作以指导意义。
关于你所谓的常识,实在有点不太想和你讨论,
其一:在主流的oo设计语言中,一般而言,一个类在拥有什么样的方法和属性
在设计期是固定的,当然也不排除像java的语言,运用反射功能使类拥有新的方法
或者属性。但是那种技巧从来就不被纳入设计手段中,破坏了设计的严密性。
在你的问题域中,一个人从儿子成为父亲,只能说是一种状态的转变。
而我说的比喻要表达的是 一个人可以有儿子的职能和父亲的职能,
比如儿子的职能之一赡养老人,
父亲的职能之一培养小孩。
一个社会人可以从两者继承这两个职能,有没有职能是一个问题,
能不能行使这个职能是另外一个问题。
其二:一个脱离类体系的类的讨论完全没有意义。要讨论是需要从全盘去考虑的。其三:没有评价标准。
一个合理的类体系设计是oo设计中最强大和最迷人也是最难的地方。
没有方法论的支撑,基本上都是属于拍脑袋出来的。
说来有点可笑,我没有碰到几个能把一个类体系的几大量化评价标准说出来的人,
更别说一个合理可行的构建类体系的过程了,
那些量化指标的后面又代表什么含义,不知道有几个人曾思考过么?
如果自己做出来的东西,都没有一个评价标准,那只能说是在自吹罢了。
如果你不知道这些的话,还是选择相信书吧,
多读点书,还是有点用的,它会告诉你这些。
支持这个另外一方面来讲,是更轻便地实现对Action Prototype 的支持.
在你表示对书中的内容不屑的时候,你有这种权利,但请确认你读懂书中的思想再说。 另外我从来没有表示过对别人的书有不屑之意...只是所谓读书不能读死书的意思...显然阁下的理解能力与众不同...长篇大论未必有力...说话要说到点子上...-----------------------------------------------------------------
你还是先把你认为的点说出来吧,也许我误解了你的意思,你引用那么多书上的东西并不能表示什么...尽信书不如无书...另外书也是人写出来的...
--------------------------------------------------
疑问1: 是我引用错了,还是书本错了,如果错了,错在哪儿?
疑问2: 你觉得那些地方那些地方不能相信?为什么?如果采用你的所谓的无书?
那如何证明我的决定是对的?
疑问3: "另外书也是人写出来的..."你是想告诉我书不尽正确,
还是想告诉我那些书没啥大不了的?还是想告诉我其他的别的什么?
如果一定要多继承的话 能不能这样
虽然不能直接
class A
{}
class B
{}
class C:A,B
但是可以
class A
{}
class B:A
{}
class C:B
{}
就是把多重继承弄成多层继承
如果LS两位只是为了这么一句话而争论的话,就显得太过于无聊了。到底是多重继承还是单链继承更符合现实世界这本来就是一个没有意义的问题,就像我们不是因为面向对象更符合我们真实的世界才选择面向对象的一样。说面向对象是从现实世界中抽象而成的理论我想在任何一个资深的OO程序员来说都知道这只不过是把OO变得玄之又玄的新手入门教材而已。OO能够抽象我们的程序,提高代码复用率,降低耦合度,使得程序可伸缩性更佳,这些是我们选择OO的原因,而不是因为我们把我们的家抽象成了一个Object然后从日常生活中脑子一拍想出了程序。
单根结构中的所有对象都有一个通用接口,所以它们最终都属于相同的类型。另一种方案(就象C++那样)是我们不能保证所有东西都属于相同的基本类型。从向后兼容的角度看,这一方案可与C模型更好地配合,而且可以认为它的限制更少一些。但假期我们想进行纯粹的面向对象编程,那么必须构建自己的结构,以期获得与内建到其他OOP语言里的同样的便利。需添加我们要用到的各种新类库,还要使用另一些不兼容的接口。理所当然地,这也需要付出额外的精力使新接口与自己的设计方案配合(可能还需要多重继承)。为得到C++额外的“灵活性”,付出这样的代价值得吗?当然,如果真的需要——如果早已是C专家,如果对C有难舍的情结——那么就真的很值得。但假如你是一名新手,首次接触这类设计,象C#那样的替换方案也许会更省事一些。
单根结构中的所有对象(比如所有C#对象)都可以保证拥有一些特定的功能。在自己的系统中,我们知道对每个对象都能进行一些基本操作。一个单根结构,加上所有对象都在内存堆中创建,可以极大简化参数的传递(这在C++里是一个复杂的概念)。
利用单根结构,我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中,而垃圾收集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构,而且系统通过一个句柄来操纵对象,那么实现垃圾收集器的途径会有很大的不同,而且会面临许多障碍。
由于运行期的类型信息肯定存在于所有对象中,所以永远不会遇到判断不出一个对象的类型的情况。这对系统级的操作来说显得特别重要,比如违例控制;而且也能在程序设计时获得更大的灵活性。
但大家也可能产生疑问,既然你把好处说得这么天花乱坠,为什么C++没有采用单根结构呢?事实上,这是早期在效率与控制上权衡的一种结果。单根结构会带来程序设计上的一些限制。而且更重要的是,它加大了新程序与原有C代码兼容的难度。尽管这些限制仅在特定的场合会真的造成问题,但为了获得最大的灵活程度,C++最终决定放弃采用单根结构这一做法。而C#不存在上述的问题,它是全新设计的一种语言,不必与现有的语言保持所谓的“向后兼容”。所以很自然地,与其他大多数面向对象的程序设计语言一样,单根结构在C#的设计方案中很快就落实下来。
都说不想再讨论了...所以只回答你的疑问...第一点:在我的问题域中,所谓的角色依靠职能而存在,
一个叫人的物体所拥有的各种职能在我们需要用到这个概念的时候,
在他出现的场合就已经拥有这些职能了,
而不是一会儿加一个职能,转化成某个状态后再动态加一个职能,
在软件里,很难做到这样一点。
-------------
专有名词扯再多看不破本质都是纸上谈兵...如果你不能理解儿子是一个属性而父亲、教师、老公、情人等应该是接口我并不奇怪...疑问1: 是我引用错了,还是书本错了,如果错了,错在哪儿?
疑问2: 你觉得那些地方那些地方不能相信?为什么?如果采用你的所谓的无书?
那如何证明我的决定是对的?
疑问3: "另外书也是人写出来的..."你是想告诉我书不尽正确,
还是想告诉我那些书没啥大不了的?还是想告诉我其他的别的什么?
-------------
基本上我不知道你的疑问都是怎么总结出来的...我从头至尾没有说你引用错了也没有说书本错了更没有说谁的不能相信...只是想告诉你知识不是简单堆积消化理解了才是自己的...前人的经验不但是总结更是破除了更前人的经验得来的...如果你不能理解尽信书不如无书这句话那更没什么可说的了...
========================================================
有点无语,也许你要真正做到设计师才能理解我要表达的意思。
至少要真正的用过一次oo思想来分析和做解决方案后了你才能理解。
我举的面向对象分析与设计一书中的桌子和组成部分的关系的例子就
是想告诉你问题域的重要性,可惜你没有去翻过。
如果你不能对问题域,角色这样的名字敏感,那也说明你还不是设计师,
至少,还达不到设计师的层面。
算了,多说无益,你还以为我在侮辱你的iq。
==============================================================to Ivony,
我并不认为单一的继承更符合真实的世界,
比如现在有的双性生物都是双继承的,甚至是多继承的,例如花授粉。
菱形继承在古代甚至现代也是有,例如近亲结婚,当然并不是严格的封闭菱形,
但可以找到菱形的。单一的继承也可以解决问题,不过并非优雅的设计。
就像lambda算子中的多参数总可以curry化成单参数一样,
两者是等价的,但是琐碎程度不一样。
我在前面也提过,单继承+接口方式对上也能实现多继承功能,
但是就像在饥饿的沙漠里面逮住了一只蜥蜴一样,虽非美味,为了
填饱肚子求生存,也只能强咽下去。
我用过java,也用过cpp,说实话,比拼coding,我也不一定输给某些人,
这点我还是有自信的,比拼design,我也不惧大多数,因为我困惑过,思考过,
并找到理论依据。
我有两年半的青春是花在java上面的,说实话,尽管java中接口满天飞,
我对这种方式没有什么好感,(没用过c#,不知道它和java的方式有什么不一样)
缺点我已经说了,设计复用性相对较差(当然是拿优良的java设计和优良的cpp设计来说的)
代替的解决方案缺点也很多,惟一可以带点说服力的是垃圾回收。
但是一个良好的设计是可以稍微花费一点开销就能够取得内存自动管理,
在开发效率和运行效率之间取得了最高的比率。
这方面具体的例子可以参考geant4,一个开源的空间物理计算引擎,
里面用到了很多cpp的oo设计技巧,很优雅,没事干的可以下来玩玩。
so,我说单继承+接口更适合愚蠢的人用,这话并不过份。
语言,思想,不过是我们描述世界的一种工具而已,
不能简单地对面向对象是否真的合适描述我们的现实世界这样的问题下一个对或者错的结论。
刀剑有刀剑的用处,枪炮有枪炮的妙用,但任何工具都有合适使用的范围。
这个使用范围就是我们的软件设计里面的问题域。 关于面向对象,本公子今天比较无聊,顺便忽悠一番。
在很久以前,有个家伙叫图灵,为了证明可计算问题是真的可以求解的,
造出了一个叫图灵机的玩意儿(计算模型),同时还催生了叫算法的玩意儿,
这是计算理论里面的可计算性和计算复杂度的内容。
(其实,同时代的还有邱奇的lambda算子,和一个忘了名字的人提出的递归函数,)
这些内容为形式演算奠定了理论基础,使自动计算成为可能。
后来,有个叫冯诺依曼的家伙,雇佣了300名女工为原子弹的扩散影响范围计算了几个月后,
非常苦恼,偶遇了一个军方人士后,在图灵机的模型上加上了可存储单元(内存)
捣鼓了一个可以自动计算的机器。后来慢慢的演化成现在计算机。
在最初的岁月里面,人们的工作重点是把自然语言里面表达的思想影射到机器可以识别的语言里面,以便可以驱使机器进行自动化的计算。繁琐的机器语言让人头晕脑胀,于是有人为了便于记忆
使用了些助记符号,于是产生了一个叫汇编代替品。
汇编语言提供的描述点是寄存器,移动,寻址,压栈,入栈,算数运算,位运算,调用
等描述点,提供的机制几乎是靠硬件实现的,如保护模式,段页式等。
so,适合汇编的问题域是关于机器操纵的。
在这种细粒度的语言描述下,要来描述现实中的模型,差距太大,
组合爆炸使其就像用一堆细胞来描述一个人体一样恐怖。
so,基本上汇编只用来描述最关键性的东西,
从现实中提出去需要的计算,转换成机器操作是机器语言程序员工作。在经过大量的使用后,人们发现在描述任务的步骤地时候,出现了一些稳定的特征,
例如有条件跳转的几种固定模式,循环的几种用法,局部变量,代码块(函数),
这些特征经过适当的封装,形成了新的描述点,同时涌现出了顺序,分支,循环,递归等流程机制,
于是便出现了过程化语言,它将任务的描述从机器层面解脱出来,扩大到面向过程式任务步骤层面,这时候类似描述一个人体就用躯干四肢和头来表达,
描述的粒度开始变大,直接影响了可描述任务的复杂度和规模。于是导致了c璀璨了近40年,
并在系统编程领域称霸,描述任务,它再合适不过了。
从现实模型中提取出需要的任务描述,是过程化语言程序员工作。当问题域涉及描述一个系统的时候,
过程式的语言就显得力不从心,就像描述一个公司系统内部的聚餐的时候,
用一堆堆躯干四肢和头和嘴如何移动来表现一样,虽然在有限的范围内,它也可能
描述的清楚,当容易转移人们的焦点,人们更关注的是系统的组,系统的状态,
系统的交互行为,系统的协作程度,而不是某一部分是如何移动的。
这迫使新的语言要提供一个更大的描述点,能够更贴近我们的生活模型,
我们的生活抽象出来的模型,是由一些系列的概念,以及概念的交互构成的。
因此oo应运而生,虽然很多人理解这个语言中类的概念是数据+函数,
但是忽略了有机构成几个字,不知道其中某些东西已经发生了质的变化,
就像木偶也是有四肢+躯干+头构成,但是却无法拥有生命,
oo,正是那个像木偶吹了口仙气的上帝,以封装,继承,多态等机制赋予类一个活的概念。恩,先到此为止,有事外出,下次再忽悠。
sjjf兄写的太长了,有没有中心句什么的放在段首啊,我看的头晕,就看了一贴,相当有文采,能忽悠
-------------------------------------------
本来就在乱弹,何须中心?继续昨天的忽悠。在讨论oo之前,让我们回头看看我们人类的一些基本认知。
以下观点纯属个人的扯淡,如有雷同,纯属偶合 :)
几百万年之前,我们的祖先茹毛饮血,所见所闻也基本是口舌相传,
知识的积累基本是新增和流失相对比例不会变化太大,
也许新增比流失总是要多一点点,
我认为在一段时期内,知识的新增和流失比例不会变化太多,
因为人脑的记忆和检索是有限制的。
这种状况直到其中某一个聪明的祖先学会了外物作为载体进行知识存储。
例如木绳记事。这将使知识得以脱离口舌进行传承。
从此在一个部落内,如果知识存储规则明确后,知识的流失量几乎成0。
虽然知识已存储,但是受限于部落规则的制约,成为信息孤岛。
知识孤岛之间不能流通和融合。可以想象一下当时的格局,
在苍茫的大地上,遍布了大大小小的原始部落,这些部落之间都有自己的知识储备,
就像星星的火种,一点一点的顽强的闪着,随时都可能会随着部落的消亡而熄灭。
随着进化,这些部落慢慢的学会了沟通,慢慢的在局部地区,语言开始有了交集
或者开始了统一。为部落之间的规则的流通提供了可能。
直到区域的部落之间统一的文字,规则的流通才畅通无阻,解读了规则,
也就能使知识融入在一起,
从此,知识在区域之间开始了雪球效应,那些星星之火终成燎原之势。
发展到今天,已走向全球的文字和语言的统一,虽然不是形式上的统一,
但是至少语言之间的同构关系已经建设起来,虽然没有全能的地球语翻译机,
但各种语言之间大体已经能够互相翻译了。人类在走过几千年的发展路程中,所见,所闻,所思,所说成淀下来的知识量日益庞大,
不知道从何时起,知识量已经超过了人脑的处理极限,一个人要想读完自己的国家的博物馆的
所有书,估计也要穷其半生或者一生。
人类的大脑在庞大的知识库面前开始感觉掌控无力,
这是便开始研究大知识量下的掌控方法,这些方法无非也是在知识的量和深度方面做文章。
在深度背后探索知识的规律,在量方面采用更有效的知识管理手段,
例如牛顿三大定律使得宏观世界的运动得到统一,让人们抛弃了纷乱复杂的运动现象,只需记住简单的但抽象的物理定律,这是知识深度的拓展,
在知识管理的工具方面计算机技术的发展,使得人类在信息量的掌控方面得到了极大的扩展。
在知识管理的方法方面也发展出了分类理论,文献检索技术等其中分类理论是我们所要关注的。
分类理论从现实中提取出具体事物的抽象描述(也就是俗称的概念),以图的方式(主要是树)
展现了概念与概念之间的关系,为我们描绘了世界认知的静态框架图。
oo借鉴了这种描述世界的方式,这些描述方式都是有数学基础支撑的,
例如类的划分标准是集合论里面的等价关系,泛化是一种偏序关系等等等。
有人发表了一篇论文叫 面向对象方法学理论基础 这本书讲的比我要好多了,就不累述了。uml与模式应用一书中忽悠我们在提炼类的概念的时候,其中有一种方法是借鉴事实。
这也是有道理的。提出了类的概念出来后,在构建一个类的体系的时候,有三种策略:自顶向下的演绎法
自下向上的归纳法,还有两者兼有之的混合法,
实际上如果和机器学习比较一下,你会发现这两者采用的策略竟是如此雷同。
我们在用oo分析与设计来描述我们的需求时,
不过是在大脑中虚拟了一种机器从另一个视角来重新认识我们的世界。
cpp多重继承很强大,但是不至于强大到为所欲为。
比如传说的人马,假设是从人和马两个继承下来的,
那么人马到底是用人的方法跑还是用马的方法跑呢?
我们人脑可以根据人马的形象,下半身是马,那肯定是用马的方法跑,
如果我以前都没有见过人马的形象,那我也判断不了到底人马是用人的方法跑还是
马的方法跑,人都无法判断,何况只拥有那点有限的规则的编译器呢?
没被足够的信息作判断,二义性是无法自动消除的。
所以我觉得scott meyers举的例子本身就不恰当。我不觉得我下面的代替方案有啥不妥。
class Lottery {
public:
virtual int draw(){printf("\nlottery::draw");};
};class GraphicalObject {
public:
virtual int draw(){printf("\n GraphicalObject::draw");};
};class LotterySimulation: public Lottery,
public GraphicalObject {
public:
virtual int draw()
{
printf("\n assume draw");
return GraphicalObject::draw();
};
};把 lottery ==>人,
GraphicalObject==>马,
LotterySimulation==>人马
draw==>跑 就是我想要实现的东西了。
在冲突的地方我显式地指明,
如果除了跑这个动作外,如果我想用的其他的东西都没有逻辑上的冲突,
那用隐式继承下来的东西,我想还是利还是大于弊,
如果大部分的东西都存在冲突,那么就需要考虑设计的合理性了,
也许那并不适合用多重继承来复用。to be continue......
你所说的解决方案,书上早已经给出,但这种方式是非常笨拙的,
1、一个类从另一个类派生除了纯虚函数,没有任何要求子类必须override它的virtual函数
2、如果两个父类同名函数有几十个怎么办 (//这里只是研究语言,不要讲设计不合理,因为有可能父类是不同的人设计的)
以上只是一个小小的问题如果是菱形继承怎么解决
pubilc class a{
public virtual void test();
}
public class b:a{
public overrider void test(){}
}
public class c:a{
public overrider void test(){}
}
public class d:b,c
{}a temp = new d();
temp.test() //????
所以ec++43 给出的方案就是多重继承下要避免菱形继承
我认为熟悉cpp语言,靠 cpp标准语言特别版 , cpp设计与演化, cpp gotchas,就够了,
昨晚我特地花了半个多小时的时间认真地读了 ec++ 43,通读一遍之后,我大致理解了作者意思,
但是实在太累了,我就只针对第一部分发表了些看法,
请你再仔细看看,作者提出来的解决方法是否和我的一样。
晚上有空再忽悠。
只不过是复杂一点(主要集中在编译器的实现上,而不是设计和使用上),
不过现实就是这么复杂的,他只是忠实的描述了现实而已。
======================
c++ 编译器可以这么实现,因为c++是源代码级面向对象,C#确不行,.net 下所有对象都是继承与object,多重继承的避免不了菱形继承,那样只有不让所有类继承于object,那么动态加载,垃圾回收所有基于运行时的面向对象部分怎么实现?
每个类都默认继承了object
这个就像是蚁后,然后能够初始的制造蚂蚁,然后有雄蚁(基类)
两个结合又能产生新的蚂蚁(子类)继承接口就像是学会了什么技能,新的蚂蚁继承了搬东西接口,它就变成工蚁,当然或许还要继承吃饭接口等等。
同样继承了打架接口的,就变成了兵蚁。这样在逻辑上就显现比较符合思维习惯多个继承的话,条理上就比较混乱,就像一只蚂蚁是由工蚁雄蚁加兵蚁雄蚁加蚁后产生。……这要怎么理解,而且为了制造这样的东西,我们还不得不去做 工蚁雄蚁 和 兵蚁雄蚁,可是雄蚁中却又不存在这样的品种所以我个人觉得这样的继承是为了更加符合逻辑,还有就是C#是结合了Java衍生的
public:
virtual int lotteryDraw() = 0; virtual int draw() { return lotteryDraw(); }
};class AuxGraphicalObject: public GraphicalObject {
public:
virtual int graphicalObjectDraw() = 0; virtual int draw() { return graphicalObjectDraw(); }
};
class LotterySimulation: public AuxLottery,
public AuxGraphicalObject {
public:
virtual int lotteryDraw();
virtual int graphicalObjectDraw(); ...};scott meyers 举的AuxLottery的例子,
实际上为了隐性的引用draw 而采用夹层+wrap的方式下放出两个纯虚接口。
杀敌一个自伤两个,这个买卖不合算。
我认为这是一种更糟糕的设计方式,而不是代替方案。
其一,违反了复用的原则,其中,没有复用到些什么,
除了对上保持了draw接口的统一外,没有啥东西是复用的。其二,作为类体系来讲,
为了一个隐形的引用,多增加了两个辅助接口,还下放到最底层的类实现。
比起将冲突的继承特征重新显式的指定,或者重新赋以新的涵义,
这种方式更差劲,没必要为了形式上的 多重继承 而束缚了自己的手脚。
so.这个不是合适的例子。在类(概念)体系中,处于上位的类(概念),必定比下位类(概念)拥有更少的内涵,
(下面请将类等同于概念,建议对这些名字感觉迟钝的朋友,
自己去弄懂客体,特征,概念,内涵,外延这些名词的意思,
当年我花了半个月的时间才理清这些名字的概念和关系,
这些逻辑学上的东西将更有助于作软件设计分析)
下位类是添加了更多内涵的类,这是我们之所以用继承的方式进行复用的原因,对一个类的下位类,根据不同的问题域可有好几种不同的划分的标准,
这些标准将会导致会有不同的侧重区分特征的内涵加入子类。
多重继承就是期望在同一划分标准或者不同划分标准中,
能够拥有这些不同的上位类的概念特征,
可以假想一下,每一个概念代表一个特征的集合,
比如上位概念A 上位概念B 以及我们要的下位概念C
不管上位概念A和上位概念B处于何种位置,
相对于A和B来说,无非是A和B有交集,A和B没有交集
A和B有交集的情况就没有任何异议了,做为多重继承,不会存在任何的二义性。
对于A和B有交集,那么A和B肯定要处于一个同一颗树里(你大约知道菱形继承为什么会存在了吧),
否则,要么这个交集将不是我们需要关注的,要么,我们的抽象出了问题。
如果交集中的特征中,型和值都一致,那么,这是最好的情况,也不会产生任何的二义性问题
因为不管是A的特征还是B的特征,都是同一个玩意儿,
如果型相同,值不一样,这就存在冲突了,那么我们需要考虑,
到底是采用A的值,还是采用B的值,
还是需要自己赋以新的值才能符合要求。
(如果型不同...天啊,那不可能...)我们期望不做任何的改动就能将冲突部分解决,这是不现实的,
因为我们没有给编译器足够的信息,即使有这个信息也不知道如何传递给编译器,
只能手工来解决。当然,如果在问题域内,通过巧妙的设计,还是能避免二义性的问题的。
如果能达到这样一点,那将是最大程度的发挥多重继承的效用。
如果我们期待能够完全利用父类的所有特征,
而在用的过程又出现了scott meyers的所举的这个例子,
那就可能出现了两个问题:
第一,在该问题域内,某些层面的类划分标准并不清晰,导致冲突出现。
第二,层次抽象并不合理,我们可能对父类有太多的期望,加入了太多的内涵。尽管scott meyers是大师,但是思考是属于我自己的,我的想法不一定对,
但目前我还没有能说服自己放弃自己的想法。不是狂妄,而是认真。
今天酒有点喝多了,姑且妄论之。to be continue...
----------------------------------------------------------
我想我并没有要求子类必须overide virtual函数,
我只是根据我的要求显式的指定
draw函数的实现。
之所以这么干,是因为我确定了如何手工解决二义性。
另:java里面的函数都类似virtual函数
不知道c#是不是这样。2、如果两个父类同名函数有几十个怎么办 (//这里只是研究语言,不要讲设计不合理,因为有可能父类是不同的人设计的)
-----------------------------------------------------------
那种情况你就要考虑设计是否合理,如果是一个拙劣的抽象导致的丑陋的设计。
你需要重构而不是将就。
另外,为什么要将设计层的问题放到语言层面来解决呢?
父亲则不同...一个人必须是男人而且还要与某个女人通过性行为后由这个女人经历怀孕、生产等过程之后才能够被其子代称为父亲...所以说它应该是一个需要实现的接口...如果将其作为一个可继承的根去理解那简直是滑稽...
毕竟C#是更偏向设计架构层次的语言,多继承类虽然有好处,但是MS觉得这种习惯不好,就不要了.