如此理解面向对象编程
从 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"))) ; } }
然后是一个幼稚的面向对象的思路。
幼稚的面向对象编程
PrintOS.java
public class PrintOS { public static void main (final String[] args) { System.out.println (OSDiscriminator.getBoxSpecifier () .getStatement ()) ; } }
OSDiscriminator.java
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 ; } }
BoxSpecifier.java
public interface BoxSpecifier { String getStatement () ; }
DefaultBox.java
public class DefaultBox implements BoxSpecifier { public String getStatement () { return "This is not a box." ; } }
UNIXBox.java
public class UNIXBox implements BoxSpecifier { public String getStatement () { return "This is a UNIX box and therefore good." ; } }
WindowsBox.java
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
PrintOS.java
public class PrintOS { public static void main (final String[] args) { System.out.println (OSDiscriminator.getBoxSpecifier () .getStatement ()) ; } }
OSDiscriminator.java
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 () ; } }
BoxSpecifier.java
public interface BoxSpecifier { String getStatement () ; }
DefaultBox.java
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." ; } }
UNIXBox.java
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) ; } }
WindowsBox.java
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) ; } }
MacBox.java
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 的设计准则。