鹏云:解析面向对象编程的正确姿势

向作者提问
世界500强公司系统架构师,曾参与多款云计算系统的研发,对面向对象编程思想,敏捷开发及DevOps有着深刻的理解。
查看本场Chat

2018年4月9日,周一晚8点30分世界500强公司系统架构师,曾参与多款云计算系统的研发,对面向对象编程思想,敏捷开发及DevOps有着深刻的理解的鹏云带来了主题为《面向对象编程的正确姿势》的交流。以下是主持人hrshy整理的问题精华,记录了作者和读者间问答的精彩片段。


内容提要:

  • 如果游戏需要增加汉字打字功能:每个汉字5分;以倒 S 型路径下落。文中两种设计方案对这个需求变化的响应度如何?
  • 适合刚入门Java编程的小白吗?
  • 做项目的时候总写着写着就面向过程来做了,怎么培养面向对象的思维方式,在写项目时是先抽象面向对象还是先面向过程在优化的时候再面向对象(JavaScript)?
  • 如果某几个对外提供的接口,需要使用类A的部分字段内容,是否一定要为每个接口定义特定的类?
  • 在掌握语法的情况下,如何从面向过程的思维转化为面向对象思维,也就是如何训练面向对象的思维?
  • 能不能讲讲实际的设计中怎样将面向对象的思想转化为设计的输出物?
  • 项目是维护一个很老的代码。现在任务也是增加一些功能,但是仍然是调用他们的基类,请问这种情况下,写新代码时候怎么用上面向对象的思维?
  • 如果需要修炼面向对象能力,建议从那些方面入手学习?
  • 在对新系统进行设计时,建议先设计类对象还是先确定数据库存储结构好?为什么?

问:如果游戏需要增加汉字打字功能:每个汉字5分;以倒 S 型路径下落。文中两种设计方案对这个需求变化的响应度如何?

答: 我们先来看第一种方案的实现。增加汉字,即是增加了一种字符类型,显然是属于字符的子类。我们的设计可以是这样的:

图1

在当前设计中只需要扩展一个地方即增加Chinese类,然后改动一个地方,即CharGenerator。这显然是符合面向对象一个很重要的设计原则——开闭原则,即设计方案应该对修改封闭,对扩展开放。

我们再来看第二种方案。在这个方案里,我们新增的汉字类型,是在Char概念范围内的,所以我们不必增加字符子类。但是汉字的移动路径是倒S型,这是一种新的Dropper类型,所以我们需要增加一个Dropper子类。方案是这样的:

图2

也是两个地方,增加一个Dropper类,修改一个CharGenerator类,也符合开闭原则。

那这两种方案哪个更好一些呢?

其实这里我们就需要对每个方案中各个概念的职责做整理了。第一种方案中Char既代表字符本身,还包含了分值,又决定着字符的移动路径。第二种方案中,Char代表字符本身,包含着分值,但将移动路径的职责委托给了另一个类即Dropper。这显然使得Char更内聚,Dropper的职责也很明显。所以我觉得第二种方案更好一些。


问:适合刚入门Java编程的小白吗?

答: Java是很多同学的第一门面向对象设计语言。我觉得在学习Java、 C#或Python前,掌握正确的面向对象方法观是非常重要的。现在很多Java课程或书记都更倾向于讲授Java语法,对面向对象只做很少的铺垫,其实是有问题的。这也造成了今天非常多的程序员还是用面向对象的语言,写着面向过程的程序。但说实话,要正确理解和使用面向对象,也不容易。

以我的经验看,你可以先掌握语法,然后投入到真实的项目中,让自己深陷焦油坑,痛苦+绝望。这时候就很关键了,有的人可能认为编程本来就应该这样苦逼,然后便一直陷在技术里面。这时候我们一定要思考,有没有更好的方式解决项目中这些问题。多问问自己,为什么我的代码总是牵一发而动全身。然后你再来看面向对象的这些思想,理解解耦,封装,抽象。这些都是良药。但很遗憾,我看到很多程序员,已经认为苦逼+加班是程序员生活的常态。缺乏去思考或改善自己的设计水平,或只喜欢去钻研具体的技术,比如分布式缓存,数据库等。但其实很多项目中的问题都是设计问题造成的,具体的技术只能缓解症状,但根因在架构设计。

回到这个问题根本。一开始掌握正确的面向对象方法,或者起码在心里知道这一块才是Java的精髓是很重要的。而本文是从“道”的层面告诉大家,面向对象是怎么一回事,它看问题的角度和过程式到底有哪些根本的区别。


问:做项目的时候总写着写着就面向过程来做了,怎么培养面向对象的思维方式,在写项目时是先抽象面向对象还是先面向过程在优化的时候再面向对象(JavaScript)?

