« 捣糨糊 | 返回首页 | 用扑克牌来玩 Condottiere »

关于 getter 和 setter

网友 "sjinny" 在上篇评论里写:

云风对那种所有成员数据都写setter/getter的做法有什么看法吗……这两天试图精简三个太庞大的类,但是单单setter/getter就让接口数目变得非常多了……

我谈谈我的看法吧。

首先,几乎任何设计问题都没有标准答案。如果有,就不需要人来做这件事了。无论多复杂的事情,只要你能定义出精确的解决方案,总可以用机器帮你实现。

下面谈谈我的大体设计原则。记住、一切皆有例外,但这里少谈例外。因为这涉及更复杂的衡量标准。

KISS 当然是首要原则。但有许多诠释角度,每个设计师眼中都有自己的 KISS 原则。

今天的我认为,我们应该尽量少提供新的概念。所以,如果你用 C 就尽量不要用函数指针数组去模拟虚表;如果你用 C++ 就别想着用模板之类的东西弄出个“属性”的概念出来…… 这些语言原本不提供的东西,对于用户(可能是你的队友、可能是今后的你自己,可能是你未来的继任者)就是新的东西。

大部分情况下,设计一个所谓框架,也是新东西。限制用户以一定的规范来编写程序,最合适的是在语言级、而且是大家都熟知的并成熟的语言特性。

我们应该坚信:简洁优良的设计一定是和语言工具无关的。优雅的接口设计,总可以以简单的方式表达出来。

第一件事情,就是寻找你选择的开发语言的惯例。因为,如果一个语言足够成熟,抽象化的需求一定有无数人遇到过,好的方案会经过时间的洗练留下来;不用我们重新发明。setter/getter 这种需求莫如是。

最近几年,我用的比较多的是 C 语言。C 语言的惯例是什么?C 语言因为 Unix 而生,并是 Unix 的原生开发语言。我们从 Unix 的接口中寻找答案。

举个大家都熟悉的例子:getsockopt / setsockopt 。几乎是一样的需求:向一个对象读取或设置某一属性值。

传统上,C 语言构建造的系统中较少为每个属性值分别留下两个接口(读/写)。对一个对象的内部状态的修改,一般会用统一的一对 API 去操控。

少即是多。


第二要点是效率。

不考虑效率的程序员不是好程序员。这是我的个人观点。可能有些老程序员不会同意,他们会苦口婆心的教导新人:性能并不总是那么重要,为了性能,你会失去很多东西,当你剩下了“性能”后,最后,还是会失去它。

在我学会编程的头十年里,我疯狂的追求速度。读了大量的书、写了大量的代码。小心翼翼的优化每处我觉得值得优化的部分,重写再重写。

慢慢的,我学会接受一些东西:

比如相信编译器。

比如别耍小聪明。

比如不要牺牲代码清晰性。

比如防御式编程。

比如先把代码做的可靠。

比如采用时间复杂度更高,但简洁的算法。

……

对于一个性能偏执狂来说,这些浅显的道理接受起来是多么的不容易。

我这里要写的,并不是重复证明这些道理多么的有价值;而是想反过来说,每次采用和性能相违背的方案时,我的内心都会抗拒和怀疑。我依旧认为应该考虑例外情况,从而破坏这些规则。如何判定什么时候该遵循、什么时候该违背。以我目前的水平,无法精确总结。只能靠大量的实践磨练出得感觉了。

同上,如果有精确的准则,我们应该让机器去选择,而不是人。

我敢肯定,无条件相信教条的程序员,不会成长。

相信 C++ 可以取得比 C 更高性能的程序员认为:C++ 语言设施会带来更高的效率。他们最喜欢举的例子是 algorithm::sort() 和 qsort() 的比较。

模板会内嵌比较函数,去掉函数调用之消耗。从而在性能测试中完全击败 qsort() (后者需要为每次比较做一次 C 函数调用)

前两周在有道难题 的决赛颁奖仪式后,我和参赛同学的交流中,我谈到了这个问题。当时,我先讲了另一段:如果你的程序要处理一组数据,是从前往后处理性能高、还是从后往前、还是间隔着处理…… 这其实取决于很多和你的算法关系不大的东西:比如内存控制器的工作方式、CPU Cache 的管理、OS 的虚拟内存调度,等等。

