如此理解面向对象编程
从Rob Pike 的 Google+上的一个推看到了一篇叫《Understanding Object Oriented Programming》的文章,我先把这篇文章简述一下,然后再说说老牌黑客Rob Pike的评论。
先看这篇教程是怎么来讲述OOP的。它先给了下面这个问题,这个问题需要输出一段关于操作系统的文字:假设Unix很不错,Windows很差。
这个把下面这段代码描述成是Hacker Solution。(这帮人觉得下面这叫黑客?我估计这帮人真是没看过C语言的代码)
public class PrintOS { public static void main(final String[] args) { String osName = System.getProperty("os.name") ; if (osName.equals("SunOS") || osName.equals("Linux")) { System.out.println("This is a UNIX box and therefore good.") ; } else if (osName.equals("Windows NT") || osName.equals("Windows 95")) { System.out.println("This is a Windows box and therefore bad.") ; } else { System.out.println("This is not a box.") ; } } }
然后开始用面向对象的编程方式一步一步地进化这个代码。
先是以过程化的思路来重构之。
目录
过程化的方案
public class PrintOS { private static String unixBox() { return "This is a UNIX box and therefore good." ; } private static String windowsBox() { return "This is a Windows box and therefore bad." ; } private static String defaultBox() { return "This is not a box." ; } private static String getTheString(final String osName) { if (osName.equals("SunOS") || osName.equals("Linux")) { return unixBox() ; } else if (osName.equals("Windows NT") ||osName.equals("Windows 95")) { return windowsBox() ; } else { return defaultBox() ; } } public static void main(final String[] args) { System.out.println(getTheString(System.getProperty("os.name"))) ; } }
然后是一个幼稚的面向对象的思路。
幼稚的面向对象编程
public class PrintOS { public static void main(final String[] args) { System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ; } }
public class OSDiscriminator // Factory Pattern { private static BoxSpecifier theBoxSpecifier = null ; public static BoxSpecifier getBoxSpecifier() { if (theBoxSpecifier == null) { String osName = System.getProperty("os.name") ; if (osName.equals("SunOS") || osName.equals("Linux")) { theBoxSpecifier = new UNIXBox() ; } else if (osName.equals("Windows NT") || osName.equals("Windows 95")) { theBoxSpecifier = new WindowsBox() ; } else { theBoxSpecifier = new DefaultBox () ; } } return theBoxSpecifier ; } }
public interface BoxSpecifier { String getStatement() ; }
public class DefaultBox implements BoxSpecifier { public String getStatement() { return "This is not a box." ; } }
public class UNIXBox implements BoxSpecifier { public String getStatement() { return "This is a UNIX box and therefore good." ; } }
public class WindowsBox implements BoxSpecifier { public String getStatement() { return "This is a Windows box and therefore bad." ; } }
他们觉得上面这段代码没有消除if语句,他们说这叫代码的“logic bottleneck”(逻辑瓶颈),因为如果你要增加一个操作系统的判断的话,你不但要加个类,还要改那段if-else的语句。
所以,他们整出一个叫Sophisticated的面向对象的解决方案。
OO大师的方案
注意其中的Design Pattern
public class PrintOS { public static void main(final String[] args) { System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ; } }
public class OSDiscriminator // Factory Pattern { private static java.util.HashMap storage = new java.util.HashMap() ; public static BoxSpecifier getBoxSpecifier() { BoxSpecifier value = (BoxSpecifier)storage.get(System.getProperty("os.name")) ; if (value == null) return DefaultBox.value ; return value ; } public static void register(final String key, final BoxSpecifier value) { storage.put(key, value) ; // Should guard against null keys, actually. } static { WindowsBox.register() ; UNIXBox.register() ; MacBox.register() ; } }
public interface BoxSpecifier { String getStatement() ; }
public class DefaultBox implements BoxSpecifier // Singleton Pattern { public static final DefaultBox value = new DefaultBox () ; private DefaultBox() { } public String getStatement() { return "This is not a box." ; } }
public class UNIXBox implements BoxSpecifier // Singleton Pattern { public static final UNIXBox value = new UNIXBox() ; private UNIXBox() { } public String getStatement() { return "This is a UNIX box and therefore good." ; } public static final void register() { OSDiscriminator.register("SunOS", value) ; OSDiscriminator.register("Linux", value) ; } }
public class WindowsBox implements BoxSpecifier // Singleton Pattern { public static final WindowsBox value = new WindowsBox() ; private WindowsBox() { } public String getStatement() { return "This is a Windows box and therefore bad." ; } public static final void register() { OSDiscriminator.register("Windows NT", value) ; OSDiscriminator.register("Windows 95", value) ; } }
public class MacBox implements BoxSpecifier // Singleton Pattern { public static final MacBox value = new MacBox() ; private MacBox() { } public String getStatement() { return "This is a Macintosh box and therefore far superior." ; } public static final void register() { OSDiscriminator.register("Mac OS", value) ; } }
作者还非常的意地说,他加了一个“Mac OS”的东西。老实说,当我看到最后这段OO大师搞出来的代码,我快要吐了。我瞬间想到了两件事:一个是以前酷壳上的《面向对象是个骗局》和 《各种流行的编程方式》中说的“设计模式驱动编程”,另一个我想到了那些被敏捷洗过脑的程序员和咨询师,也是这种德行。
于是我去看了一下第一作者Joseph Bergin的主页,这个Ph.D是果然刚刚完成了一本关于敏捷和模式的书。
Rob Pike的评论
(Rob Pike是当年在Bell lab里和Ken一起搞Unix的主儿,后来和Ken开发了UTF-8,现在还和Ken一起搞Go语言。注:不要以为Ken和Dennis是基友,其实他们才是真正的老基友!)
Rob Pike在他的Google+的这贴里评论到这篇文章——
他并不确认这篇文章是不是搞笑?但是他觉得这些个写这篇文章是很认真的。他说他要评论这篇文章是因为他们是一名Hacker,至少这个词出现在这篇文章的术语中。
他说,这个程序根本就不需要什么Object,只需要一张小小的配置表格,里面配置了对应的操作系统和你想输出的文本。这不就完了。这么简单的设计,非常容易地扩展,他们那个所谓的Hack Solution完全就是笨拙的代码。后面那些所谓的代码进化相当疯狂和愚蠢的,这个完全误导了对编程的认知。
然后,他还说,他觉得这些OO的狂热份子非常害怕数据,他们喜欢用多层的类的关系来完成一个本来只需要检索三行数据表的工作。他说他曾经听说有人在他的工作种用各种OO的东西来替换While循环。(我听说中国Thoughtworks那帮搞敏捷的人的确喜欢用Object来替换所有的if-else语句,他们甚至还喜欢把函数的行数限制在10行以内)
他还给了一个链接http://prog21.dadgum.com/156.html,你可以读一读。最后他说,OOP的本质就是——对数据和与之关联的行为进行编程。便就算是这样也不完全对,因为:
Sometimes data is just data and functions are just functions.
我的理解
我觉得,这篇文章的例子举得太差了,差得感觉就像是OO的高级黑。面向对象编程注重的是:1)数据和其行为的打包封装,2)程序的接口和实现的解耦。你那怕,举一个多个开关和多个电器的例子,不然就像STL中,一个排序算法对多个不同容器的例子,都比这个例子要好得多得多。老实说,Java SDK里太多这样的东西了。
我以前给一些公司讲一些设计模式的培训课,我一再提到,那23个经典的设计模式和OO半毛钱关系没有,只不过人家用OO来实现罢了。设计模式就三个准则:1)中意于组合而不是继承,2)依赖于接口而不是实现,3)高内聚,低耦合。你看,这完全就是Unix的设计准则。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《如此理解面向对象编程》的相关评论
Pike的意思其实是数据驱动编程,在Art of Unix Programming 对于数据驱动编程 和 OO 有过比较
个人认为:第一 小型的编程(100行内),OO没有意思。而且那些包装来,包装去;反而让简单的事情变得复杂了;第二 常考虑到重构;第三 要考虑后人的感受(包括使用与维护)。所以,正如作者说是,举得例子太不恰当了!至于作者对于设计模式与面向对象(OO)没有任何关系,个人保持保留观点。个人认为说面向对象,就要说面向对象设计,如果单学面向对象语言,而不理解设计的方法和原则。那么学习面向对象语言是非常抵触的。(正如文章中举得例子,让人费解,不想学习)。OO设计的原则是:1)数据和其行为的打包封装,2)程序的接口和实现的解耦。(作者的原话)说出来容易,如果要有所理解,那么就得费很大的脑力来分析,思考;我还想补充一点:重用。这也正是设计模式与OO的共同点。
把一个函数的代码行数限定在10行,20行以内是错误的吗?我的主管,曾经因为我一个函数的代码量太大,让我拆成几个函数。
rob的评论就是code complete里讲的表驱动编程嘛。建两个数组完事儿了,或者建个结构体数组完事儿。
作为一个工厂模式的例子,也没有什么不妥的。。
我经常看到网上或者个人在一致强调“组合优于继承”,这句话只能算是对了一半,组合很好,但是继承也没有错。“组合优于继承”这句话应该是有上下文的。当分析事物间关系的时候,关系是继承的,那就是继承的,关系是组合的那就是组合的,没有谁好谁坏之分。
的确是这个例子举得太差了,单就这个例子来说确实是一张配置表格的事儿。中国的教科书里就有很多类似的例子,为了说明一些问题举一些不切实际的例子。
一直以来都觉得国内乱吹OOP还是很明显的。刷LeetCode的都知道,对于这种单个的算法问题,C是最快的,java跑400ms的东西,拿一样的算法用C写一写,都不用优化,只要个位数ms。单效率来说,一般什么抽象语言都比不过C(C++个别情况下还是可以的,但是这个时候他的写法就接近C了)。。。 C还比不过汇编呢!汇编还比不过100010010001呢!但是我们依然义无反顾地抽象语言,降低效率,原因并不是因为这样能帮我们把程序写快,而是因为人脑不是电脑,把语言自然化,抽象化有助于我们更好地理解问题,少写错误。其中OOP尤其是为了Scalability而不是efficiency,各个接口都是为了大家互相利用现有code(当然一直有人说函数型比这个好。。。也不是不对),多人同时工作等等。 所以啊,什么事情用什么语言,要和一个大团队写万行,十万级的代码,java的优势是明显的,失掉的那点速度是可以接受的,你用x86去写,得到的那点runtime绝对不值得他带来的额外的设计,合作,调试上的难度。同理,写一段特别注重速度的底层优化码,java再易懂再好用他那点overhead就注定了你不该用他,汇编和C这个时候就该上场了。那些偏要再不同的语言分出个高下的程序员,只能说是学业不精。
@foo
不错..没有无脑黑语言的还是比较难得浮出来一个, 说实话现在很多产品还没做到需要靠算法来解决瓶颈的时候. Java本身被开发出来也不是因为运行效率如何, 而是为了提高开发效率, 而且人才流动这么快的现在, 如果有个大神用c写一套…找人来维护得多耗时啊…
OO大师的方案其实是想讲工厂模式,其目的是可以动态添加一个含有一组具有不同行为实现的对象。只是他举得例子不是很好而已。如果这个os的行为不只是输出一句,而是还有运行软件、关闭软件、优化软件等行为,并且不同的os会有不
同的运行软件、关闭软件、优化软件的算法实现、运行和关闭软件的策略、优化软件的策略。Rob Pike所说的使用的表格方式可以实现吗?表格可以动态添加软件的行为吗?而工厂模式就是为了解决这个问题的
为什么不可以? 你连最基本的冯诺依曼架构都忘了吗? 程序也是数据.
看看游戏技能都是怎么做的吧, 技能有唯一ID, 对应一些基本属性和一段Lua脚本. 当释放技能时, 将基本属性带入伤害计算公式, 并执行Lua脚本来做到一些特殊需求(比如特殊伤害, 释放限制, 附加Buff, DeBuff等).
这不就是Rob Pike说的查表吗?
@勇敢的springz 你说的太对了
我觉得例子和java spi 的实现理念相似。
不同情况不同讨论。我觉得Rob Pike作为一名黑客,更多的是关注一个人怎样去高效快速地实现,而设计模式更多的关注于代码的工程化,好比是一种既定的规则,使一个团队容易协作。
我的原则是,怎么简单怎么来,如果你是写library,你需要的是让使用者如何易于理解,使用简单,容易扩展。如果你写的是程序,你如何写才简单易于扩展就如何写。23个设计模式不如一句话,简单而可复用。简单总是没错的,为了一点的扩展性,少写一点的代码,而引入一堆的OO构建,就是奢侈,程序拒绝奢侈浪费。
面向对象不过是一种归一化和封装的实现方式, 总有人把这玩意当作银弹.
而且程序中的类(class), 和生活中的类根本不是一个玩意, 但是几乎所有的教面向对象的都让学生看作是同一个. 按照某些人眼中的面向对象编程, 一个游戏的技能树怕不是要搞出几万个类几十层继承来.
把刚看来的观点原封不动的拿来输出,看起来是真尴尬。