如此理解面向对象编程

如此理解面向对象编程

从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 ,请勿用于任何商业用途)

好烂啊有点差凑合看看还不错很精彩 (63 人打了分,平均分: 4.25 )
Loading...

如此理解面向对象编程》的相关评论

  1. 事实上,博主的文章题目——《如此理解面向对象编程》——已经显而易见的表达出了这样一个意思:文章主要讨论的,不是面向对象(的问题),而是“错误理解面向对象”的问题。

    这种错误理解,就是我所谓的“跑肚拉稀”:什么都不懂,什么都不会,先把一切都class化了,然后就着这个诡异的class方案使劲扯淡。

    任何不太傻的人都明白,这种行为艺术和面向对象没有半毛钱关系。

    PS:连这么简单的人话都看不懂,可以想象一些人的思维混乱程度。也难怪他们会去捧大屎的臭脚。

  2. invalid :

    而这个大屎的例子,则生动的展示了“过早优化的架构”会带来什么。

    会过早的优化性能的,往往不是水平不够,而是太过自信。

    而过早的优化架构、甚至把架构“优化”的酷似跑肚拉稀的,正说明了他完全不懂何谓架构。

    对合格的架构师来说,不存在什么大项目。在他眼里,应该只有 语句级的小项目、函数级的小项目、模块级的小项目乃至系统级的小项目——懂的人自然懂我在说什么。

    凡这种过早优化架构优化出稀屎、然后又用“大项目怎么怎么”来为自己辩护的,100%没见过什么严肃的项目;甚至,有没有真正写出过一个可靠的函数(语句级的小项目!)都是个问号。

  3. jametong :
    模式是一种设计思维,OO只是实现这种思维的一种手段。
    而不是反过来,没有这种设计思维,单纯的OO几乎是没有任何价值的。

    呵呵,貌似老兄完全搞反了吧

  4. 我们评价一个行为的好与坏,一定要放在一定的环境中,切记。

    我也不说谁对谁错,一定要明白这么做的“价值”何在,成本如何去考量,在时间维度上如何去平衡。

  5. 我觉得这几个例子刚好就是经典面对不同层次需求的解决方案。
    从第一个方法(Hacker Solution是说它是一个硬编码的实现方式),它适用于一个需求极其明确并且不会再修改的情况。
    到最后一个方法,它适用于预先就已经可知的在未来会快速膨胀的需求。
    大家在解决各自的问题时都能提供较好的表现。
    例子看起来愚蠢,因为例子需要简单易懂,而且针对这几种所有的实现方法我们必须采用同样的例子。

    所谓OO大师的实现方法,除了复杂和没有必要我们还看到了什么呢?
    跟硬编码的解决方案相比,如果getStatement需要一系列复杂的计算呢?我们想要一个独立的代码文件,还是穿插在其他代码之中呢?
    跟过程化的解决方案相比,如果你第一次接触这一段代码,你是希望看到所有的情况被整齐的排列为若干个名称相近的类文件还是在一个冗长的文件中找到自己关心的部分呢?
    跟幼稚的面向对象编程方案相比,如果你需要运行时添加一种条件分支,谁能满足需求谁又不能呢?

    可以肯定的是,更多的设计带来更强的灵活性,同时也要付出更复杂的实现。那样的话,面对同一个问题,选择什么样的设计模式,其实主要看的就是程序员的性格了。

  6. 什么都怕狂热和偏执,无论对喜欢OO的还是喜欢UNIX的,其实我认为 Pragmatic 是最好的,我们心中对技术有某种喜好和原则,但遇到具体问题一定要仔细思考什么方法是最好的,而不是教条和照搬。
    这个例子如果只是打印点什么,用table显然是最好的。但如果不只需要打印,而是有很多其它需求的场景,实现多个相同接口的类就有优势了,很多的代码就可针对这个接口编程,从而复用代码。

  7. 看到评论中太多的无脑跟风吐槽, 我也忍不住吐槽一下.

    当我是Rob Pike, 并且自认为是Hacker的话,当我看到Joseph Bergin所描写的Hacker Solution时,也会忍不住吐槽.

    在看到Joseph Bergin最后举得例子,真是一个绝佳的吐槽点!

    话说回来, 如果实际需求仅仅是为了打印一句话, Joseph Bergin就采用这样重量级的方式, 他无愧于被称为大屎,
    可这篇文章的目的只是在教OO而已, 从OO入门的角度来说, 我认为他举得例子很不错,简单,易懂.

    从陈浩的观点出发, 这段代码也完全满足了” 1)中意于组合而不是继承 2)依赖于接口而不是实现,3)高内聚,低耦合”。

  8. 我想说这个例子只是个例子,OO大师的代码进化并没有什么问题。
    实际需求,不仅仅是输出一句话。

  9. 如何杀鸡?

    取我公孙大娘双剑,请君欣赏一场享誉千年之剑器舞……

    5个小时后:会杀鸡了吗?

    你怎么这么蠢呢。须知剑器舞脱胎于战阵、但去其肃杀、溶以舞技,看这个动作,稍微往后拉一点,多优美的自杀姿势啊!

    自杀都行,何况杀鸡耳!

    ——从此,国人杀鸡,必先舞5~10个小时,成功自杀3~5人不等之后,方可得享鸡肉。
    ——可惜,好景不长。因为据说厨师也开始持干戈舞了……

  10. 不管怎样都是一次温习~lz对问题的讨论和解析令人钦佩,oo是一种分析问题思想和解决问题方法论,它更结构化,但并不是万能的;真正能够从里面优雅的跳入跳出估计就是牛人啦;示例更贴近oo教学,所以偏颇极致些,离真正的应用还远~

  11. 大炮,机枪比苍蝇拍厉害多了,但如果用来打苍蝇,还是苍蝇拍好使。 OO是个好东西,但要用对地方

  12. mmxcraft :
    大炮,机枪比苍蝇拍厉害多了,但如果用来打苍蝇,还是苍蝇拍好使。 OO是个好东西,但要用对地方

    正解。。作为入门例子 其实还算可以,

  13. Tao :
    看到评论中太多的无脑跟风吐槽, 我也忍不住吐槽一下.
    当我是Rob Pike, 并且自认为是Hacker的话,当我看到Joseph Bergin所描写的Hacker Solution时,也会忍不住吐槽.
    在看到Joseph Bergin最后举得例子,真是一个绝佳的吐槽点!
    话说回来, 如果实际需求仅仅是为了打印一句话, Joseph Bergin就采用这样重量级的方式, 他无愧于被称为大屎,
    可这篇文章的目的只是在教OO而已, 从OO入门的角度来说, 我认为他举得例子很不错,简单,易懂.
    从陈浩的观点出发, 这段代码也完全满足了” 1)中意于组合而不是继承 2)依赖于接口而不是实现,3)高内聚,低耦合”。

    同意

  14. 第一种方法包装成一个单独的函数有什么不好呢,最后一种方法写那么多乱七八糟的横跨4个文件有什么意义?

  15. invalid :
    os_rank.ini
    [os]
    name=linux;windows;os x;free bsd
    [rank]
    linux=This is a UNIX box and therefore good.
    windows=This is a Windows box and therefore bad.
    读之,显示之,想怎么扩展怎么扩展……
    甚至,os_rank.json:
    {“windows”:{“rank”:”3″;”desc”:”This is a Windows box and therefore bad.”;”detail”:{“0″:16; “1″:12;”2″:”40″;”3″:”5″;”4″:”3″;”5″:”0″}}
    }
    读之,显示之,不光可以随意扩展,甚至总评3星、给0星16人、1星12人等等信息都能以[b]同样的模式[/b]显示出来。
    更进一步,引入meta data,不光可以随意扩展操作系统数目,甚至连里面的评价项目、项目数据、项目说明等等,都可以动态增加或者删除,然后以[b]同样的模式[/b]读取并显示出来。
    至于代码,利用json/ini库,读取如上文件并显示,至多50行代码足矣——其中包括详尽的错误提示、包括meta data支持。
    没错,无限扩展的实现,用“过程式”思维只需50行代码。

    invalid :
    os_rank.ini
    [os]
    name=linux;windows;os x;free bsd
    [rank]
    linux=This is a UNIX box and therefore good.
    windows=This is a Windows box and therefore bad.
    读之,显示之,想怎么扩展怎么扩展……
    甚至,os_rank.json:
    {“windows”:{“rank”:”3″;”desc”:”This is a Windows box and therefore bad.”;”detail”:{“0″:16; “1″:12;”2″:”40″;”3″:”5″;”4″:”3″;”5″:”0″}}
    }
    读之,显示之,不光可以随意扩展,甚至总评3星、给0星16人、1星12人等等信息都能以[b]同样的模式[/b]显示出来。
    更进一步,引入meta data,不光可以随意扩展操作系统数目,甚至连里面的评价项目、项目数据、项目说明等等,都可以动态增加或者删除,然后以[b]同样的模式[/b]读取并显示出来。
    至于代码,利用json/ini库,读取如上文件并显示,至多50行代码足矣——其中包括详尽的错误提示、包括meta data支持。
    没错,无限扩展的实现,用“过程式”思维只需50行代码。

    太赞了。最好写一个脚本语言解释器。然后就永远不需要更新代码了哦亲。无限扩展哦亲。纯“数据”驱动哦亲。只是配置文件维护起来比较困难而已。

  16. LZ的水平令我吃惊,请别自称老程序员
    “1)数据和其行为的打包封装,2)程序的接口和实现的解耦。”, 好吧,我承认你比鸡是鸟,公鸡母鸡也是鸟的OO前进了一步.
    至于为什么OO会成为主流,想破头也想不明白了吧,慢慢想吧
    INVALID的话说的虽然难听,但说的很对, 真没想到这么傻,还要祸害学生和刚毕业的新手

  17. ddou :
    关于OO的书籍也罢,博文也罢,大都拿简单的例子来阐述OO。太简单的例子,体现不出OO的好处,倒是暴漏了缺点:一大堆接口、类。太复杂的例子本身又把读者搅得头晕脑涨,偏离了主题。真是个两难的境地。 简单例子讲OO是让读者理解OO,而不是真教人拿OO解决这些小case。

    严重同意!

  18. invalid :

    qingfeng :
    @invalid
    关键还是看你驾驭抽象的能力了,能把业务模型抽象出来,之后用哪种工具或方法来实现,已经不重要了吧。就看你用哪种工具更熟练了

    Thoughtworks的那帮整天在别的公司骗顾问咨询费的骗子们就是这种OO的SB。
    但是OO大师不关心业务,不关心业务模型,更不关心如何抽象业务模型才能更加简练、精确的解决问题;至于未来的业务扩展能力,更加不会放在他的考虑范围之内——但这些,才是设计的真谛。
    他关心的,就是用OO写if——函数式连map-reduce都研究透了,他觉得用面向对象写if很值的炫耀。
    惊天动地的傻。

  19. 从最初级的过程编码 到后面的分解 通过增加代码的复杂性 提高灵活性, 虽然代码看起来臃肿, 不过这些是符合 OO 的思想吧,, 毕竟可以通过 继承那个接口 轻易的增加新的元素.. 而不用修改旧有代码的结构 而且不用担心影响旧有的功能…

  20. 原文中解释了所谓的hacker:“Their claim: It works doesn’t it what more do you want? Also I got mine implemented and working faster than any of the others, so there.”
    他认为hacker要的就是简单。
    我看这里争议的无非就是因为各自角色不同,因而对需求的理解不同。
    作者本想解释OO的好处,却反而被其简单的例子绊倒。作者加了个Mac只是想说明OO的方式具有有效的灵活性,当然其他解决方案很多,所以OO反显得“笨拙”。当然,作者并没有用hacker的思路来看待OO。
    可是,这里很多人还是从hacker出发在理解问题。

    我认为OO优势是其表现能力,如果文章不是给初学者看的,作者完全可以想一个更为复杂的例子来说明。通常,hacker的方案几乎无法让一般人看懂,而OO可以结构化地表现出来。

    对于追求可拓展性的开发者,有人提到使用meta data,可以用ini、json什么的来表示(其实,json也很OO的)。这是一种很好的方法,有时也可以用OO的形式表示出来,真的和OO思想没有半点冲突。但是,要注意的是meta data毕竟是data,没有行为,这更能说明OO的表现能力。至于陈皓的3个准则,我相当同意。总之,这就是个接口问题,Java支持interface,这玩意儿似乎真和OO没半毛钱关系,但若Java没interface,估计就半个垃圾了(换句话说,OO就没那么强大了)。

    什么if、while的,人家不是发疯,一般的程序确实没有必要,但如果你要做一个可以拓展的基于规则的OO程序,你首先想到的应该是OO化。即使使用其他格式的meta data,到程序里还是objects。

    有人在论什么愚蠢,我觉得不知道需求才是愚蠢!

  21. 我觉得那个例子很好,因为我真被无数超长超复杂的配置文件折磨过,这个例子是个简化了的案例,展示了利用语言的oo功能来分解逻辑的方法

  22. 这个例子展示了两个技巧:
    一是用数据结构查找代替分支判断,这一点上用动态绑定也是很常见的,但是本例没用是因为第二点,
    二是使用静态代码注册代替反射,在c++这类无法通过反射生成实例的语言里,这种方法尤为实用。个人也觉得如果靠配置文件去写数据对应类名,一来需要反射,代码可读性受影响;二来配置文件过于复杂不是好事,会腐化成一种可读性更差的源代码文件

  23. 如果getStatement方法非常复杂,涉及很多逻辑,不光光是返回一个可以用文件存储的数据,该咋办?
    人家只是为了说明一种解耦的方式,而举出一个简化的例子,我看是有些人误会了。

  24. @volcano
    如果……
    如果一切皆有可能,干脆就别写程序了……
    所有程序,都是在一定需求范围的前提下写就的,脱离开这个范围空谈扩展性兼容性前瞻性什么的就是OO顾问们的本职工作。

  25. 赞同一点 “设计模式原则和面对对象设计原则没有必然联系”,设计模式原则也许适合面向对象,但不只是只有面向对象才能用设计模式原则。
    面向对象有个思想让我很别扭,“一切都是对象”,这种思想让本来只是功能函数集合的类变成伪对象。
    而真正的对象是什么?

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注