有时候,我们需要关心这些、有时候我们不关心这些。如果想把整个系统做的高效,何时关系,何时不关心,这个决策比如何优化更难,更要功力。对于性能偏执狂来说,影响他决策的才不是哪些重要,哪些不重要;不是把有限的精力投入到关键点的优化中;因为对于他来说,反正重要不重要的都会去优化的,他会无视旁人的嘲笑,做他觉得有兴趣的事情。做的久了,不存在事后优化,因为对他来说,第一次编写时就考虑了种种,而且随着经验的增加,代码可以在保证高效的同时清晰可靠。

但是,我们有时就不去关心那些底层的性能差异。这同样出于性能考虑。因为追究到细节,全体和局部很可能得到相反的结论。因为代码本身也是系统的一部分。就连代码的规模也会影响到机器的运作效率。

当你可以从更高层次来看问题,你就会对性能有更多的理解。我们编写和设计软件,最大的敌人是复杂度。性能的敌人同样也是它。控制软件的每个层次上处理对象的粒度就是减小复杂度的武器。

回到 sort 的问题。函数调用真的是不可饶恕的开销吗?如果你对一个整数数组排序,那么性能考虑就很巨大。对于整数比较操作而言,函数调用,寄存器压栈出栈这些,会有成倍的开销。

但是,除了教科书和考试题中,我们有多少机会对一个整数数组排序?

排序是为了重新组织一组对象(往往这还不是最终目的。比如说,排序只是为了更有效的检索,有效检索才是目的),在一个特定层次上,对象的数量不宜很多,对象的粒度应该保持相当的规模。其结果就是,对对象的比较的开销会大于函数调用。因为涉及对象细节的操作是跨层次的。正如大多数情况下,你不会考虑访问一个内存字节的操作,对于机器意味着什么,OS 怎样调用虚拟内存、CPU 怎样管理 Cache 、内存管理器怎么收发控制信号。跨越层次的数据访问,函数调用是应该被忽略的。

C++ 的 sort 想获得更高的性能,代价是破坏了层次间的封闭性,往往是得不偿失的。

关于函数调用的开销这个问题,和不把性能做为第一位的程序员讲是很容易的;但对于看中性能的程序员来说,其实是很纠结的一件事。

《Unix 编程艺术》在 Part 2 开篇(模块性:保持清晰,保持简洁)就提到了这点。中文版 P84 引用了一段话:

Dennis Ritchie 告诉所有人 C 中的函数调用开销真的很小很小,极力倡导模块化。于是人人都开始编写小函数,搞模块化。然而几年后,我们发现在 PDP-11 中函数调用开销仍然昂贵,而 VAX 代码往往在 “CLASS" 指令上花费掉 50% 的运行时间。 Dennis 对我们撒了谎!但为时已晚,我们已经欲罢不能……

我想说的是,我们得承认一些损失。承认它们最终是为了更好的性能。而不是在同一层次上用语言技巧去抹平它。过度依赖 template inline 这些,并不仅仅是浪费你的时间去等待编译。

舍就是得。


上面写了这么多,只想引出下面这个话题:

有时候,对于宏定义式的属性管理方式,并不满足我们的需求。

C 语言里其实还有另一种惯例:我们可以考察 FILE 的接口。fopen 在打开文件的时候,可以传入多个选项。它是用字符串传入的。每个字符表示了一个属性。这使得使用它们更加友好,并极具灵活性。

还可以看 XLib 的接口设计。虽然不算太好,以至于后来又有人制作 XCB 。但我个人觉得,已经比 Windows 的对应部分设计的好太多了。

Xlib 里,使用联合 + 位域的方式管理对象的内部结构。也是相当不错的。

谈及 GUI ,我推荐大家读读 IUP 的代码,或许你会喜欢上它。在折腾 GUI 的东东时,在 QT/GTK/WsWidgets 等等之外,又可以多一个选择。它的接口设计采用了一些原则,使得足够简洁。而我在经历了太多次的重构 GUI 模块后,才领悟了点点东西。之后,发现了 IUP ,看到了许多我最终认同的东西,感慨颇多。

