Java 实战(二) - 编程思想概论

461 阅读9分钟

问题驱动 - 方法和函数的本质区别?

函数是对过程的抽象,而方法是依赖于对象的行为抽象,在最常用的数据集合变换场景下,更多的是需要与任何对象无关、纯粹的转换过程(函数),如map、filter,此时 Java8 以往的"接口-匿名对象"的方式就显得极为冗余而笨重,从而限制了上次api的发展。

上述问题揭露了 Java8 以前语言底层架构 - 编程模型的局限性,从而簇生了 Java8 函数式编程。仓库地址

java8-func.png

编程思想/模型,即允许开发者以何种方式组织或编写描述逻辑的代码Code,并最终由相应的编译器或解释器处理成计算机可执行的二进制流。

编程模型(思想)是不分语言的,但却是各类计算机语言的最底层,是语言级、框架级乃至上层应用级API的语法支持和思想支撑。主流高级语言总是尽可能的为开发者提供便于表达的多个编程模型,从而从多方面吸引开发者应用于商业级的项目。

multi-langs.png

面向发展的程序语言

程序本身是对现实的、发展中的物理世界的抽象和模拟,即程序总是从启动那一刻开始,就不断接收和响应来自内部或外部的各类事件或刺激,从一个状态发展到另一个状态,最终被终止。可见,程序是面向发展、面向变化的,而程序语言则是如何更好的描述和组织这些"发展",最终组成完整的程序体。从哲学的角度抽象,发展或变化可以用下面2种方式表述:

  • 物化表述

发展可以看作是物与物间的相互作用推动的,如人踢球导致球从静止态变为滚动态。

football.png

  • 过程表述

发展亦可以看作是由一个又一个阶段性、微小过程叠加的结果,如工厂产品总是经过多个流水线的阶段性转换、最终成为一个成熟的产品。

pipeline.png

上述这2种表述方式是对同一发展的不同解读或抽象,并无绝对的好坏、对错之分,高级语言如NodeJs等皆为开发者提供了这2种表述发展或逻辑的方式,即面向对象(物)编程OOP、面向过程OPP编程,从而让开发者可以自由根据场景或需要选择最佳的"表述方式",平衡开发的一致性和效率。

oop-vs-opp.gif

面向对象编程 OOP

Object Oriented Programme,是"物化表述"的进一步抽象和提炼,以结构化的对象 Object 作为一等公民,程序是由众多对象及其间的相互作用(方法Method)驱动的,程序本身作为最外层、整体的最大对象,最典型的语言如 Java。

  • 优点

    结构化、对象化是符合人的思维习惯的,利于组织和分类程序的不同部分,加上OOP内含的3大特性 - 封装、继承、多态 ,天然是为解决程序冗余而生的,因此 OOP 整体上、宏观上是非常适合工程化的,设计友好的

  • 缺点

    单纯的 OOP 是较重的,必须依赖于一等公民 对象Object 去定义方法, 并不适合阶段性的、微观化的过程或变化,这也是Java一直以来被人诟病过重的重要原因,如map映射、filter过滤等这些完全与特定对象无关,而仅仅是一个小微过程本身,因此更适合用过程表述,即OOP。

面向过程编程 OPP

Procedure Oriented Programme,是"过程表述"的进一步抽象和提炼,以函数作为阶段性的、微观的过程或变化的抽象,程序是由无数个过程函数累加的,并最终达到量变而质变的发展结果。

  • 优点

过程函数游离的,不依赖于任何对象的,因此自由度很高,可以在任何地方、任何粒度上定义并与其他函数组合(相互作用),非常适合阶段性的、微小的、对象无关的过程,如 map转换过程。

  • 缺点

游离的过程函数非常松散,对于开发者本身的"自觉性"要求极高,否则代码的可读性和可维护性都很难保证,非常不利于结构化、类型化的设计,且不能很好的模拟客观的物化世界,因此整体上是不利于工程化,设计不友好的。

vs.png

语言的进化

从以上简单对比可以发现 面向对象OOP 和 面向过程OPP 均有其优点和缺点,面向对象更适合宏观的、骨架、组件类的设计和封装,而面向过程更适合微观的、阶段性的拼接或过渡,真正的"好"语言应该同时提供这2种组织或表述逻辑的方式,事实上这也是目前主流高级语言的趋势。

  • 老生代语言的进化

由于历史原因和发展限制,老生代的C系语言如 Java、JavaScript 等,只支持单一的编程思想,如Java只支持面向对象OOP、Object 是一等公民,不允许有任何游离的变量和方法,而 JavaScript 只支持面向函数 OPP 、函数是一等公民,虽然基本可以达到完整"编写"一个完整、复杂项目的能力,但同时也会有相应单一模型的局限性,所以 Java 给人的感觉太过老重、细节表达能力不够灵活,而 JavaScript 或 Python 则又显得过于零散,冗余繁多,且不够对象化、结构化。

