面向对象编程为什么没有在科学计算领域获得普及？
在实施OOP时出现的毛病有： 迷信面向对象设计。偏执采用的面向对象设计，往往导致数量庞大的class定义。有时候为了保持面向对象的洁癖，每个类型重又包含繁杂零碎的方法。更为麻烦的是各种类之间琐碎的关系。这些复杂零碎的类型网络，对自己和协同开发者造成极大的困难。 C++是一个面向对象的语言，但是面向对象设计并不是一个万能钥匙，在很多情况下也并不见得是合适的选择。一个显然的趋势是，各种语言都逐步引入了函数式编程成分，其目的之一也是避免上述麻烦。比如Lambda的引入使得我们不必定义大量的短函数，tuple也使得一些小范围使用类型不再需要被显式定义。 提前设计和过分设计是迷信的另一个严重表现。复杂软件的设计并不是一蹴而就的，我们不可能在一开始就构造好一个框架，然后逐步的添加细节。这种提前设计会导致再后续由于未预料的问题而不断的打补丁，使得最初的设计变成一个“缝合怪”。这仅仅是因为我们无法一开始就理清全部的关系。过分设计的幼稚表现是，即使完成一个简单任务（比如测试验证）也要写一堆 class，带有一堆的成员方法和操作符重载。 封装造成隐含修改与状态记忆。面向对象设计中的关键在于封装，其目的是对数据和实现细节的隐藏。对象的方法在执行中往往会修改自身的成员变量。问题在于，这种修改是隐含，即从方法本身的定义（即函数原型）无法得知哪些成员会被修改。这些信息隐藏在函数源代码中，无论是文档还是开发者的记忆都是不可靠的。此外，由于对象有状态记忆，因此对方法的执行产生影响，使得方法对于相同输入不一定能产生同样的输出。这两个问题给排错和测试带来特别大的困难。   科学计算本身往往是一个比较单纯的数学模型，一切都可以采用数学函数来描述。数值模拟的流程无非是以给定的网格坐标及其拓扑关系，流动条件和初始场，将数据输入到一系列的数学函数构成的处理管道，得到最终的结果。另一方面，其中的数学函数又非常复杂，涉及的基本操作和变量很多。因此单纯的采用面向对象思路是不合适的，退回到结构化编程更不可能。 我认为科学计算的特点比较切合于函数式编程的思路。但是，由于开发者的技术背景和项目的性能要求都不能容许采用函数式语言来设计。不过函数式编程的“无副作用的纯函数”哲学对于科学计算软件设计式是非常有启发的。   以下是本人在实际工作中总结的一些看法 1. 属性与数据隔离 在PDE求解中一个常见的设计方式是，将一个网格设计为一个类BlockType，包含网格的属性（尺寸、类型等）和网格中的场数据（坐标、几何度量、物理场等）。这是一种很自然的设计方案，在我们早期的Fortran版本程序中采用。 但是这种自然思路的设计还是带来了很大的麻烦。一方面，BlockType类型显得特别臃肿，包含大量的成员数据。另一方面，随着程序功能的扩展，需要增加新的场数据（比如新模型引入的场变量），这就需要改动网格类型的定义。当多人团队协作时，会有多个开发者同时改动BlockType的内部成员定义，很容易造成冲突。因此不得不发展了一个管理动态成员数组的模块，可以在运行时增加新的属性和数组。 因此后来在设计中，在BlockType中只存储网格的关键属性，不存储的场数组。由于网格关键属性不易变，因此几乎不会造成扩展上的困难。场数组另外存储也使得扩展需要的数组开辟完全不受约束。  在上面这个例子中，早期的BlockType类型设计也将场数据和相应的方法都封装起来。因此造成两者的强绑定。例如计算扩散量方法flux，只能采用数组w和s计算出残差rhs。这种呆板的强绑定，导致我们不能给flux方法输入其它数组。同时这样的设计也导致了前面所提到的隐含修改问题。 显然更灵活的方式是将w、s和rhs作为flux函数的哑元参数，实现flux与这些数组的绑定解除。同时也解除了flux对着作为成员的w、s和rhs的隐含修改。在新的设计中，w、s和rhs这类场数组已经从BlockType剥离，invflux这样的函数也被剥离成为独立函数。因此在计算某个网格Block的耗散通量时，BlockType对象以引用方式传递以便flux函数获取网格Block属性，并且根据这些属性来访问场数组w、s和rhs。 这就是所谓的方法与数据隔离。  为了完全避免前面提到的封装造成隐含修改与状态记忆，除了前面提到两个隔离原则，还需要确保方法无记忆，即仅仅根据输入来得到输出，不隐含修改全局变量和内部静态变量。这样的函数被成为纯函数。 在新的设计，几乎所有功能都以纯函数形式提供。从每个函数原型上可以轻易看出输入输出关系，这将大为减轻维护与查错的负担。  本人并非抵制面向对象设计，而是提倡在软件设计中不盲目迷信面向对象。在前述经验和原则的基础上，适度和合理的封装，仍然是有益的。 4.1. 隐藏复杂操作细节，展现组合关系 某些操作过程很复杂，在实现中被分解为多个子操作。但是对于App开发者来说，并不需要了解这些中间过程。同时为了在逻辑上更为明显的显示这些操作的关系，可以将它们封装在一个类中。通过少数几个公开方法作为接口，众多的子过程则作为私有方法。这样的封装， 如setGridMertic类就是一个例子。因为从顶点坐标计算几何度量需要较多的步骤（计算、外推、交换、变换等），因此就把blockcfd::metric命名空间中的基本函数调用封装起来，所有的子操作都作为私有方法，仅仅提供一个()操作符重载作为唯一对外接口来完成一个多块网格的几何度量计算。  对于多块区域的数据传输而言，完全实现上述无状态记忆的纯函数操作就不合适。因此设计了多个类的协作来完成这一功能。但是这个设计中，仍然没有把所有的方法都封装在类中，而是先实现了一些基本功能的纯函数，然后在此基础上设计多个类。一个主要的考虑是在某些特殊情况下，我们也许还会使用这些基本操作来完成特别的数据传递。  在上述原则的叙述中，已经展现出某种层次化的设计思路。即先实现一些基本操作的函数，在此基础为了隐藏细节又设计了一些类作为App开发的接口。当然，在这些类之上，就可以开发各种App了。    至少在我国，还有一些最重要的现实问题。 这样的情况下，OO设计是没法一蹴而就的，也没法执行严格的开发管理。因此我在实践中，只能要求我的合作者暂时放下OO的执念，先依照数学原理把函数库写出来，做好测试，完成初步的结构化程序集成。然后再以函数库为底层基础，将结构化程序中的上层逻辑中必要的部分重构为类，就得到了一个不那么讲究的类库。后续继续迭代重构，逐渐的提升。   