btw, 真的,boost::python 或是 LuaBind 这样的,利用一大套代码,让机器转换繁杂的接口,从一个语言的接口转换另一个语言的方式,最终都是权益之计。把接口设计简洁方是正道。

这里引出另一种需求,我们需要保留属性的名字信息。这样在分割明确的模块之间使用更加人性。尤其是在需要跨语言使用的时候。这种情况下,我会选择使用 string 做 key 而不是宏定义出来的整数。

这就牵扯到实现的效率问题了。好吧,我们又绕回了性能这个话题。

为此,我 谨慎 的给我们的系统添加了一个新概念:const_string 这个类型(注:在《C语言接口与实现》中,这个东西叫 atom ,这是个贴切的名字)。在我的项目中,反对随意的使用 typedef ,因为那意味着不断的新概念的加入,为此,付出更大的体力代价也是值得的。也就是说,宁可在每个结构和联合前显式的敲上 struct 和 union 。

这个类型其实是个特殊的指针,指向一个不变的字符串。如果需要调试输出,可以直接用 (const char *) 强制转换。但是,一个字符串必须通过 api ,build 出这个类型来。

其实现就是建立一个全局的字符串池,用 hash 表索引。里面存放不重复的字符串。我们只在其间存放那些系统中的标识符(用来索引资源用的字符串)。我们在进程生命期间,不再释放任何标识符。因为它们是限的,所以我们不用担心它们会吞噬我们的内存。也不需要用复杂的引用计数或是 gc 来管理它们。

这些特殊 string 可以用简单的指针去使用,不用再顾及生命期。可以直接用高效的变量比较、可以方便的在模块间传递、可以参与排序、可以用于 hash 映射的 key …… 总之,当成基本类型用就好了。一定程度上,可以弥补 C 语言没有原生字符串类型的不足。

我用它们去索引对象的属性名字。这样可以兼顾性能。

具体的实现是:我在每个 C 模块(利用宏)初始化一些字符串常量。当然这本是链接器应该干的事情。可 C 语言的模型并不原生支持这个,依赖 C 语言模型的编译器就不会给你代劳。忍住 C++ 的诱惑,我用丑陋的宏辅助我实现了一点东西。给语言增加并不存在的概念(新的类型)、使用宏、这都让我非常有罪恶感。带着这种感觉做设计,不至于犯太多错误,不至于破坏 KISS 。

然后在系统运行起来后,这个模块中的函数,可以通过简单的 if else if 来筛选不同的属性访问请求。如果对性能要求再苛刻一点的,还可以做一个简单的映射,最终转换为 switch case 。不要问我为什么不使用函数指针数组,在前面已经解释过了。如果真的需要,也值得考虑。

注:虽然 const_string 其实就是一个 const char * ,但也不要直接 typedef const char * const_string; 。这样编译器不会帮你找出错误的类型匹配。正确的方式是定义成 typedef struct literal * const_string; ,我们不需要让使用 const_string 的模块了解 struct literal 是什么,实际上它什么都不是。想当成 const char * 的时候,依旧可以强制转换。但直接赋值是编译通不过的。

最终,一个对象的 getter 和 setter 可能被统一成两个 api :

int object_get(struct object *, const_string property, ...);
int object_set(struct object *, const_string property, ...);

有点 printf 风格?这就是 C 语言。


还想问这样会不会导致性能问题?如果在系统里,模块之间存在大量的交互,会高频率的访问对象的属性。这样不关是函数调用的开销了。可能还涉及 hash 表,涉及大量的 if else 比较……

那、就是你的设计问题了。你怎么可以允许这样的存在?

不同的对象活在不同的层次,它们最好是自生子灭。尽量少干涉它们。只在极少情况下改变它们的状态。大部分时间让它们自己运转去。至于模块内部,毋用我说,你不会使用外部接口去操控自己内部的数据吧?

对于 C++ ,不要幻想 inline 总能帮你解决问题;对于 C ,inline 并非传统。

承认 getter 和 setter 的开销。


