`

如何思考面向对象

阅读更多

在学习了面向对象的语言,比如Java、Python和Ruby之后,看起来每个人都觉得自己在进行面向对象的编码。但是如果你仔细审视一下代码,你就会发现还是无意识地使用了很多过程语句。

 

静态方法

 

静态方法是最天然的过程方法,它和面向对象没有一点关系。好吧,我已经听见质疑的尖叫了,那么,我就来给你解释一下为什么。首先我们可以达成一个共识,全局变量和全局状态是魔鬼。如果你觉得前面说的静态方法的话会没什么可争论的,那好,我认为静态方法就应该返回一个常量,因为没有全局状态量(时间和随机数,这些都是全局状态量,所以不能算进去的,对象必须有不同的实例,但是对象图的连线是一致的)。

 

这就意味着静态方法要做什么有价值的事情的话,就必须要有参数的传入了。但是那样的话,我又会啰嗦一句,这样的方法应该简单地属于其中的某一个参数。举例来说,Math.abs(-3) 应该写成-3.abs()。实际上,并不是说-3非得是一个对象,但是有的编译器可认可这件事情,比如Ruby。如果你有一堆参数的话,不妨选择一个对方法影响最大的参数来做这个方法的调用者。

 

给静态方法一个最公正的定义:工具方法。假使你想要按照骆驼命名法来将“my_workspace”转成“myWorkspace”,那么绝大多数开发者会完成一个类似这样的方法来解决这个问题:StringUtil.toCamelCase(“my_workspace”)。但是,还是那句话,我可以给String类定义一个方法,方法调用变成了这样:“my_workspace”.toCamelCase()。当然了,在Java里面我们无法随意扩展String这个类,但是很多面向对象的语言是允许我们做到这一点的。

 

最后,我有时候(一年有几次吧)会因为语言的局限被迫写静态方法,但是静态方法实在是很难做单元测试,所以我尽量不用它。我发现在大多数项目中,静态方法的使用是很难得到合理控制的。

 

实例方法

 

现在你摆脱了这些静态方法,可是你的代码还是面向过程的。面向对象要求行为和数据是放在一起的。所以如果一个人不去理解代码的实际意义,就可以评估出代码是做什么的,那么通常他是看了行为和数据之间的关系。

 

class Database {
  // some fields declared here
  boolean isDirty(Cache cache, Object obj) {
    for (Object cachedObj : cache.getObjects) {
      if (cachedObj.equals(obj))
        return false;
    }
    return true;
  }
}

 

 

这段代码的问题在于,方法完全可以定义成是静态的!而且这个方法放错位置了,因为你可以看到,方法内部并未和Database这个类的对象做交互,而是使用getObjects()来和cache做交互。我觉得这个方法应该放在类似于一个“Cache”的类里面。那样的话,Cache类就不需要这个getObjects()方法了,因为这个for循环可以放到Cache内部去,Cache的状态量可以在方法内直接获取到了。嘿,我们这样做,简化了代码(移动了一下方法的位置,删除了一些多余的方法),皆大欢喜,多好。

 

有意思的是,getter方法经常意味着,你把数据放到这个类的外面去处理了(译注:参见这篇文章。换句话说,方法和数据没有放在一起。

 

class Authenticator {
  Ldap ldap;
  Cookie login(User user) {
    if (user.isSuperUser()) {
      if ( ldap.auth(user.getUser(),
             user.getPassword()) )
        return new Cookie(user.getActingAsUser());
    } else (user.isAgent) {
        return new Cookie(user.getActingAsUser());
    } else {
      if ( ldap.auth(user.getUser(),
             user.getPassword()) )
        return new Cookie(user.getUser());
    }
    return null;
  }
}

 

我不知道这段代码写得好不好,但是我很确定它的login()方法很喜欢user对象,它和user的交互大过和它自己内部状态量的交互,现在它看起来就像一个无名的数据仓库。我再说一遍,它违背了方法要和数据放在一起的原则。我觉得方法应该放在一个和它自己交互最多的地方,在这里,就是User类的对象中。现在我改成这样:

 

class User {
  String user;
  String password;
  boolean isAgent;
  boolean isSuperUser;
  String actingAsUser;

  Cookie login(Ldap ldap) {
    if (isSuperUser) {
      if ( ldap.auth(user, password) )
        return new Cookie(actingAsUser);
    } else (isAgent) {
        return new Cookie(actingAsUser);
    } else {
      if ( ldap.auth(user, password) )
        return new Cookie(user);
    }
    return null;
  }
}

 

好,我们取得了一些进展,可以注意到那些getter方法不见了(这个例子最终是要让Authenticator类消失掉),但是还有一些问题存在,这就是一堆“if”分支。我觉得Authenticator里的if (user.isSuperUser())这个判断带来了冗余,这样会要求重构后的代码增加一个isSuperUser这样的状态量。我现在一见到这样的标志量,我就知道,这里可以用多态的方式来优化:

 

 

class User {
  String user;
  String password;

  Cookie login(Ldap ldap) {
    if ( ldap.auth(user, password) )
      return new Cookie(user);
    return null;
  }
}

class SuperUser extends User {
  String actingAsUser;

  Cookie login(Ldap ldap) {
    if ( ldap.auth(user, password) )
      return new Cookie(actingAsUser);
    return null;
  }
}

class AgentUser extends User {
  String actingAsUser;

  Cookie login(Ldap ldap) {
    return new Cookie(actingAsUser);
  }
}

现在我们利用了多态,每个不同的User对象知道怎么去login,并且我们很容易就可以扩展一个新的用户类型到系统中。还可以看到,那些用来控制给予User不同行为的标志位都消失了,那一堆if分支也消失了

 

现在来看看这样的问题:User对象应该知道Ldap吗?实际这里存在有两个问题:

(1)User应该具备一个引用类型的属性Ldap吗?

(2)User应该在编译期就和Ldap建立依赖关系吗?

 

关于第一个问题,回答是:不。因为你可能想把user序列化到数据库中,但是却不想把Ldap序列化到数据库中。看这里

 

关于第二个问题,这就比较复杂了。总的来说,回答取决于你是否打算在不同的工程中重用User对象,因为编译期依赖在强类型语言中是过渡性质的。我的经验是每个人都想写某天可以重用的代码,但是那一天从未到来,并且那么做的人会陷入代码的纠缠之中,所以在实际需要重用之前,不要太过考虑这件事情(开发一个可重用的库另说)。我持有这样一个观点,有很多人为“假如”付出了太多的代价,但是并没有从“假如”中获益,所以,不要太过担心User对Ldap的依赖问题。

 

翻译自misko.hevery.com,原文出处:http://misko.hevery.com/2009/07/31/how-to-think-about-oo/

4
2
分享到:
评论
4 楼 RayChase 2012-02-03  
该用户名已经存在 写道
个对象要求既有行为又有数据,这样才是完整的面向对
RayChase 写道
该用户名已经存在 写道
“面向对象要求行为和数据是放在一起的”。行为和数据分离不更好么?

就像之前我说的贫血模型和充血模型一样,所有的“好”和“不好”都是相对而言的,不同的场景下你可以选择不同的标准。
这里单纯从面向对象的角度看,一个对象要求既有行为又有数据,这样才是完整的面向对象。否则,我们把只有行为的“对象”叫做工具方法;把只有数据的“对象”叫做数据容器。


有道理,其实现在很多设计都是将行为和数据分开的,像Service,Dao,Util大多都是一些工具方法,数据全都是封装到Model里面,这样的分层设计似乎俯拾皆是,暂且不敢说其好坏与否,看了火哥的文章,只是感觉我们离“面向对象”已经渐行渐远。

首先,对于贫血模型的说明,不妨看看Martin Fowler自己的说法:http://martinfowler.com/bliki/AnemicDomainModel.html
其次,我这篇文章也对此有介绍:http://raychase.iteye.com/blog/1328224
3 楼 该用户名已经存在 2012-02-03  
个对象要求既有行为又有数据,这样才是完整的面向对
RayChase 写道
该用户名已经存在 写道
“面向对象要求行为和数据是放在一起的”。行为和数据分离不更好么?

就像之前我说的贫血模型和充血模型一样,所有的“好”和“不好”都是相对而言的,不同的场景下你可以选择不同的标准。
这里单纯从面向对象的角度看,一个对象要求既有行为又有数据,这样才是完整的面向对象。否则,我们把只有行为的“对象”叫做工具方法;把只有数据的“对象”叫做数据容器。


有道理,其实现在很多设计都是将行为和数据分开的,像Service,Dao,Util大多都是一些工具方法,数据全都是封装到Model里面,这样的分层设计似乎俯拾皆是,暂且不敢说其好坏与否,看了火哥的文章,只是感觉我们离“面向对象”已经渐行渐远。
2 楼 RayChase 2012-02-03  
该用户名已经存在 写道
“面向对象要求行为和数据是放在一起的”。行为和数据分离不更好么?

就像之前我说的贫血模型和充血模型一样,所有的“好”和“不好”都是相对而言的,不同的场景下你可以选择不同的标准。
这里单纯从面向对象的角度看,一个对象要求既有行为又有数据,这样才是完整的面向对象。否则,我们把只有行为的“对象”叫做工具方法;把只有数据的“对象”叫做数据容器。
1 楼 该用户名已经存在 2012-02-02  
“面向对象要求行为和数据是放在一起的”。行为和数据分离不更好么?

相关推荐

    漫谈设计模式:从面向对象开始(带书签扫描版).刘济华.pdf

    本书主要从最基本的设计模式入手,并结合一些J2EE开发过程经常遇见的技术和概念,你将全面理解这10多个设计模式,并在开发过程中,让你真正体会和思考面向对象编程的思想,也只有掌握这些,你才会能成为一位真正的...

    设计模式:可复用面向对象软件的基础--详细书签版

    ”)的应用经验和体验后,你将用一种非同寻常的方式思考面向对象设计。你将拥有一种深刻的洞察力,以帮助你设计出更加灵活的、模块化的、可复用的和易理解的软件—这也是你为何着迷于面向对象技术的源动力,不是吗?...

    面向对象的思考过程(英文版).zip

    这个压缩包文件《面向对象的思考过程(英文版)》很可能提供了一个深入探讨面向对象设计原则、概念和技术的详细指南。 1. **对象与类**:对象是OOP中的核心单元,它包含属性(也称为成员变量或字段)和方法(成员函数...

    设计模式可复用面向对象软件的基础

    ”)的应用经验和体验后,你将用一种非同寻常的方式思考面向对象设计。你将拥有一种深刻的洞察力,以帮助你设计出更加灵活的、模块化的、可复用的和易理解的软件—这也是你为何着迷于面向对象技术的源动力,不是吗?...

    基于Java语言的面向对象编程教学新方法探究.pdf

    + 鼓励学生思考面向对象编程的优势、适用场景和设计原则 * 引入案例研究和真实应用场景 + 通过案例研究和真实应用场景,帮助学生将所学知识应用于实际情境中 3. 实现基于Java语言的面向对象编程教学新方法的优势...

    漫谈设计模式:从面向对象开始

    本书主要从最基本的设计模式入手,并结合一些J2EE开发过程经常遇见的技术和概念,你将全面理解这10多个设计模式,并在开发过程中,让你真正体会和思考面向对象编程的思想,也只有掌握这些,你才会能成为一位真正的...

    修练8年C++面向对象程序设计之体会

    ### C++面向对象程序设计之体会 #### 一、引言 本文作者通过分享自己八年来使用C++进行面向对象程序设计的心得体会,探讨...本文旨在启发读者思考面向对象编程的本质,鼓励大家在实践中不断探索和完善自己的编程技能。

    面向对象的思维过程

    但读完这些书之后,许多人忘记了这些主题都是建立在一个基础之上的:你是如何思考面向对象方法的。不幸的是,许多软件专业人员花费了大量时间在这些书中,但却没有努力真正理解包含在它们之中的思想。我主张学习面向...

    面向对象编程思想

    面向对象编程思想 面向对象编程思想是当前计算机界关心的重点,它是 90 年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用...

    面向对象技术-1

    - **面向对象方法学**:面向对象方法学是一种新的思考方式,它改变了传统的编程思维方式,更加接近于人类自然思维习惯。面向对象方法学主要包括面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)三个...

    面向对象设计理论

    下面将详细阐述面向对象设计的基本概念、原则以及在两个文档——"面向对象分析与设计"和"面向对象的思考过程"中可能涵盖的关键知识点。 1. **面向对象的基本概念**: - **对象**:对象是类的实例,具有属性(数据...

    设计模式可复用面向对象软件的基础(中文版)

    ”)的应用经验和体验后,你将用一种非同寻常的方式思考面向对象设计。你将拥有一种深刻的洞察力,以帮助你设计出更加灵活的、模块化的、可复用的和易理解的软件—这也是你为何着迷于面向对象技术的源动力,不是吗?...

    面向对象的软件构造.pdf

    3. **认识论原则**:面向对象编程鼓励开发者从现实世界的角度去思考问题,并将这些想法映射到软件设计中。这意味着将实体和行为抽象成类和对象,以便更好地模拟真实世界的现象。 4. **分类技术**:在面向对象编程...

    写给大家看的面向对象编程书(第3版).pdf

    书中结合代码示例生动透彻地讲述了面向对象思想的精髓,让读者真正学会以对象方式进行思考。此外,《写给大家看的面向对象编程书(第3版)》还讨论了各种与面向对象概念密切相关的应用主题,包括XML、UML建模语言、...

    python面向对象练习题.pdf

    ### Python面向对象编程知识点解析 #### 一、面向对象三大特性及作用 **1. 封装** 封装是面向对象编程中的一个重要概念,它通过隐藏类的内部细节并对外提供公共接口来保护数据的安全性和完整性。封装使得类的使用...

    信科专业面向对象程序设计课程的教学改革与实践.pdf

    在分析案例的过程中,教师应引导学生思考面向对象设计在问题解决中的作用,让学生在解决实际问题的过程中理解面向对象的设计原则,从而达到理论与实践的无缝对接。 此外,加强实践教学同样是教学改革的重要方向。...

    测试驱动的面向对象软件开发

    测试驱动的面向对象软件开发是一种将测试作为软件开发过程中核心组成部分的方法论。这种方法结合了测试驱动开发(TDD)和面向对象编程(OOP)的优势,旨在提高代码质量、增强可维护性和降低后期修复错误的成本。 ##...

Global site tag (gtag.js) - Google Analytics