为什么C#舍弃了C++中的多继承而支持单一继承与多接口继承呢?
个人学习C#的疑惑,想不明白,是出于什么设计目的而改变这种继承方式的呢?
[解决办法]
二义性
ex
class a
{
public virtual int test();
}
class b
{
public virtual int test();
}
class c:a,b
{
//
overrider test();
}
c c1 =...
c1.test() //
[解决办法]
就是,像c++这种传统的OO语言,都没能很好控制多继承,
所以java,c#都已经舍弃了多继承,改用更加窄的接口
[解决办法]
多重继承太混乱了,所以要用单继承,这是C#的优点,
多接口只是定义了一些规范,这个比较深奥,不是一两句能说清楚的,可以看看设计模式
[解决办法]
其实接口可以说并不存在的二义性问题。
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() //
[解决办法]
总结来讲就是更加接近JAVA,当初MS推出C#对手就是JAVA。java和C#都是单继承多接口。多继承容易出错是程序员实践中总结来的
[解决办法]
单一继承更符合真实的世界。面向对象本身起源于实际生活。
[解决办法]
呵呵,每个语言都有自己的特点,C++中需要多实现继承的地方,C#中可能通过改变一下设计也可以达到相同的目的。C++中的多继承有好处也有坏处,灵活代码紧凑,但难写也难懂,一个选择的问题。或许是考虑降低C#编程的门槛,许就是当时制定语言规范的那个小组坐在会议室里一投票,ok就这么着了……
[解决办法]
多继承较之多接口更难合理的应用,这个是为什么除了C++和Effel提供这个东西,其他的OOP语言都不提供多继承。
个人感觉是简单的多继承很容易控制,不会犯错,但是复杂的情况下面多继承很难控制,容易出错——很多时候都不知道类型怎么设计才合理。相比之下多接口的设计更容易理解。
------解决方案--------------------
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() //????
[解决办法]
接口就是当初MICROSFOT提出来代替多重继承的
主要就是为了功能更加明确,接口实现的是一种契约,即要实现哪个功能
继承自接口的类必须实现这个功能
并且这个二义性会得到解决,它并没有提供实现代码,所以具体实现哪个是无所谓的(个人想法)
但是如果想具体到接口就得加上接口名了
[解决办法]
二义性的责任在程序员而不再语言本身.C#的定位是简单快速开发.所以去除一切不必要的麻烦.当初甚至想把指针都去掉.不过这样又和VB有何区别了呢?对了,最大的区别就是有了.Net Framework.其实世界上99%的程序员于其说选择语言归根到底其实是选择类库或API库.(有些跑题了)
[解决办法]
是为了用户考虑的,因为现在编程的门槛太低了,愚蠢的人太多了,没有办法。
多继承是最贴近现实的描述的,也是最省力的。比如现实中一个人可能有父亲,教师,儿子,老公,情人等等多种角色,多继承能够恰如其分的描述这样一点。
采用接口的方式,每一个指定的东西都要不厌其烦得再实现一次,累不累啊?
虽然采用多继承会导致混乱,但是这取决于现实中的模型的复杂程度和模型的建构者的功力。
[解决办法]
改多重继承为接口一方面为了降低编程难度 有利于垃圾回收
[解决办法]
单一继承更符合真实的世界。面向对象本身起源于实际生活。
如果LS两位只是为了这么一句话而争论的话,就显得太过于无聊了。
到底是多重继承还是单链继承更符合现实世界这本来就是一个没有意义的问题,就像我们不是因为面向对象更符合我们真实的世界才选择面向对象的一样。说面向对象是从现实世界中抽象而成的理论我想在任何一个资深的OO程序员来说都知道这只不过是把OO变得玄之又玄的新手入门教材而已。
OO能够抽象我们的程序,提高代码复用率,降低耦合度,使得程序可伸缩性更佳,这些是我们选择OO的原因,而不是因为我们把我们的家抽象成了一个Object然后从日常生活中脑子一拍想出了程序。
[解决办法]
这点我还是有自信的,比拼design,我也不惧大多数,因为我困惑过,思考过,
并找到理论依据。
我有两年半的青春是花在java上面的,说实话,尽管java中接口满天飞,
我对这种方式没有什么好感,(没用过c#,不知道它和java的方式有什么不一样)
缺点我已经说了,设计复用性相对较差(当然是拿优良的java设计和优良的cpp设计来说的)
代替的解决方案缺点也很多,惟一可以带点说服力的是垃圾回收。
但是一个良好的设计是可以稍微花费一点开销就能够取得内存自动管理,
在开发效率和运行效率之间取得了最高的比率。
这方面具体的例子可以参考geant4,一个开源的空间物理计算引擎,
里面用到了很多cpp的oo设计技巧,很优雅,没事干的可以下来玩玩。
so,我说单继承+接口更适合愚蠢的人用,这话并不过份。
语言,思想,不过是我们描述世界的一种工具而已,
不能简单地对面向对象是否真的合适描述我们的现实世界这样的问题下一个对或者错的结论。
刀剑有刀剑的用处,枪炮有枪炮的妙用,但任何工具都有合适使用的范围。
这个使用范围就是我们的软件设计里面的问题域。
关于面向对象,本公子今天比较无聊,顺便忽悠一番。
在很久以前,有个家伙叫图灵,为了证明可计算问题是真的可以求解的,
造出了一个叫图灵机的玩意儿(计算模型),同时还催生了叫算法的玩意儿,
这是计算理论里面的可计算性和计算复杂度的内容。
(其实,同时代的还有邱奇的lambda算子,和一个忘了名字的人提出的递归函数,)
这些内容为形式演算奠定了理论基础,使自动计算成为可能。
后来,有个叫冯诺依曼的家伙,雇佣了300名女工为原子弹的扩散影响范围计算了几个月后,
非常苦恼,偶遇了一个军方人士后,在图灵机的模型上加上了可存储单元(内存)
捣鼓了一个可以自动计算的机器。后来慢慢的演化成现在计算机。
在最初的岁月里面,人们的工作重点是把自然语言里面表达的思想影射到机器可以识别的语言里面,以便可以驱使机器进行自动化的计算。繁琐的机器语言让人头晕脑胀,于是有人为了便于记忆
使用了些助记符号,于是产生了一个叫汇编代替品。
汇编语言提供的描述点是寄存器,移动,寻址,压栈,入栈,算数运算,位运算,调用
等描述点,提供的机制几乎是靠硬件实现的,如保护模式,段页式等。
so,适合汇编的问题域是关于机器操纵的。
在这种细粒度的语言描述下,要来描述现实中的模型,差距太大,
组合爆炸使其就像用一堆细胞来描述一个人体一样恐怖。
so,基本上汇编只用来描述最关键性的东西,
从现实中提出去需要的计算,转换成机器操作是机器语言程序员工作。
在经过大量的使用后,人们发现在描述任务的步骤地时候,出现了一些稳定的特征,
例如有条件跳转的几种固定模式,循环的几种用法,局部变量,代码块(函数),
这些特征经过适当的封装,形成了新的描述点,同时涌现出了顺序,分支,循环,递归等流程机制,
于是便出现了过程化语言,它将任务的描述从机器层面解脱出来,扩大到面向过程式任务步骤层面,这时候类似描述一个人体就用躯干四肢和头来表达,
描述的粒度开始变大,直接影响了可描述任务的复杂度和规模。于是导致了c璀璨了近40年,
并在系统编程领域称霸,描述任务,它再合适不过了。
从现实模型中提取出需要的任务描述,是过程化语言程序员工作。
当问题域涉及描述一个系统的时候,
过程式的语言就显得力不从心,就像描述一个公司系统内部的聚餐的时候,
用一堆堆躯干四肢和头和嘴如何移动来表现一样,虽然在有限的范围内,它也可能
描述的清楚,当容易转移人们的焦点,人们更关注的是系统的组,系统的状态,
系统的交互行为,系统的协作程度,而不是某一部分是如何移动的。
这迫使新的语言要提供一个更大的描述点,能够更贴近我们的生活模型,
我们的生活抽象出来的模型,是由一些系列的概念,以及概念的交互构成的。
因此oo应运而生,虽然很多人理解这个语言中类的概念是数据+函数,
但是忽略了有机构成几个字,不知道其中某些东西已经发生了质的变化,
就像木偶也是有四肢+躯干+头构成,但是却无法拥有生命,
oo,正是那个像木偶吹了口仙气的上帝,以封装,继承,多态等机制赋予类一个活的概念。
恩,先到此为止,有事外出,下次再忽悠。
[解决办法]
额..这个和面象对象编程有关吧。
比如说人。。只会有一个父亲一样。。 但是他可能会有多个人的行为习惯。。~
抽象到程序里来就是单一继承和多接口实现。。
[解决办法]
C#面向的使用者和应用层次不同
无论那一种语言,都必须考虑到使用的环境和语境
[解决办法]
单继承更容易理解,要不然哪里来这么多人看csdn
[解决办法]
上面的争论跑远了,多接口确实比多继承清楚,LZ自己写个简单的控制台程序看看就明白了
至于哪种好,更符合实际-----sjjf兄写的太长了,有没有中心句什么的放在段首啊,我看的头晕,就看了一贴,相当有文采,能忽悠。
(其实各人对世界的认知都是不同的,有毛线争的,我自认还没上升到那个理论高度,能很好的实现我就OK了~,也许再过个5年我能想想这问题)
[解决办法]
sjjf兄写的太长了,有没有中心句什么的放在段首啊,我看的头晕,就看了一贴,相当有文采,能忽悠
-------------------------------------------
本来就在乱弹,何须中心?
继续昨天的忽悠。
在讨论oo之前,让我们回头看看我们人类的一些基本认知。
以下观点纯属个人的扯淡,如有雷同,纯属偶合 :)
几百万年之前,我们的祖先茹毛饮血,所见所闻也基本是口舌相传,
知识的积累基本是新增和流失相对比例不会变化太大,
也许新增比流失总是要多一点点,
我认为在一段时期内,知识的新增和流失比例不会变化太多,
因为人脑的记忆和检索是有限制的。
这种状况直到其中某一个聪明的祖先学会了外物作为载体进行知识存储。
例如木绳记事。这将使知识得以脱离口舌进行传承。
从此在一个部落内,如果知识存储规则明确后,知识的流失量几乎成0。
虽然知识已存储,但是受限于部落规则的制约,成为信息孤岛。
知识孤岛之间不能流通和融合。可以想象一下当时的格局,
在苍茫的大地上,遍布了大大小小的原始部落,这些部落之间都有自己的知识储备,
就像星星的火种,一点一点的顽强的闪着,随时都可能会随着部落的消亡而熄灭。
随着进化,这些部落慢慢的学会了沟通,慢慢的在局部地区,语言开始有了交集
或者开始了统一。为部落之间的规则的流通提供了可能。
直到区域的部落之间统一的文字,规则的流通才畅通无阻,解读了规则,
也就能使知识融入在一起,
从此,知识在区域之间开始了雪球效应,那些星星之火终成燎原之势。
发展到今天,已走向全球的文字和语言的统一,虽然不是形式上的统一,
但是至少语言之间的同构关系已经建设起来,虽然没有全能的地球语翻译机,
但各种语言之间大体已经能够互相翻译了。
人类在走过几千年的发展路程中,所见,所闻,所思,所说成淀下来的知识量日益庞大,
不知道从何时起,知识量已经超过了人脑的处理极限,一个人要想读完自己的国家的博物馆的
所有书,估计也要穷其半生或者一生。
人类的大脑在庞大的知识库面前开始感觉掌控无力,
这是便开始研究大知识量下的掌控方法,这些方法无非也是在知识的量和深度方面做文章。
在深度背后探索知识的规律,在量方面采用更有效的知识管理手段,
例如牛顿三大定律使得宏观世界的运动得到统一,让人们抛弃了纷乱复杂的运动现象,只需记住简单的但抽象的物理定律,这是知识深度的拓展,
在知识管理的工具方面计算机技术的发展,使得人类在信息量的掌控方面得到了极大的扩展。
在知识管理的方法方面也发展出了分类理论,文献检索技术等
其中分类理论是我们所要关注的。
分类理论从现实中提取出具体事物的抽象描述(也就是俗称的概念),以图的方式(主要是树)
展现了概念与概念之间的关系,为我们描绘了世界认知的静态框架图。
oo借鉴了这种描述世界的方式,这些描述方式都是有数学基础支撑的,
例如类的划分标准是集合论里面的等价关系,泛化是一种偏序关系等等等。
有人发表了一篇论文叫 面向对象方法学理论基础 这本书讲的比我要好多了,就不累述了。
uml与模式应用一书中忽悠我们在提炼类的概念的时候,其中有一种方法是借鉴事实。
这也是有道理的。
提出了类的概念出来后,在构建一个类的体系的时候,有三种策略:自顶向下的演绎法
自下向上的归纳法,还有两者兼有之的混合法,
实际上如果和机器学习比较一下,你会发现这两者采用的策略竟是如此雷同。
我们在用oo分析与设计来描述我们的需求时,
不过是在大脑中虚拟了一种机器从另一个视角来重新认识我们的世界。
[解决办法]
ec++ 43中 scott meyers 关于多继承的论述,我并不尽赞同。
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......
[解决办法]
有些人说:“有些人之所以被称为高手,是因为他们在思想上走的更远。”
那么---水晶剑锋.林公子---足已被称为高手了,就算不是高手,至少也是一个“思想者”。
因为他考虑问题比一般人站的都高,看问题比一般人更客观,对事物认识的比一般人更深。
[解决办法]
[解决办法]
class AuxLottery: public Lottery {
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...
[解决办法]
1、一个类从另一个类派生除了纯虚函数,没有任何要求子类必须override它的virtual函数
----------------------
我想我并没有要求子类必须overide virtual函数,
我只是根据我的要求显式的指定
draw函数的实现。
之所以这么干,是因为我确定了如何手工解决二义性。
另:java里面的函数都类似virtual函数
不知道c#是不是这样。
2、如果两个父类同名函数有几十个怎么办 (//这里只是研究语言,不要讲设计不合理,因为有可能父类是不同的人设计的)
-----------------------
那种情况你就要考虑设计是否合理,如果是一个拙劣的抽象导致的丑陋的设计。
你需要重构而不是将就。
另外,为什么要将设计层的问题放到语言层面来解决呢?
[解决办法]
菜鸟,初出江湖,看大侠们论战。招式一概不懂,一样看的有味。
我不知道儿子和父亲有何本质的区别,请告诉我本质性的区别。
---------
这个世界上只要是男人就是儿子...但父亲必须要有实现...不是每个男人都一定是父亲也不是每个男人都一定会成为父亲...你不会连这点常识都不知道吧...
--------
仅从概念上理解,技术上的我不懂。本质区别,我理解的不深刻。“这个世界上只要是男人就是儿子”这句话的表达的意思应该指的是儿子的继承特性,儿子上面肯定有父亲的存在。在程序中,父亲是可以没有儿子的角色的,上帝也就是程序员创造了他,也就是说在程序中,父亲是可以有不继承这一特性的,所以,在程序中,通过继承这一特性,能区别出父亲儿子。但是在现实中的概念,父亲肯定也会是儿子,在整个时间段上是连续的,但是你找不到起点,你找不到初始的父亲,通过继承特性就区分不出父亲儿子的区别,父亲也是儿子。所以,想到这里有点混乱。
[解决办法]
发完不能改啊。改句话。“也就是说在程序中,父亲是可以有不继承这一特性的”改为“也就是说在程序中,父亲是可以有无继承这一特性的”