一家之言,别相信我。请自己思考。实践。

Comments

语言嘛……分清元语言和对象语言再说吧。 这年头还纠结setter/getter的,先复习LSP/behavioral subtyping去。当然property是没什么意思。 至于C?233。槽点太多了也不用洗了(如果觉得被坑得不够多,请便)。C++什么的虽然整体也烂,起码也没tentative definition什么的奇葩玩意儿。而对于上层应用限定对象布局这种实现就是原罪,无论给了多少hack语言实现的便利。性能?profiling去。 KISS没DIY实用,而且容易变成庸人偷懒的借口,没什么好鼓吹的。
@七心葵 哲学不是玄学,沟通是为了理解。云风作出这样逻辑上矛盾的陈述,正是因为他的语言不足以表达他的思想。
@analyst 你还是没明白 少即是多 的道理。 注意到 analyst 和 云风 的这个讨论没有结果。我也惘然的不知道应该站在 云风 还是analyst 这一边。 但我觉得有一点似乎是确凿的,云风的展开论述中并没有说到他所想的那个点上,“少即是多”如何给人讲明白,也许还真的是个问题,我试着这么想:一个新特性(即便property是真的有用的特性,有百利而无一害的特性),只要其不是必需的,就值得商榷其存在的价值,这种争论其实与 C语言(TAOUP一书)和 C++语言(许多的OO读物)的争论是很类似的。 analyst的考虑也是很有道理的,我觉得跟这个类似的是讨论“思想是和语言无关的,还是受语言表达的制约影响的”。我个人倾向于认为思想与语言表达方式有关,也就是说一个设计是受所采用的基础设施(不光是语言,还包括library,framewrok等)影响的,这方面比较明显的例子是近来的元编程带来的影响,也许在云风眼里,不够的完全可以通过自己更精简的设计补上来(比如本文的话题?),但是语言直接有高级机制的支持,也许可以让普通程序员的生活更好过一些?“少即是多”的哲学对于普通人来说恐怕还是难以接受的,普通人的想法大概是“多多易善”,这是一个“最小接口”和“人本接口”的话题,艺术的话题。 话说回来,property真的算是一个“高级”特性么?我感觉这与OO,Mixin这类 高级机制还不同,这大概可以算作一个边角料吧,(就如同inline,const 这类的 最初被 c语言遗漏的东西,后来又被补充如c99中?) 也许这真的是一个艺术的话题吧。
挺喜欢云风的文章的
看懂了一小部分,已经受益颇多。真心希望前辈们可以多写一些类似的切身感悟出来,这是书本上无法获取的东西。当然肯定会有很多反对的意见,个人经历和层次不同嘛。但是通过辩论,无疑加深了对问题和观点的理解。
用字符串作属性key而不是用整数或直接使用成员变量,好处就是UNIX编程艺术里说的文本流是最好的通讯渠道。通过字符串,可以让属性优雅地穿梭于模块间,网络间。
nice const_string == atom in erlang
get set 本来就是种错误的设计思想啦 一个类 私有几个漂亮的小妞 就是想 自己占有这个成员 不让别人碰得到 影响这个成员的逻辑性方法都得经过这个类 私有同时也表明它有这个管理好这个成员的责任了 私有才是程序进化的表现 C语言 不私有 我感觉不符合社会进步的思想 毕竟不是机器思考社会了 是面向美人的社会 不过好似 好难想得好 对这个成员的方法有哪些 迫不利己将它公开给别人
get set 本来就是种错误的设计思想啦 一个类 私有几个漂亮的小妞 就是想 自己占有这个成员 不让别人碰得到 影响这个成员的逻辑性方法都得经过这个类 私有同时也表明它有这个管理好这个成员的责任了 私有才是程序进化的表现 C语言 不私有 我感觉不符合社会进步的思想 毕竟不是机器思考社会了 是面向美人的社会 不过好似 好难想得好 对这个成员的方法有哪些 迫不利己将它公开给别人
Bill Venners:这样说来,不变式就是类中各个不同的数据成员之间的一种关系? Bjarne Stroustrup:是这样的。如果每个数据成员都可以被赋予任何值,那就没什么太大必要做成一个类。以一个简单的“名字—地址”数据结构为例,如果任何字符串都是合法的名字,任何字符串都是合法的地址,那么它本质上就是一个结构,而不是一个类,请使用struct关键字来声明它。千万别把名字和地址作为私有数据成员隐藏起来,然后再提供类似于get_address、set_address、get_name以及set_name这样的成员函数来存取它们。或者更糟糕地,提供一个拥有get_name和set_name之类的虚函数的抽象基类,然后在一个派生类中重写它们,这种做法纯粹是挖空心思而已,绝无必要。
云风你看过Gobject的API吗?我觉得它的API和你说的满像的。 另外const_string的概念,GLIB里面也有一个。 当然,实际上用GObject是开发是很烦的一件事情,不得不借助一些。 看了这篇文章之后,最大的感觉是API的定义是非常非常重要的一件事情。
感觉有点累,你的模块你负责,我要接口AND结果,速度不行你负责优化,速度OK如何实现的我不管~整体代码编写要求一下风格就好了.讨论这些多少有点累,每个程序员都是艺术家,艺术家对事物的看法都是不同的嘛,所以不要太强求这个那个...~ ------------ 计算机也知道7~
sjinny: 我觉得对于很多情况来说,暴露对象的数据布局并不会产生太严重的后果,比如一个对象完全可以对所在的库里的代码暴露数据布局,而只是对库的使用者隐藏。 -------------- 是的, 隐藏也应该分层次和对象. 不能说隐藏就只有类成员这种层次的隐藏了, 以为给类成员加了个private/protected就认为理解了封装的所有精神了. 库级别的隐藏, 进程级别的隐藏都有自己的适用情况.
想问个小问题。为什么我把typedef struct literal const_string;写在非结构体,类或者函数里面时编译会通不过呢?哈,用的VS。。。
看完以后我怀疑我是脑残... 我竟然只会一点Pascle~~~~ 众位高人....晚生佩服~ 难道都是专业人士?O.O
这个讨论范围太大了,肯定会有人有不同的意见。特别是对KISS没有深刻体会的人,会有别的想法,但是这些想法无关对错。条条道路通罗马,理解别人的想法,并能做完事情,才是最重要的。就游戏上讲,国内的技术水平比国外还是差很远吧?
没有太明白云风说的: C++ 的 sort 想获得更高的性能,代价是破坏了层次间的封闭性,往往是得不偿失的。 std::sort的接口和qsort大同小异,只不过用iterator代替了void*,对用户来说,他们需要提供的比较函数也几乎是一样的,为什么说破坏了层次间的封闭性呢?
我觉得对于很多情况来说,暴露对象的数据布局并不会产生太严重的后果,比如一个对象完全可以对所在的库里的代码暴露数据布局,而只是对库的使用者隐藏。C++里自动调用子对象的构造/析构,这在我看来也是一种自动的资源管理机制,而且地位不下于栈;)
另外,想必你对GUI的设计也颇有心得,那么不妨再领略一下WPF的设计,看一下WPF在GUI设计上所达到的高度,我想你会有另一番感悟。 ------------------------------ 对不起,云风不用windows.
我 谨慎 的给我们的系统添加了一个新概念:const_string 这个类型(也考虑过另一个名字 literal 或许更贴切一些)。 ---------------------------- 呕,这个名字糟透了。。。应该叫unique_string才更贴切。 高手要有高手的姿态,说起来都是有着数十年编程经验的人了,还整天说着一些"打太极"之类说了等于没说的话,让一些晚辈的小P孩跟在屁股后面欢呼雀跃,很爽? 别人问setter/getter这个问题的初衷是出于设计的考量,你却扯到性能上来,你的强大的引擎需要在乎这个的开销?你能理解可执行程序在当前和将来的处理器架构下的瓶颈吗?还好你还没有扯到机器指令的时序上来。
每个设计良好的语言都有其最适用的领域,比如C语言其实就是机器抽象,如果所要解决的问题就是机器相关层面的,那么C无疑是最佳选择。 如果不是,那么就应该认真、深入分析问题领域本身,找出最适合解决该领域问题的语言特性,如果有合适的语言就用,没有就自己设计一个语言来,这应该是一个优秀程序员的基本能力。
可能这样命名更合适 设置对象的输入状态 SetObjectInputStatus(...) 获取对象的输出状态 GetObjectOutputStatus() 而不是针对成员变量去做setter getter 以C语言的方式去思考, 会更容易得到合理的接口定义
@sjinny 我的建议已经写出来了。 作为对对象内部状态的控制。我认为两个接口足以。一个 get 一个 set 。 不需要为每个属性单独设计接口。 暴露对象的内部数据布局是我极力反对的。所以即使用 C++ ,我也不会把成员变量 public 出来,甚至我不赞成暴露在接口描述的类定义中,即使是 private 也不行。 建议而已。我对自己没有教条。
看来云风误会我那个留言的意思了……我本来想表达的是,有的人认为,任何数据成员都不能直接暴露出来,即不能有任何public的成员;而另一种观点则是,如果这个成员本身就可以作为接口,那么作为public成员而暴露会更好。不过既然说到属性的实现,其实我对这个也感兴趣,但是我还是希望能在C/C++这一层实现类型安全的属性机制。目前还没想到好的方法。目前的做法就是把各种属性类型做成一个模板类,它们有共同的基类,能通过虚函数获得属性对象的类型(枚举)。但是要把接口做得简洁安全(指类型安全)似乎仍然很困难。
C#的property或者Java等的getter/setter, 非常丑陋. 使用public字段, 如果需要把字段值做一些转换后再设置或者获取, 再写getter/setter. 我不觉得getter/settter比public字段更能隐藏.
@云风 1、C#的protery代码会被JIT成native code并且自动内联掉,没有实际的函数调用动作,你如何得出C#的性能不如C的getter/setter的结论? 2、你既然强调C不需要强类型约束,为何不把const_string定义成const char *,而是用struct literal来定义const_string?不还是需要编译器来帮你做类型检测么?既然const_string需要编译器帮你做类型检查,为什么后面的value就不需要类型检查了?这在道理上说不通,不是么?只不过是因为C缺乏必要的语言特性,无法实现...的类型安全,所以你不得不做出这样的牺牲。如果是C++的话,至少你还可以弄个Any对象,也比用...要安全的多。 3、对象之间的交互是需求来决定的。凭什么认为用了高级的语言特性,我们就会凭空制造出一些对象之间的交互,以至让设计变得糟糕?这两者之间似乎并没有必然的逻辑关系。锤子大了只是帮你更省力的敲钉子,而不是让你把什么东西都当成钉子。你既然说“大的层次上,避免对象对象之间的交互”,那么就是说getter/setter可以通过更好的设计来避免掉,那你在文章中大谈getter/setter的C实现方案又是为何?需要为一个本可以不存在的需求做设计吗? 4、高级特性本来就是针对某一类需求而专门设计的,难道C#的property不是专心为了做好getter/setter而设计出来的吗?我倒还真没见过凭空设计出来的“万能”的高级特性。 5、既然你也认为“在每个层次上用最合适的解决方案。解决方案包括了选择开发语言...”。那么我认为需要大量getter/setter的对象,并不适合用C来开发,为什么不放到一个更高的层次上去解决呢?我们要讨论的不是怎么用C搞出一个没有类型安全、没有编译期检查、性能也不太好的解决方案,而应该是怎么选用更合适的语言来解决这个问题。
觉得云风的const_string在作为选项在不同函数之间传送就如OCaml的Polymorphric Invariants. let f opt = match opt with | `A -> 1 | `B -> 2.
有时候想想,自己在不同时期的左右互搏,其实是挺有意思的。个人认为,聪明的人不需要任何信条或哲理类的东西,因为这些是扰乱我们心志的罪魁祸首。 路过,呵呵
大哉斯言,收藏了。
@analyst 开篇我就写了,用你选择的开发语言提供的东西。如果没有,不要造一个。 如果要和 C# 比较,C 的 getter 和 setter 的性能比 C# 高。这不关 getter 和 setter 的事。C 实现出来后,其功能模块可以被大多数别的语言用,可以在各种平台编译,可以适应更多的需求环境。 我从来不是单一语言的簇拥。在每个层次上用最合适的解决方案。解决方案包括了选择开发语言;也包括的切分层次;包括了划分模块;包括了去掉不需要的需求等等。 强类型系统是好的。btw, C 语言并非强类型系统,在使用传统上不是。有如 printf 一样,这里的设计是符合 C 的传统的,是一致的。硬给 C 语言加上严格的强类型就需要考量设计问题了。 编译检查是好东西。但它增加了模块之间交互的规范。直到今天,二进制交互的标准还是以 C 语言调用接口为主流,除了历史原因外,肯定还有其它理由。 是否需要在更宏观的模块间使用编译器去检查调用规范,这才是问题。 如正文所言:大的层次上,避免对象对象之间的交互。而怎样实现从外部探测对象内部状态,只是一个技术细节手段。已经不是设计上的重点了。放弃这点,转而向语言高级特性需求支持,加强对象之间的交互。把本该用 C 语言解决层次的模块提高到用 C# 的层次上,是比在 C 或 C++ 中,模拟出 C# 的语言设施更糟糕的事情。 "语言的高级特性对于构建高层次搞复杂度的应用是至关重要的" 如果无法分解复杂度,那么我们的确需要一些针对性的工具。在此之前,我们首先是反复考虑,复杂度应该怎样分解;而不是用更复杂的语言特性去解决它。 针对性的工具指专心做好一件事的工具,不是万能的高级特性。
@云风 空谈道理没什么意义。我们还是回到具体问题上来,既然你这篇文章是谈property,那么你的object_get/object_set的设计相对于原生支持property的语言(比如说C#),其优势何在?如何来证明你所说的“少既是多”的道理? 另外,想必你对GUI的设计也颇有心得,那么不妨再领略一下WPF的设计,看一下WPF在GUI设计上所达到的高度,我想你会有另一番感悟。
GUI的东西云风研究一下SWT/Jface看看。从我了解过的GUI库来看,这个东西最简洁实用
@analyst 你还是没明白 少即是多 的道理。
我所坚持的一个观点就是语言的高级特性对于构建高层次搞复杂度的应用是至关重要的。“语言不重要,重要的是设计”这样的说法其实并不完全正确,设计固然很重要,但是设计依赖于语言特性,因为缺乏必要的语言特性支持将使设计不得不做出牺牲甚至根本无法实现某些设计。就拿你这个object_get/object_set来说,因为语言缺乏property机制,因此不得不做出这样一个折中的设计,虽然避免了大量的get/set接口,但是却丧失了类型安全,编译期检查,性能等方面的好处。设计者在使用时总不是那么心安理得,时不时要被这些问题所纠结。再来看看WPF中DependencyProperty的设计,在.net丰富的语言特性支持下DependencyProperty实现了非常高阶非常强大的功能。DependencyProperty可以是个默认值,也可以是个静态值,也可以是个父对象的值,也可以是个动态的表达式求值,还可以是一个绑定到其他对象上的值(绑定对象自动维护属性值之间的同步更新)。如果单纯用C的话是没有办法实现这样高阶的设计的,勉强实现出来也会丧失优雅性而难于使用,用动态语言确实也可以实现出这样的设计,但是却又丧失了性能,而性能恰恰又是这个设计中的关键因素之一。另外一个例子则是关于Erlang的,这个不用我多说云风肯定也会明白,用C是实现不出Erlang这种优雅简洁的并发设计的。可以说,语言特性决定了你的设计所能达到的高度。
云风啥时候能搞点high level的心得,别总是低着头看底层。
const_string 这东东是在《C 语言接口与实现》里面看到的,还以为是个在 C 里面常见到不需要解释的东东呢。 赞这句:“这就是 C 语言。”
很喜欢你的blog~~ 我觉得工程项目中的编程者需要在性能,可读性,可测试性,可重用性等方面做权衡.这个时候新老程序员的差距体现的比较明显
高哦。。我承认,云风天生就是为程序而生的
赞,颇有不少共同的看法

Post a comment

非这个主题相关的留言请到:留言本