0.前语

软考的题目中通常会触及结构化和面向对象两种理念,让大家选择哪种更适合杂乱的、大型的、需求多变场景。刷过题的都知道要选面向对象,但为啥要选面向对象呢?

教材、真题中还大量提到结构化思维等同于面向过程编程,面向对象思维对应面向过程编程。这些概念之前都是生背,好像理解了,但也说不清楚为什么。

在读完《软件规划的哲学》这本书后,得到一些启发。

1.引子

本文中的事例思维来自《软件规划的哲学》这本书书

假如要做一个文件读写的模块(这儿的文件是内部有必定杂乱结构的文件,例如配置文件、excel文件之类,而非简略的无结构文本),咱们要做的事很简略,将文件读取出来、对读取出的内容做一些修正、保存文件。
顶层流程必然这样规划:

graph LR
读取文件 --> 处理文件 --> 保存文件

根据上述流程会规划三个中心类和一个操控类,类结构图如下:

classDiagram
Main -->Reader
Main -->Processor
Main -->Writer
class Main {
main()
}
class Reader{
+read()
}
class Processor{
process()
}
class Writer{
write()
}

上述几个类的职责十分明确,Main做主控、Reader读文件、Writer写文件、Processor处理文件。

2.问题

至今为止,一切都十分合理,咱们用的是面向对象言语,也用到了类,但这真的是面向对象吗?

让咱们深化考虑Reader和Writer两个类的中心流程:

  • Reader.read()
graph TB
翻开文件 --> 以二进制方法读取文件内容 --> 将文件内容转换为程序可读的内部数据结构
  • Writer.write()
graph TB
将程序生成的数据成果转换为文件的二进制内容 --> 写入文件 --> 封闭文件 

上面两个步骤中,RederWriter需求将二进制文件转为内部数据结构、或许将内部数据结构转为二进制文件内容,即两者都需求知晓文件的内部结构

上述两个类,尽管将文件结构封装到内部,但依然存在一些常识的共享,假如开发ReaderWriter的的两个人,那么就存在一些交流成本,即需求一致文件结构,开发完成后进行联谐和修订。这样明显有问题,那么问题在哪里呢?

咱们能够对类结构进行重构,将与文件结构相关的内容剥离到公共类中,公共类担任文件结构的encode和decode:

classDiagram
Main -->Reader
Main -->Processor
Main -->Writer
Reader --> Codec
Writer --> Codec
class Main {
main()
}
class Reader{
+read()
}
class Processor{
process()
}
class Writer{
write()
}
class Codec {
decode()
encode()
}

问题好像是解决了,但类变多了(对于Java来说也不是什么大事,Java写代码类便是多)

咱们考虑下这个功用的演进:现在需求添加V2版别,V2版别功用更多、并且与V1版别的存储结构不兼容,那么CodeC就需求笼统了:

classDiagram
Main -->Reader
Main -->Processor
Main -->Writer
Reader --> Codec
Writer --> Codec
class Codec
<<interface>> Codec
CodecV1 --|> Codec
CodecV2 --|> Codec
class Main {
main()
}
class Reader{
+read()
}
class Processor{
process()
}
class Writer{
write()
}
class Codec {
decode()
encode()
}
class CodecV1 {
decode()
encode()
}
class CodecV2 {
decode()
encode()
}

完成上述结构调整后,Reader和Writer类的内部代码要改吗?或许有两种答案:

  • 答案1:需求修正,由于需求根据类型构造Codec的实例
  • 答案2:不需求修正,由于上述Codec实例能够由Main注入,或许Spring等机制动态注入

其实答案1和答案2都是修正,仅仅修正点不同。答案2或许更差,由于文件的版别和Main函数其实是不要紧的,它不需求知道这种常识

回想上面的过程,咱们用的是面向对象言语、也有类,类之间还有关联关系、也有公共笼统,但这便是最好成果吗?

尽管文件结构都躲藏起来了,但对于MainProcessor两个类来说,依然能够见到Codec这个类,也知道文件有encode()encode()这两个函数,更重要的是假如未来文件结构、存储方法发生改变,那么ReaderWriterCodec这三个类要一起改变,明显违反了面向对象中的敞开封闭准则

3. 根因

面向对象规划最根本要解决的问题是信息躲藏,对外露出的信息越少越好,那么程序结构将越稳定,需求改变后修正的规模会越少。

上面问题的跟进,是在用面向对象的言语对结构化规划的成果进行完成

假如将文件读写都放到同一个类中完成,那么文件结构将不再对外露出,所有关于文件读写、文件结构的改变将封装在文件类中:

classDiagram
Main -->File
Main -->Processor
class Main {
main()
}
class File{
read()
write()
}
class Processor{
process()
}

main()函数的调用流程能够看成这样:

graph LR
File.read --> Processor.process --> File.write

流程不变,但整体变简略了许多,Main需求、也仅仅能看到自己关怀的模块和函数。
至于File,能够是一个模块、也能够是一个类,内部的文件结构改变再与外部无关。

4.结论

个体以及团队的认知才干都是有限的,在有限的认知才干水平下,只有更好的躲藏不必要对外的信息,才干做到更快的迭代演进,这也是面向对象规划能应对杂乱的、大型的、需求多变场景的根本原因。

能否应对杂乱的、大型的、需求多变场景与言语自身无关,只与结构、信息躲藏有关 ,即使是C言语,也能通过合理的模块规划、合理的动态/静态链接库封装完成上述意图。