如此理解面向對象編程
從 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 的設計準則。