从Rob Pike 的 Google+上的一个推看到了一篇叫《Understanding Object Oriented Programming》的文章,我先把这篇文章简述一下,然后再说说老牌黑客Rob Pike的评论。
先看这篇教程是怎么来讲述OOP的。它先给了下面这个问题,这个问题需要输出一段关于操作系统的文字:假设Unix很不错,Windows很差。
这个把下面这段代码描述成是Hacker Solution。(这帮人觉得下面这叫黑客?我估计这帮人真是没看过C语言的代码)
01 |
public class PrintOS |
02 |
{ |
03 |
public static void main( final String[] args) |
04 |
{ |
05 |
String osName = System.getProperty( "os.name" ) ; |
06 |
if (osName.equals( "SunOS" ) || osName.equals( "Linux" )) |
07 |
{ |
08 |
System.out.println( "This is a UNIX box and therefore good." ) ; |
09 |
} |
10 |
else if (osName.equals( "Windows NT" ) || osName.equals( "Windows 95" )) |
11 |
{ |
12 |
System.out.println( "This is a Windows box and therefore bad." ) ; |
13 |
} |
14 |
else |
15 |
{ |
16 |
System.out.println( "This is not a box." ) ; |
17 |
} |
18 |
} |
19 |
} |
然后开始用面向对象的编程方式一步一步地进化这个代码。
先是以过程化的思路来重构之。
01 |
public class PrintOS |
02 |
{ |
03 |
private static String unixBox() |
04 |
{ |
05 |
return "This is a UNIX box and therefore good." ; |
06 |
} |
07 |
private static String windowsBox() |
08 |
{ |
09 |
return "This is a Windows box and therefore bad." ; |
10 |
} |
11 |
private static String defaultBox() |
12 |
{ |
13 |
return "This is not a box." ; |
14 |
} |
15 |
private static String getTheString( final String osName) |
16 |
{ |
17 |
if (osName.equals( "SunOS" ) || osName.equals( "Linux" )) |
18 |
{ |
19 |
return unixBox() ; |
20 |
} |
21 |
else if (osName.equals( "Windows NT" ) ||osName.equals( "Windows 95" )) |
22 |
{ |
23 |
return windowsBox() ; |
24 |
} |
25 |
else |
26 |
{ |
27 |
return defaultBox() ; |
28 |
} |
29 |
} |
30 |
public static void main( final String[] args) |
31 |
{ |
32 |
System.out.println(getTheString(System.getProperty( "os.name" ))) ; |
33 |
} |
34 |
} |
然后是一个幼稚的面向对象的思路。
PrintOS.java
1 |
public class PrintOS |
2 |
{ |
3 |
public static void main( final String[] args) |
4 |
{ |
5 |
System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ; |
6 |
} |
7 |
} |
OSDiscriminator.java
01 |
public class OSDiscriminator // Factory Pattern |
02 |
{ |
03 |
private static BoxSpecifier theBoxSpecifier = null ; |
04 |
public static BoxSpecifier getBoxSpecifier() |
05 |
{ |
06 |
if (theBoxSpecifier == null ) |
07 |
{ |
08 |
String osName = System.getProperty( "os.name" ) ; |
09 |
if (osName.equals( "SunOS" ) || osName.equals( "Linux" )) |
10 |
{ |
11 |
theBoxSpecifier = new UNIXBox() ; |
12 |
} |
13 |
else if (osName.equals( "Windows NT" ) || osName.equals( "Windows 95" )) |
14 |
{ |
15 |
theBoxSpecifier = new WindowsBox() ; |
16 |
} |
17 |
else |
18 |
{ |
19 |
theBoxSpecifier = new DefaultBox () ; |
20 |
} |
21 |
} |
22 |
return theBoxSpecifier ; |
23 |
} |
24 |
} |
BoxSpecifier.java
1 |
public interface BoxSpecifier |
2 |
{ |
3 |
String getStatement() ; |
4 |
} |
DefaultBox.java
1 |
public class DefaultBox implements BoxSpecifier |
2 |
{ |
3 |
public String getStatement() |
4 |
{ |
5 |
return "This is not a box." ; |
6 |
} |
7 |
} |
UNIXBox.java
1 |
public class UNIXBox implements BoxSpecifier |
2 |
{ |
3 |
public String getStatement() |
4 |
{ |
5 |
return "This is a UNIX box and therefore good." ; |
6 |
} |
7 |
} |
WindowsBox.java
1 |
public class WindowsBox implements BoxSpecifier |
2 |
{ |
3 |
public String getStatement() |
4 |
{ |
5 |
return "This is a Windows box and therefore bad." ; |
6 |
} |
7 |
} |
他们觉得上面这段代码没有消除if语句,他们说这叫代码的“logic bottleneck”(逻辑瓶颈),因为如果你要增加一个操作系统的判断的话,你不但要加个类,还要改那段if-else的语句。
所以,他们整出一个叫Sophisticated的面向对象的解决方案。
注意其中的Design Pattern
PrintOS.java
1 |
public class PrintOS |
2 |
{ |
3 |
public static void main( final String[] args) |
4 |
{ |
5 |
System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ; |
6 |
} |
7 |
} |
OSDiscriminator.java
01 |
public class OSDiscriminator // Factory Pattern |
02 |
{ |
03 |
private static java.util.HashMap storage = new java.util.HashMap() ; |
04 |
|
05 |
public static BoxSpecifier getBoxSpecifier() |
06 |
{ |
07 |
BoxSpecifier value = (BoxSpecifier)storage.get(System.getProperty( "os.name" )) ; |
08 |
if (value == null ) |
09 |
return DefaultBox.value ; |
10 |
return value ; |
11 |
} |
12 |
public static void register( final String key, final BoxSpecifier value) |
13 |
{ |
14 |
storage.put(key, value) ; // Should guard against null keys, actually. |
15 |
} |
16 |
static |
17 |
{ |
18 |
WindowsBox.register() ; |
19 |
UNIXBox.register() ; |
20 |
MacBox.register() ; |
21 |
} |
22 |
} |
BoxSpecifier.java
1 |
public interface BoxSpecifier |
2 |
{ |
3 |
String getStatement() ; |
4 |
} |
DefaultBox.java
1 |
public class DefaultBox implements BoxSpecifier // Singleton Pattern |
2 |
{ |
3 |
public static final DefaultBox value = new DefaultBox () ; |
4 |
private DefaultBox() { } |
5 |
public String getStatement() |
6 |
{ |
7 |
return "This is not a box." ; |
8 |
} |
9 |
} |
UNIXBox.java
01 |
public class UNIXBox implements BoxSpecifier // Singleton Pattern |
02 |
{ |
03 |
public static final UNIXBox value = new UNIXBox() ; |
04 |
private UNIXBox() { } |
05 |
public String getStatement() |
06 |
{ |
07 |
return "This is a UNIX box and therefore good." ; |
08 |
} |
09 |
public static final void register() |
10 |
{ |
11 |
OSDiscriminator.register( "SunOS" , value) ; |
12 |
OSDiscriminator.register( "Linux" , value) ; |
13 |
} |
14 |
} |
WindowsBox.java
01 |
public class WindowsBox implements BoxSpecifier // Singleton Pattern |
02 |
{ |
03 |
public static final WindowsBox value = new WindowsBox() ; |
04 |
private WindowsBox() { } |
05 |
public String getStatement() |
06 |
{ |
07 |
return "This is a Windows box and therefore bad." ; |
08 |
} |
09 |
public static final void register() |
10 |
{ |
11 |
OSDiscriminator.register( "Windows NT" , value) ; |
12 |
OSDiscriminator.register( "Windows 95" , value) ; |
13 |
} |
14 |
} |
MacBox.java
01 |
public class MacBox implements BoxSpecifier // Singleton Pattern |
02 |
{ |
03 |
public static final MacBox value = new MacBox() ; |
04 |
private MacBox() { } |
05 |
public String getStatement() |
06 |
{ |
07 |
return "This is a Macintosh box and therefore far superior." ; |
08 |
} |
09 |
public static final void register() |
10 |
{ |
11 |
OSDiscriminator.register( "Mac OS" , value) ; |
12 |
} |
13 |
} |
作者还非常的意地说,他加了一个“Mac OS”的东西。老实说,当我看到最后这段OO大师搞出来的代码,我快要吐了。我瞬间想到了两件事:一个是以前酷壳上的《面向对象是个骗局》和 《各种流行的编程方式》中说的“设计模式驱动编程”,另一个我想到了那些被敏捷洗过脑的程序员和咨询师,也是这种德行。
于是我去看了一下第一作者Joseph Bergin的主页,这个Ph.D是果然刚刚完成了一本关于敏捷和模式的书。
(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的设计准则。