如何理解面向对象编程

总算来谈下这个烂大街的话题了。

面向对象一般会谈继承封装多态,但列举一门理论具备什么特征并不能帮助程序员合理地应用这门理论。

在传统面向数据和过程的编程方式中, 我们关心的东西有

  • 特定结构的数据
  • 针对数据定义的行为
1
2
struct {} A;
void function act(A a);

实话说,这样的编程手段对于解决问题本身已经够用了。

但对于现代化的软件工程项目,为了更好地解决开发效率与开发成本等问题,
我们需要引入一种更加有效的描述方式。使得开发任务更加顺畅, 开发成本能够有效降低。

java 界似乎在远古时期宣扬了一种基于 class 的编程方式,并将这种行为称作 oop 的有效实践。
而我们知道, 那端时期 java 界所宣扬的从来就没几个是靠谱的。

首先要摒弃所谓的 class 方式就是面向对象。
即使是 javascript 这种基于原型的编程语言,也是满足面向对象原则的。

只是纯粹描述数据本身的东西, 我们称之为 struct(结构体), 它具备了类型和实例两个内容。
而面向对象,则需要在结构体之上,增加一个可以描述行为的方式, 这种方式一般叫做 method (方法)
这里方法虽然看起来和 function 类似, 但是它是提供了数据绑定的, 而函数只和输入输出相关,不关心状态。

这个东西从实现上,仅仅是个手法而已,和上文所提到的结构体和函数的接合可以达到同样的目的。
现代化的编程语言,正是通过这样的变换手法,优化编程的模型而提高生产力的。

使用面向对象方式编程的开发者,关注的是某一个对象,它所具备的行为(method)。

好吧,因为类型的扩散,我们还是得考虑多种类型之间的交互, 和前面说的 函数 + 结构体 的方式,
差别并不大。
为了优化这中情况,程序员能做到的无非就是简化传入参数中的类型,尽量使用平坦的数据类型.

1
2
3
4
Name n = new Name("John");
Person p = new Person();
p.setName(n);
p.sayName();

上面的这个例子里, 我们倾向于认为 Name 是一个简单的类型, 而 Person 则是一种具有复杂行为的
高级类型。
但是,也正是面向对象语言提供的便捷手段,可以通过 封装 手段解决这个问题。

1
2
3
//JhonPerson extends Person
Person jp = new JhonPerson();
jp.sayName();

面向对象的本质在于 消息传递, 方法调用 不过是类 java 语言中的一种实现。
为了简化开发任务, 通过扩展组合等手段, 可以轻易地将复杂行为封装到新类型中,但是提供了相同的接口。

这个例子中, 我们可以体验到面向对象编程中的封装和多态.
封装重用了数据, 而多态重用了行为.

面向对象语言过度地提到继承, 似乎所有关联任务都可以通过 extends 来解决。
而我们知道,对象之间的关联关系, 扩展基类意味着需要继承行为和数据,是一种垂直的继承。
为了打破这种限制, 提供能够横向扩展的能力, java 引入了 接口 ,设计模式则提到了 组合.

前面提到,方法是一种和类型绑定的行为,如果只是垂直的单继承体系,多态只能在继承类型之间实现。
为了能够满足不同类型之间能够完善地支持多态,接口的引入解决了这个问题,但是,事情并没有能够完美地结束。

java 和其他类似语言中,由于函数并不是语言中的第一公民,接口仍然是一个具体的类型,只不过进行了一次间接的多继承.
到这里, 我们还是没能够得到我们希望的, 完全基于行为的多态。

在动态语言中,方法的调用是动态进行的,近似于在运行时直接查找同名方法,因此不存在这个问题。
而在静态语言中实现了类似行为的有 go , 虽然最终因为缺少泛型而导致了 interface {} 满天飞。

好不容易脱离了类型和方法的限制,又回到了如何解决方法和参数类型之间的绑定关系.