y******3 发帖数: 3 | 1 老规矩,先解释一下标题,我承认有点标题党,其实就是为了抓人眼球,希望通过这种
标题吸引你点进来看看。我有自信这是一篇好文章。我曾经写过一篇很有名的博客:《
为什么C语言不会过时》 很多的网络媒体都进行了转载。我相信这篇博客的质量应该比
《为什么C语言不会过时》还好。因为它会颠覆你对C++面向对象编程的传统的认知。
再详细解释一下什么是非耍流氓。其实C++面向对象编程的问题,很多大牛都讨论过,
但是因为人家是大牛,所以文字描述居多,很少给出代码。人家毕竟是阅近天下**,
心中早已无码。我个人认为讨论编程的问题,如果你不给出代码就是在耍流氓。 而一
些C++初学者,倒是愿意给出一大堆关于什么dog继承animal的类似的源代码,但是这样
讨论面向对象编程有些肤浅,片面,甚至是把人带向了错误的方向。所以我这篇文章首
先要基于源代码的方式讨论面向对象,而且介绍现代方式的面向对象编程,你会惊讶的
发现,无论是从理念上,还是实现方式上,它都和传统意义的面向对象编程截然不同。
我刚开始接触C++的面向对象的时候,首先接触的例子就是duck是animal, student是
一个people等等那一套。这种例子铺天盖地,充斥于各种C++的各种初级书本中。我相
信大部分人和我都有一样的记忆。当时感觉这个面向貌似没什么难的。但是当自己写程
序的时候,大部分时间都根本没怎么使用到继承。这个是第一个层次。
Class Animal{
Virtual Speak() = 0;
}
Class Duck: Animal{
Virtual Speak(){cout<<"quack"<
}
慢慢地开始接触设计模式,这个就有点难度了。这里没有什么鸭子,学生之类的东西了
。基本上都是一些很抽象的东西。比如说,策略是一个基类,命令是一个基类等等。当
时看的云山雾罩,不得所以。只是机械的记住几个名字,几个类关系图而已。
再慢慢地,开始意识到一些本质的东西。继承是面向对象编程的核心实现方式,其思想
本质是把变化的东西封装到子类中,而客户只针对一个基类的指针来调用:
Class Base{
Virtual Do{}= 0
};
Class Implement1: Base{
Do(){do it in this way};
}
Class Implement2: Base{
Do(){do it in thatway};
}
//Client code
Base* Ibase;
Ibase->do(); //
如果你仔细研究设计模式,尤其是建造模式和行为模式,你会发现它们都符合这个基本
的想法。例如工厂方法模式,我们把Do 函数换成Factorymethod方法就OK了,换句话说
,我们把对象生成方法的变化下推到子类中,使得这种变化不会影响到客户的代码。再
举了例子,策略模式,你把Do函数换成AlgorithmInterface()就可以了。我们把算法的
变化封装到不同的子类中。当运行的时候调用不同的子类,客户端的代码也不受影响。
顺便说一句,这种思想后面还有两个非常凡尔塞的原则。一个是里氏替换原则:如果S
是T的子类型,则类型T的对象可以用类型S的对象替换(即类型T的对象可以用子类型S
的任何对象替换(即向上转换))而不更改程序的任何期望属性(正确性,任务执行等
)。其实就是我们能用Implement2替换掉IBase, 客户的程序不受影响。另外一个就是
反向依赖原则:高层模块不应依赖低层模块,低层模块也不应依赖高层模块,两个都应
该依赖抽象层。抽象不应该依赖细节,细节应该依赖抽象。 这个其实更简单, 换成人
话就是一旦Do()这个玩意定下来,客户就去调用它,子类就去实现它。换句话说,客户
不依赖于子类了,而是客户和子类都依赖于Do()这个玩意。这里很多人把Do()这个玩意
也叫做接口,抽象,基类,契约等等。 看明白了吗?其实这两个原则其实说的都是一
回事。
如果上面我说的你都明白,那么我恭喜你。你对传统意义的面向对象编程已经基本了解
了,而且23个设计模式对你来说就不那么难懂了。
在传统的面向对象编程中,你会发现另外的两个问题,1) 什么应该是基类?2)如果
基类需要变化怎么办? 而恰巧正是这两点,直接击中了传统面向对象编程的痛点。让
我们一个一个说。 首先什么应该是基类?关于这个问题,C++面向对象里面有一个经典
的案例,那就是:椭圆应不应该是圆的基类。我这里只是简单的介绍一下,详细的讨论
可以参考后面的参考文献1。 长话短说,欧几里得认为圆形就是椭圆,但是C++里面你
却不能这么用,这是因为椭圆有两个圆心,但是圆却只有一个圆心。 没有办法直接使
用继承。请看源代码;如果使用了继承,你会发现圆这个类有三个圆心。而且
setLongRadius在圆形里面也没什么实际的意义。这明显违背了里氏替换原则。
Class ellipse{
setLongRadius(float);
setShortRadius(float);
pair center1;
pair center2;
}
Class circle{
setRadius(float);
pair center;
}
问题出在哪里,到底是欧几里得错了? 还是Bjarne Stroustrup(C++之父)错了?大
部分人的第一反应都是一定是我错了。然后针对这个问题,层出不穷的解决方案就出来
了。但是无非就三种方法,让基类变弱,让子类变强。或者再建造一个更抽象的shape
基类,然后让ellipse和circle都继承于它。但是无论是哪种方法,都相当的别扭和不
自然。
如果如何在开始的时候设计类继承体系已经很成问题了,那么未来的变化更是大麻烦。
这里有另外一个例子。比如我们现在设计了一个面向对象的继承体系如下图:
Class Animal{
walk();
speak();
}
现在People和Dog继承于它。 貌似非常地完美,没有任何问题。但是两个月以后,我们
需要新加入一个类鲨鱼。问题就来了。鱼能继承于Animal吗?不能,因为调用Shark.
walk()的方法没有办法解释。但是鱼明明就是一种Animal吗?没关系,面向对象是不会
错的,C++语言也是没错的,只是我分类的方法不对。我应该再加入一个哺乳动物类,
鱼类,然后把swim这个行为放到鱼这个类中。看着不错,虽然改变类体系这种事情会对
现有的系统带来巨大的冲击和影响(几乎所有过去的代码需要改写和重新编译。)但是
你相信现在的分类是完美的。但是两个月以后,鲸鱼来了。没关系,我们可以使用双继
承去解决这个问题。(一旦使用了双继承,基本上就是代表你的设计需要一个补丁了。
)鲸鱼同时继承哺乳动物类和鱼类,也算暂时过关了。但是两个月以后,鸭子来了。这
个时候,我估计你快疯了。这个能walk, speak 和swim的东西到底应该放到哪里呢?真
没想到,你会被一个鸭子伤害到。 其实你不是个例。 Linux之父炮轰过C++,指出任何
现在看起来伟大,光明正确的类设计,两年以后都会有各种问题,那个时候修复起来成
本很大,因为整个的系统实现都是基于现有的类设计架构,非常难于修改。同时继承关
系是一种很强的耦合关系,这种强耦合使得整个程序变成了一大坨,牵一发而动全身。
通过我们上面这个类似玩具的例子,我希望你能对这句话有所体会。
传统面向对象编程(我是指那种上来就去设计复杂的类的分类和继承体系)的问题在于
把人类日常科学的分类方法照搬到编程领域,这是不对的。再详细点说,is-a关系并不
能完美地适用于编程领域,is-substituable(可替代关系)更适合。在Python语言中,
这种思想得到了贯彻和体现。Python语言中有著名的(duck type)鸭子类型。只要你叫
起来象鸭子,走路像鸭子,那么你就可以替代鸭子。 至于你本身是什么,我不care。
我们再论is-substituable(可替代关系),现代面向对象的本质到底是什么呢?是构
造一个分类准确,漂亮并且复杂的分类体系吗?当然不是!编程的最终目的就是完成一
个任务,这个任务需要多个对象配合,换句话说就是各个对象能够彼此发送消息(A对
象给B对象发消息就是A对象调用B对象的某个方法。)现代面向对象编程的本质就是轻
对象分类体系(少用继承),而重视可替代关系的表达和实现。有了这种可替代关系的
加持,对象之间就能更好的彼此合作(对象间调用彼此方法),从而更好地完成一个任
务。
一旦我们把is-a关系换成is-substituable(可替换关系),我们就完成了面向对象编
程部分的思想改造,但是不要忘记,C++是一门静态语言,不是动态语言。就语言技术
本身,我们还不能像Python语言那样直接地支持动态数据类型(鸭子类型)。 但是感
谢C++的generic编程。这给现代C++语言打开了一扇新的大门,使得我们不用使用继承
也能支持泛型和多态(编译期间的)。不仅如此,现代的C++语言对泛型的支持更安全
,对多态的支持更高效。可以这么说,C++11以后,尤其是C++/14/17/20的不断进步和
演化,为我们的现代面向对象编程提供了强大的语言技术支持,使得我们完成了现代面
向对象编程部分的工具改造。
思想和工具都改造完成,我们该上码了。这里我们对一个形状求面积。 我会首先给出
基于传统面向对象编程的实现,然后再给出现代面向对象编程的实现。
首先是设计一个形状的基类, 然后派生出三角形, 椭圆,圆形等等。
Class Shape{
virtual getArea() = 0;
}
Class Rectangle: public Shape{
setLength(float);
setHight(float);
virtual getArea(){return length* hight;}
}
Class Triangle: publich Shape{
....
}
float getShapeArea(Shape* ptr_shape){
return ptr_shape->getArea();
}
代码相当标准和传统,这里不过多解释。请注意,使用传统的面向对象编程,你就会遇
到上面我们讨论的所有的面向对象的问题。圆形是不是椭圆?点是不是个形状等等。
好吧,现在我们用现代面向对象编程的思路去解决这个问题。看下面的代码:
Template
float getTArea(T t){
return t.getArea();
}
请注意,并不是使用模板了就实现现代化了,而是这段代码背后的思想。现在我们已经
不需要T一定是Shape类型了。相反地,只要在计算面积这个事情上,某个类型T有可替
换性,那么它就可以上。 注意到没,我们再也不需要关注椭圆是不是圆形这个问题了
。而且以后就算是出现了3D的对象,只要它在计算面积这个事情上有可替换性,那么我
们的代码就不需要更改,你只需要不断的加入新类就行了。就这样。
比起Shape基类,T也是类型安全的。因为在C++编译并且实例化这个函数模板的时候,
编译器会检查实例化的T类型支不支持getArea()这个函数。在C++20中,我们更是引入
了concept的概念。使得对类型的静态检查变得更加简单和方便。
template
concept supportArea = requires(T v)
{
v.getArea()
};
Template //supportArea is concept
float getTArea(T t){
return t.getArea();
}
不仅不再需要纠结圆形是不是椭圆这个问题了,我们还可以在可替换性上再前进一步。
你想得到面积,我给你面积就得了,至于我怎么算的,你不用担心。并不是我有
getArea()函数才有可替代性,而是我把面积返还给你我就有可替代性。假设有个圆形
,我们想求面积,但是目前圆形中还没有实现getArea()方法,没关系,我们可以用一
个矩形替换圆形来计算面积。这个理解起来很简单。我们可以把一个圆形转换成一个矩
形,矩形的长就是PI*R, 高就是R。 这样长乘以高正好就是
pi*R*R。
有了现代C++语言特性,实现起来也不难。参看源代码:
Template
float getTArea(T t){
if constexpr(Is_Circle::value){
auto getArea = [](auto& t){
Rectangle r;
r.setLengh(pi*t.Radius);
r.setHigth(t.Radius);
return r.getArea();
};
return getArea();
}
else{
return t.getArea();
}
}
这段代码用到了很多的C++14/17的特性,比如if constexpr,lambda和 type_trait等
等。限于篇幅我不多解释了。这里简单介绍一下思想:首先构建一个Is_Circle
type_trait类。然后判断T是否是一个圆形,如果是,那么构建一个矩形,然后把矩形
的长设置为pi*r,把高度设置为r。然后利用矩形的求面积公式计算圆形的面积。如果
不是圆形,那就直接调用类型T自己的getArea。
也就是说,在求面积这件事上,只要在语义层面上满足可替换性,那么一个圆甚至可以
被一个矩形所替代,而且我们也彻底摆脱了有没有getArea()函数这种语法层次的的限
制。怎么样,够酷吗?
如果这个例子你读懂了,一次类似的例子还可以阅读参考文献2《Linux多线程服务端编
程》。第一章里面有一个使用variadic template实现Observer设计模式的例子。书中
的一个观点令我印象很深:设计复杂的分类和继承体系就是“叠床架屋”,不如直接基
于对象编程(不使用继承,直接使用各种对象彼此协作),这样才能“拳拳到肉”。我
深以为然。
这里必须要再提一句,可替代性的度量不仅依赖于某个函数本身的语法和语义限制,它
还依赖于调用这个函数的前后条件的限制。也就是pre-condition和post-condition也
要同时满足客户的要求。这么说有点抽象,再举个例子,现在有两个鸭子类,调用一个
鸭子类的speak方法,它返回“quack”,调用另外一个鸭子类的speak方法,它返回“
姐,好久没来了!”。单从speak方法本身,语法和语义都貌似符合可替代性。但是很
明显,客户对调用speak方法后的post-codition的期待是完全不一样的。所以在这种情
况下,貌似完美的可替代性是不成立的。
最后总结一下:
1)现代的面向对象设计并不是基于Is-a关系设计复杂的类继承体系。而是基于可替代
关系构建一个对象间能够高效彼此协作的系统。
2) 可替代关系的理解是基于语义的,而不是基于语法的。例如有的时候语法上不可替
代,但是语义上可替代就行。例如圆形即使没有getArea函数,它也可以被矩形替代(
在计算面积的时候)。而有的时候,即使语法上完全可以替代,但是postcondition不
满足,也不满足替代关系。例如两种不同的鸭子。
3)利用现代C++的语言特性,尤其是基于generic programming语言特性实现上面介绍
的基于可替代关系的设计意图。
4)现代面向对象设计并不完全排斥对象,那种没有附加成本的基于数据抽象的对象还
是推荐的。例如把一个点对象和数值对象抽象成一个圆形是推荐的。这种对象是更方便
我们编程,而且没有任何附加成本。我们反对的是设计复杂的类分类和继承体系。
参考文献:
Inheritance — Proper Inheritance and Substitutability, C++ FAQ (isocpp.org)
Linux多线程服务端编程 (豆瓣) (douban.com) |
h**********c 发帖数: 4120 | 2 你这个我觉得还是你大型项目干的少,面向对象一个重要概念你没有讨论。我看的不太
细。太长了。
比如namespace吧。
我这两天看一个放屁程序。在function里有一个JSON,结果this指的是json objec. 那
么function的json 里的funcition 里的json,...怎么指带倒数第二层的 declaration
。整个一个错乱。估计会和赵高一样上史书的。LOL,拉的不要在惜。
当然书就是disrupt 一下。
至于什么access restriction。
我说大型项目主要是说代码重用比较多,这个相当于数学书某定理被重复引用,那么
lemma 3.2.1 基本就是最简洁的字符表达。
【在 y******3 的大作中提到】 : 老规矩,先解释一下标题,我承认有点标题党,其实就是为了抓人眼球,希望通过这种 : 标题吸引你点进来看看。我有自信这是一篇好文章。我曾经写过一篇很有名的博客:《 : 为什么C语言不会过时》 很多的网络媒体都进行了转载。我相信这篇博客的质量应该比 : 《为什么C语言不会过时》还好。因为它会颠覆你对C++面向对象编程的传统的认知。 : 再详细解释一下什么是非耍流氓。其实C++面向对象编程的问题,很多大牛都讨论过, : 但是因为人家是大牛,所以文字描述居多,很少给出代码。人家毕竟是阅近天下**, : 心中早已无码。我个人认为讨论编程的问题,如果你不给出代码就是在耍流氓。 而一 : 些C++初学者,倒是愿意给出一大堆关于什么dog继承animal的类似的源代码,但是这样 : 讨论面向对象编程有些肤浅,片面,甚至是把人带向了错误的方向。所以我这篇文章首 : 先要基于源代码的方式讨论面向对象,而且介绍现代方式的面向对象编程,你会惊讶的
|
h**l 发帖数: 168 | 3 简单说就是要用OBP,不要用OOP.
encapsulation是好东西。inheritance能够帮助reuse code.
polymorphism, virtual 都是垃圾,尽量不要用。
【在 y******3 的大作中提到】 : 老规矩,先解释一下标题,我承认有点标题党,其实就是为了抓人眼球,希望通过这种 : 标题吸引你点进来看看。我有自信这是一篇好文章。我曾经写过一篇很有名的博客:《 : 为什么C语言不会过时》 很多的网络媒体都进行了转载。我相信这篇博客的质量应该比 : 《为什么C语言不会过时》还好。因为它会颠覆你对C++面向对象编程的传统的认知。 : 再详细解释一下什么是非耍流氓。其实C++面向对象编程的问题,很多大牛都讨论过, : 但是因为人家是大牛,所以文字描述居多,很少给出代码。人家毕竟是阅近天下**, : 心中早已无码。我个人认为讨论编程的问题,如果你不给出代码就是在耍流氓。 而一 : 些C++初学者,倒是愿意给出一大堆关于什么dog继承animal的类似的源代码,但是这样 : 讨论面向对象编程有些肤浅,片面,甚至是把人带向了错误的方向。所以我这篇文章首 : 先要基于源代码的方式讨论面向对象,而且介绍现代方式的面向对象编程,你会惊讶的
|
m*****n 发帖数: 3575 | 4 那不就是golang的设计思想吗?
弱化类,把类丢进struct的纯静态数据
而以interface为纲,interface的组织原则是能应用某些函数。
【在 h**l 的大作中提到】 : 简单说就是要用OBP,不要用OOP. : encapsulation是好东西。inheritance能够帮助reuse code. : polymorphism, virtual 都是垃圾,尽量不要用。
|
n******t 发帖数: 4406 | 5 polymorphism, virtual這些概念當然其實是有用的,你的文件系統,還有device
driver都是用了這些概念搞出來的東西。問題是這些概念,如果不是對計算機有native
層次理解的人,很容易越用越high,變成了不顧具體情況的套用,最後就成了看起來牛
逼其實傻逼的東西。
用Object組織code本身沒有問題,但是有很多計算機概念和object並沒有什麼直接關係
,這個時候一定要用object這個框架去套這些東西,表示自己的framework無所不能,
就是事倍功半。這也是所有這些什麼OOP語言,一寫網絡程序就現原型的原因。因爲
communication和object就沒什麼關係,這也是爲啥只用oop編程的人一般never會知道
怎麼設計協議,成天只知道怎麼serialization,deserialization然後用whatever語言
支持的庫把東西給扔出去的原因:並且因爲實現一個協議是如此困難,所以mostly like
這個協議就是http。
CS這個行業最大最大的問題就在於,咬人的狗不叫,不會咬人的狗特別喜歡叫。具體就
是一個技術概念,真正用好的人就直接寫軟件去了,沒工夫成天叫。最喜歡叫的人都是
腦子不適合和機器打交道,但是特別喜歡把自己學到或者“參悟到”的概念一及其垃圾
的辦法實現了,並且到處販賣的人,這種人一般都成了evangelist,到處誤人子弟。C+
+,Java,這些都是這樣,這裏面還包括了docker,k8s這些東西。
【在 h**l 的大作中提到】 : 简单说就是要用OBP,不要用OOP. : encapsulation是好东西。inheritance能够帮助reuse code. : polymorphism, virtual 都是垃圾,尽量不要用。
|
d*******r 发帖数: 3299 | 6 差不多是这样
native
like
【在 n******t 的大作中提到】 : polymorphism, virtual這些概念當然其實是有用的,你的文件系統,還有device : driver都是用了這些概念搞出來的東西。問題是這些概念,如果不是對計算機有native : 層次理解的人,很容易越用越high,變成了不顧具體情況的套用,最後就成了看起來牛 : 逼其實傻逼的東西。 : 用Object組織code本身沒有問題,但是有很多計算機概念和object並沒有什麼直接關係 : ,這個時候一定要用object這個框架去套這些東西,表示自己的framework無所不能, : 就是事倍功半。這也是所有這些什麼OOP語言,一寫網絡程序就現原型的原因。因爲 : communication和object就沒什麼關係,這也是爲啥只用oop編程的人一般never會知道 : 怎麼設計協議,成天只知道怎麼serialization,deserialization然後用whatever語言 : 支持的庫把東西給扔出去的原因:並且因爲實現一個協議是如此困難,所以mostly like
|
Y********g 发帖数: 1 | 7 楼主的“化圆为方”也是奇葩啊。直接说不要面向对象不就完了。 |
m*****n 发帖数: 3575 | 8 请评价一下golang抛弃class的优缺点。
driver都是用了這些概念搞出來的東西。問題是這些概念,如果不是對計算機有native
層次理解的人,很容易越用越high,變成了不顧具體情況的套用,最後就成了看起來
牛逼其實傻逼的東西。
【在 n******t 的大作中提到】 : polymorphism, virtual這些概念當然其實是有用的,你的文件系統,還有device : driver都是用了這些概念搞出來的東西。問題是這些概念,如果不是對計算機有native : 層次理解的人,很容易越用越high,變成了不顧具體情況的套用,最後就成了看起來牛 : 逼其實傻逼的東西。 : 用Object組織code本身沒有問題,但是有很多計算機概念和object並沒有什麼直接關係 : ,這個時候一定要用object這個框架去套這些東西,表示自己的framework無所不能, : 就是事倍功半。這也是所有這些什麼OOP語言,一寫網絡程序就現原型的原因。因爲 : communication和object就沒什麼關係,這也是爲啥只用oop編程的人一般never會知道 : 怎麼設計協議,成天只知道怎麼serialization,deserialization然後用whatever語言 : 支持的庫把東西給扔出去的原因:並且因爲實現一個協議是如此困難,所以mostly like
|
N***e 发帖数: 61 | 9 我觉得大部分人分不清OOP中的类和现实生活中分类的类有本质的不同。导致做项目的
时候接口和实现类搞不清楚。除了OOP,不会其他方法。然后对OOP一顿狂喷。
至于Python“只要你叫起来象鸭子,走路像鸭子,那么你就可以替代鸭子“这种动态类
型,真是谁用谁知道。项目大了最后自己都搞不清楚放进去的是不是鸭子,只有赶鸭子
上架了的时候才发现大事不好。 |
n******t 发帖数: 4406 | 10 golang的struct其实就是class,但是golang不想要inheritance,因为这东西对于it的
target是没事找事。
没有inheritance的原因是,OOP还有很多别的人没搞懂很多理论概念的合理实现并不是
人的直觉实现。继承是一种描述对象关系的模型,然后很多人一听到继承脑子里面就是
父子关系,然后就parent class什么的就上来了。然后觉得这个模型很优美,就打算把
这个东西解决所有问题。
问题是,除了trivial的例子,什么car,animal之类的,大部分这个世界的对象关系并
不是什么继承关系,在两个没什么亲缘关系的物件中间,如果你需要套用继承,你就需
要人为地给他们造一个parent class出来,而且这个东西还most likely只apply这两个
class,当你再加入一个class的时候,你就需要修改所有之前类定义,这是活生生的把
O(N)的工作量改成了O(N^2)的做法。
所以除了非常明显(一般能用inheritance的,都是你不用都觉得难受的场景,比如说
窗口控件,游戏里面的精灵,一堆明显属于一类的商品),在很多时候,继承是非常糟
糕的设计,应该ban掉。
一般来说,和人越近的东西,类继承的关系会比较自然,和机器越近就越不自然。所以
为什么OOP在系统编程项目里面如此蛋疼就是这个原因。golang其实是穿了moden外衣(
GC,concurrency)的C+shell,想法就是降低C和shell的门槛,所以不要继承是完全可
以理解的。
native
【在 m*****n 的大作中提到】 : 请评价一下golang抛弃class的优缺点。 : : driver都是用了這些概念搞出來的東西。問題是這些概念,如果不是對計算機有native : 層次理解的人,很容易越用越high,變成了不顧具體情況的套用,最後就成了看起來 : 牛逼其實傻逼的東西。
|
d*******r 发帖数: 3299 | 11 狭义上的(Java/C++)OOP为啥弱鸡?
因为一层层继承定义其实是个Tree
但是世界上的依赖关系其实是个Graph
Tree对hierarchical structure 友好, 人类易懂是优点
但是很多东西难以用狭义的Tree来定义, 所以蹩脚了 |
r*g 发帖数: 3159 | 12 没看完。但椭圆为何不能是圆的基类?
在子类园里,set a=b, 另引入新成员r=a. 所有椭圆的成员和方法都不应该出现问题。 |
m*****n 发帖数: 3575 | 13 那你对interface怎么看?
我完全不理解interface是什么个东西,别的语言没有。
的target是没事找事。
是人的直觉实现。继承是一种描述对象关系的模型,然后很多人一听到继承脑子里面就
是父子关系,然后就parent class什么的就上来了。然后觉得这个模型很优美,就打算
把这个东西解决所有问题。
并不是什么继承关系,在两个没什么亲缘关系的物件中间,如果你需要套用继承,你就
需要人为地给他们造一个parent class出来,而且这个东西还most likely只apply这两
个class,当你再加入一个class的时候,你就需要修改所有之前类定义,这是活生生的把
【在 n******t 的大作中提到】 : golang的struct其实就是class,但是golang不想要inheritance,因为这东西对于it的 : target是没事找事。 : 没有inheritance的原因是,OOP还有很多别的人没搞懂很多理论概念的合理实现并不是 : 人的直觉实现。继承是一种描述对象关系的模型,然后很多人一听到继承脑子里面就是 : 父子关系,然后就parent class什么的就上来了。然后觉得这个模型很优美,就打算把 : 这个东西解决所有问题。 : 问题是,除了trivial的例子,什么car,animal之类的,大部分这个世界的对象关系并 : 不是什么继承关系,在两个没什么亲缘关系的物件中间,如果你需要套用继承,你就需 : 要人为地给他们造一个parent class出来,而且这个东西还most likely只apply这两个 : class,当你再加入一个class的时候,你就需要修改所有之前类定义,这是活生生的把
|
k****i 发帖数: 101 | 14 大白话就是,go的struct定义数据(nouns, derivative nouns)相关性,interface定义操
作或属性(verbs,adjectives)相关性,c++等的class非要二者水乳交融产生难以描述的
化学反应,强扭的瓜甜不甜真相了 |
b***i 发帖数: 3043 | 15 是的。不过C++是不是也可以通过has来实现这个,就是一个类不直接继承一个
interface,而是加入一个Interface的变量。
【在 k****i 的大作中提到】 : 大白话就是,go的struct定义数据(nouns, derivative nouns)相关性,interface定义操 : 作或属性(verbs,adjectives)相关性,c++等的class非要二者水乳交融产生难以描述的 : 化学反应,强扭的瓜甜不甜真相了
|
m*****n 发帖数: 3575 | 16 虽然还不能完全看懂你的说法,
但是这好像证明当时我强迫用go来做一些工程,具有高瞻远瞩的智慧。:D
操作或属性(verbs,adjectives)相关性,c++等的class非要二者水乳交融产生难以描述
的化学反应,强扭的瓜甜不甜真相了
【在 k****i 的大作中提到】 : 大白话就是,go的struct定义数据(nouns, derivative nouns)相关性,interface定义操 : 作或属性(verbs,adjectives)相关性,c++等的class非要二者水乳交融产生难以描述的 : 化学反应,强扭的瓜甜不甜真相了
|
k****i 发帖数: 101 | 17 C++ is a versatile beast and endorses nothing to do anything. People can
aggressively use whatever they conceive fit, and the smarties probably tend
to. OTOH, Go is so opinionated that one has to either surrenders its ego or
runs away.
【在 b***i 的大作中提到】 : 是的。不过C++是不是也可以通过has来实现这个,就是一个类不直接继承一个 : interface,而是加入一个Interface的变量。
|
f*******a 发帖数: 663 | 18 感谢分享。前端时间看到了,但没有时间细读。最近正好在做设计,于是又仔细看了一
遍。
文章很有思想,有些概念也很不错。从我个人的经验而言,还有些不同的想法,也在这
探讨一下,抛砖引玉。
在复杂系统的设计中,接口很重要。接口的设计,涉及到耦合性、复用性、扩展性、易
用性几个主要方面。设计接口就像设计插头/插座,或者是水管套接,希望低耦合、高
复用、易扩展、易理解便于使用。具体在实现时,又设计到代码的复用(减少冗余相似
代码)和封装(隐藏非必须细节给用户)。具体就不展开了。
接口都是通过函数定义的,在C/C++这种强类型检查的语言里,输入输出的参数必须明
确化,因此灵活性大受限制。基于C++的类、基于模板以及基于重载,都是解决这个问
题的一种思路。
文中提到的基于模板的方式,在我看来本质上依然可以用C++实现。只需在基类中定义
接口,但没有任何属性(即成员变量)即可。只是这样,完全不考虑代码的重用性。基
于重载也可以实现。但以这样的方式,在代码复用、扩展性(需要修改的文件、函数定
义等)和封装性(对用户暴露除接口外不必要的细节)上是有劣势的。
文中的问题很好,就是基类发生变化时怎么办。接口很难做到一成不变,但要改底层接
口那当然伤筋动骨,我想换哪种语言都是这样。所以,最初接口的设计要有前瞻性,基
于功能需求,在耦合性、复用性、扩展性、易用性反复推敲,实现细节反而不需要在这
个阶段考虑。
最后,说点具体的。最近设计的传感器类,是这样的继承关系。
ISensor
- CSensor
- CSensorCamera
- CSensorLidar
ISensor类中就是纯粹的接口,给用户看的,只有Open(), Close(), GetData()这几个
标准的接口函数(纯虚函数),没有任何成员变量。
CSensor继承了这几个函数(以虚函数的方式),共性代码自己实现(如通过网口或COM
口寻址),个性化代码在子类中自己实现。CSensor和子类对用户隐藏,只有dll。
这种方式,我觉得还是比较符合上面提到的对接口设计的预期。也欢迎大家指正。
另一个,椭圆的例子,以前也困扰了我很久,今天看到这个,又想了想,我觉得应该是
这样:
CEllipse
- CCircle
Ellipse有两个半径,所以函数
setRadius(float r1, float r2);
设计为虚函数
或者重载
setRadius(float r1, float r2);
setRadius(float r1, float r2 = 0);
在子类CCircle中对应实现即可。这样,计算面积、周长等函数的代码就可以复用了。
CEllipse中有两个关于半径a/b的成员变量,在CCircle中其实有特殊的限制,就是a=b
,这点需要注意。或者是特定操作时注意检查,或者是第2个变量不用,都可以,仔细
一些即可。
以上。供参考。 |
d******c 发帖数: 2407 | 19 再进一步到函数式编程,能进一步减少程序员构思的负担。
很多时候你需要在脑子里模拟程序的运行,函数式编程能让你模拟的任务尽量简化,思
考简单,轻松,你知道想清楚的肯定是对的,而不是一个大的黑箱,不知道什么时候可
能会出问题。
每个操作是函数,确定所有输入,所有输出是确定的。
函数式会有一些额外成本,
- 不用全局数据,需要更多copy
- 不inplace修改,需要更多copy
这些都可以通过更好的数据结构来降低额外成本。
当你组合的是函数,而不是对象时,只需要考虑函数的输入是否符合要求。 |