答: 培养面向对象的思维的核心就是站在问题领域看用纯类来表达和解决问题,有个词叫Plain Old Java Object(POJO)。然后再考虑持久化,界面显示等技术性问题。当前火热的DDD就是将这个过程体系化了,有一整套方法论来告诉大家如何一步步这么做。

关于“在写项目时是先抽象面向对象还是先面向过程在优化的时候再面向对象”,当然是一开始就面向对象。面向过程和面向对象是两种完全不同的范式,不可能从一种优化到另一种。除非推倒重来。


问:如果某几个对外提供的接口,需要使用类A的部分字段内容,是否一定要为每个接口定义特定的类?

答: 这个问题也是站在实现的角度在看问题,不过如果一个接口只使用一个类中的部分字段,很可能是这个类设计的有问题,不够内聚。考虑将这个类的职责拆分到几个类中去,可能这个问题也就迎刃而解了。

那就是用户类承载的职责过多了。这会面临两种情况。

一种是同一个概念,在多个的上下文中使用。导致职责过载。所以在DDD里有一个概念叫Bounded Context(限界上下文),即将一个类限定在一个特定的上下文中,这样他的职责便受到约束了。比如在认证上下文中,我们只关注用户的登陆名和密码。而在财务管理上下文中,我们会关注用户的职级,工资等。第二种情况是,如果所有这些信息本来就在一个上下文中,我们可以通过值对象来传递这些部分的值。即将一个实体对象包含多个值对象。这也是DDD里面的典型实践。


问:在掌握语法的情况下,如何从面向过程的思维转化为面向对象思维,也就是如何训练面向对象的思维?

答: 只有在具体的项目实践中练了。推荐几本书《设计模式解析》(可惜已绝版)、《敏捷软件开发——原则、模式与实践》,还有就是《领域驱动设计》。前两本比较通俗易懂,《实现领域驱动设计》也很好。


问:能不能讲讲实际的设计中怎样将面向对象的思想转化为设计的输出物?

答: 我推荐的是在白板前研讨。通过UML简单表示设计的意图和想法就可以了。一些重要的设计可以拍照存档,但不需要有一个重量级的过程专门设计,然后编码。

因为在项目的任何一个阶段,我们对需求的理解都是不完整的,所以要强调迭代。

简单来说就是多沟通,项目组成员间,与客户之间。然后深入理解领域概念,用代码实现,很多时候实现的时候又会发现一些细节理解不够清晰。再回到沟通环节,这便是一个迭代循环了。


问:项目是维护一个很老的代码。现在任务也是增加一些功能,但是仍然是调用他们的基类,请问这种情况下,写新代码时候怎么用上面向对象的思维?

答: 这便是项目重构的问题了。建议对于一些与现有功能耦合性不是很强的功能,逐渐剥离。其实用了面向对象的思维,也要涉及修改的问题,面向对象最大的好处便是让我们的代码可以更容易得适应需求的变化。之所以老代码需要改或重构,很多时候就是因为它没有经过很好的设计,也就是修改成本很高。这是同一个问题的两面。面向对象不能保障你一开始的设计就是最好的,但面向对象能在你对问题的认识更深刻后,很容易对现有的设计进行修改或扩展。


问:如果需要修炼面向对象能力,建议从那些方面入手学习?平时都是面向过程编程。

答: 我介绍一下我自己的经历。最早也是只顾实现功能,根本不care什么设计。然后便陷入了项目焦油坑,加班快加废了,便开始思考有没有更好的做法。于是便看了《设计模式解析》那本书,感觉如获至宝。书中讲了很多面向对象设计的原则,及常用的设计模式。但当掌握了这些原则后,问题便来了,发现自己不会写代码了。因为每写一个类或方法,都发现自己触犯了一些原则。当时快疯了!然后又读了Bob大叔的 《敏捷软件开发——原则、模式与实践》。终于,Bob大叔让我理解了如何将这些原则调和在一起,让其和平相处。不过当时这些理解还是都停留在代码层面,总感觉有一层窗户纸还没有捅破。再后来便又读到了DDD,哇,发现打通了,从需求,到设计,到实现。不过要掌握,关键还是要在实战中多用,才能又更深刻的体会,才会锻炼自己的微观体感。


问:在对新系统进行设计时,建议先设计类对象还是先确定数据库存储结构好?为什么?

答: 建议是前者。后者只是通过一个具体的技术对对象的状态进行保存而已,类和对象才是程序的关键。当类和对象确定后,具体的数据存储是可以有很多种实现方式的,你可以存储在数据库里,也可以存在文件里,也可以存在NOSQL中。对于一些测试需求或演示DEMO,你甚至可以将其存在内存中。比如用MAP来存。所以围绕问题领域,寻找概念,然后确定职责,设计类和对象。最后才是如何显示,如何存储,这些技术细节。


本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

微信扫描登录