现在这些老生代C系语言也逐渐意识到了这种局限,并新版本中逐渐提供拟合对方的方案,如 Java 用单方法接口以及匿名对象模拟函数以逐渐弥补 面向过程 OPP 方面的不足,而 JavaScript 以及 Python 也都可以通过 prototype原型继承的方式模拟 面向对象OOP,甚至最终新增了单独的 class 关键字或语法糖以逐渐支持 面向对象OOP。

Java 函数式编程

java-func.png

ES6 class 类定义语法糖

js-class.png

  • 新生代语言的标配

以 Golang、Kotlin、Scala 为代表的C系新秀都明智选择了同时支持 面向对象OOP 以及 面向过程OPP,给予开发者以灵活性与设计性的平衡统一,编程体验更加顺滑。

以 Kotlin 为例,提供面向过程的函数式关键字 func,即可以任意地方、细节定义函数

fun map (){
  println("This is map function")
}

亦提供了面向对象OOP的关键字 - class,可以实现物化组件的封装

abstract class Person(open val age: Int){
    abstract fun work()
}

class MaNong(age: Int): Person(age){
    override val age: Int
        get() = 0
        
    override fun work() {
        println("我是码农,我在写代码")
    }
}

面向对象编程 OOP

以上分析可见,面向对象编程模型是结构化、设计友好的,因此是很多新兴高级语言的首选。

在 OOP 中,对象 Object 无疑是核心、首位的,是对发展的【物化描述】中物以及相互作用(行为,如踢)的抽象,因此对象等价于现实世界中的实体的概念。更抽象的说,物即对象,是表征和行为的集合,即 物/对象 = 表征 + 行为,如 🚗汽车 是由其表征壳子、轮胎、方向盘,以及其可以加油高速狂奔的行为共同决定了这是一个汽车🚗,而不是一颗静止的🌲。

car-run.png

上面的物的表征进一步抽象,即是语言中的专属名词--属性/字段,而物的行为即是面向对象中的另一专属名词 - 方法,以上表述转换成 OOP 的语言,即是

/**
 * 对象🚗 = 属性/字段 + 行为
 */
public class Car {
    /**
     * 车身
     */
    private String body;
    /**
     * 四个车轮
     */
    private String[] wheels;

    public void runOnRoad() {
        System.out.print("I am a Rolls-Royce,Go Away");
    }
}

car-obj.png

而这样一个描述对象的模板,即为类,通常由 class 定义,各语言通用。这样一个最简单的 OOP模型 就推演出来了,当然这样的 OOP模型 是简陋的,当复杂的系统中有成千上万个类和对象的定义时,仅仅这样简单的约束是不够的,很容易出现冗余、混乱,因此 OOP 天然内含**【封装】、【继承】、【多态】**三大特性,用于指导开发者如何更好的设计类和对象,从而更好的用好 OOP 模型,将在后续专门章节详细剖析。

面向过程编程 OPP

在 OPP 中,核心、一等对象无疑是过程函数 Function,是对阶段性、微小变化/发展过程的抽象。程序就是有无数个过程函数、组合叠加的最大函数,以 JavaScript 最为经典。

/*
* 过程(函数)驱动 
* */
function settupWheels (car) {
  console.log('setup wheels for car', car)
}

function fillGas (car) {
  console.log('fill gas for car', car)
}


function start (car) {
  console.log('run to 200km/h', car)
}

var car = {}

settupWheels(car)
fillGas(car)
start(car)

伴随着 函数式数论 的发展,函数式编程更是大大扩展了传统OPP的灵活性和易用性,逐渐被各语言所吸收,如 java以及JavaScript的 函数式编程等。

高阶函数 - Function 接口

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

/**
 * Returns a function that always returns its input argument.
 *
 * @param <T> the type of the input and output objects to the function
 * @return a function that always returns its input argument
 */
static <T> Function<T, T> identity() {
    return t -> t;
}

结论:1+1>2,非二选一

通过以上分析可以得出,面向对象 OOP 和 面向过程 OPP 有其鲜明优缺点,二者互补,共同构建工程友好、开发体验流畅的语言体系,正逐渐成为高级语言的主流趋势。在功能组件的封装上,使用 OOP 更利于结构化、设计化,而对于局部的、微观的"缝合"更适合灵活的过程函数 即 OPP。

例如 Stream 流式编程,Stream 对象抽象和定义一个转换流水线,而微观的、转换节点则由 过程函数 灵活定制

stream.png