前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何思考面向对象

如何思考面向对象

作者头像
四火
发布2022-07-15 19:48:44
1930
发布2022-07-15 19:48:44
举报
文章被收录于专栏:四火的唠叨四火的唠叨

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

静态方法

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

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

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

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

实例方法

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

代码语言:javascript
复制
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 方法经常意味着,你把数据放到这个类的外面去处理了(译注:参见这篇文章)。换句话说,方法和数据没有放在一起。

代码语言:javascript
复制
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 类的对象中。现在我改成这样:

代码语言:javascript
复制
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 这样的状态量。我现在一见到这样的标志量,我就知道,这里可以用多态的方式来优化:

代码语言:javascript
复制
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/

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档