你在用面向对象语言Java写C风格的代码吗?

在开始之前,先来讲个小故事

在一次代码评审中,小李兴致勃勃地给大家讲解自己用心编写的一段代码。这段代码不仅实现了业务功能,还考虑了许多异常场景。所以,面对同事们提出的各种问题,小李能够应对自如。

在讲解的过程中,小李看到同事们纷纷点头赞许,心中不由得生出一丝骄傲:我终于写出一段拿得出手的代码了!讲解完毕,久久未曾发言的技术负责人老赵站了起来:“小李啊!你这段代码从功能上来说,考虑得已经很全面了,这段时间你确实进步很大啊!”

要知道,老赵的功力之深是全公司人所共知的。能得到老赵的肯定,对小李来说,那简直是莫大的荣耀。还没等小李窃喜的劲过去,老赵接着说了,“但是啊,写代码不能只考虑功能,你看你这代码写的,虽然用的是 Java,但写出来的简直就是 C 代码。”

正在兴头上的小李仿佛被人当头泼了一盆冷水,我用的是 Java 啊!一门正经八百的面向对象程序设计语言,咋就被说成写的是 C 代码了呢?“你看啊!所有的代码都是把字段取出来计算,然后,再塞回去。各种不同层面的业务计算混在一起,将来有一点调整,所有的代码都得跟着变。” 老赵很不客气地说。还没缓过神来的小李虽然想辩解,但他知道老赵说得是一针见血,指出的问题让人无法反驳。

在实际的开发过程中,有不少人遇到过类似的问题。老赵的意思并不是小李的代码真就成了 C 代码,而是说用 Java 写的代码应该有 Java 的风格,而小李的代码却处处体现着 C 的风格。

以上故事中老赵说的代码风格其实是指的编程范式,目前常见的编程范式分为3种,分别为:面向过程编程、面向对象编程、函数式编程。由于这三种编程范式没有官方的定义,此文我们主要分析面向过程和面向对象编程范式,现在我以各自的特点来描述下之间的差别:

  • 面向过程编程:我们入门编程的时候,基本上都是这种编程方式进行,它经过一些结构化的控制结构进行程序的构建,比如最常用的 if-else, do-while,以数据(可以理解为成员变量或属性)和方法分离为最大特性,代表性语言为 C。
  • 面向对象编程:面向对象编程是目前主流的编程方式,它以类为最小基本单元组织代码结构,并将封装、继承、多态、抽象作为基本四大特性,代表性语言为Java。

接讲概念比较抽象,下面我们举个例子来进一步解释,比如实现一个支持整数加减乘除的简单计算器,我们分别用面向过程语言C和面向对象语言Java 实现,C 代码如下:

#include<stdio.h>

void plus(int a, int b)
{
  printf("%d\n",a+b);
}

void minus(int a, int b)
{
  printf("%d\n",a-b);
}

void multiply(int a, int b)
{
  printf("%d\n",a*b);
}

void divide(int a, int b)
{
  printf("%d\n",a/b);
}

int main()
{
  plus(1,2);
  minus(2,1);
  multiply(2,3);
  divide(4,2);
  return 0;
}
public class Calculator {

    final private int a;
    final private int b;

    public Calculator(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public int plus() {
        return a + b;
    }

    public int minus() {
        return a - b;
    }

    public int multiply() {
        return a * b;
    }

    public int divide() {
        return a / b;
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator(4, 2);
        System.out.println(calculator.plus());
        System.out.println(calculator.minus());
        System.out.println(calculator.multiply());
        System.out.println(calculator.divide());
    }
}

对比C和Java的实现,再结合我们之前提出的面向过程和面向对象的特点,一一对照分析。

几种典型的违反面向对象的案例

  • 滥用 setter,getter 方法

假如我们有一个班级 Class 类,有两个成员变量,一是学生list, 另一个是int型班级学生总数,定义如下

public class Class {

    private int studentTotalCount;

    private List<Student> students;

    public int getStudentTotalCount() {
       return this.studentTotalCount;
    }

    public void setStudentTotalCount(int studentTotalCount) {
        this.studentTotalCount = studentTotalCount;
    }

    public List<Student> getStudents() {
        return students;
    }

	public void addStudent(Student student){
		this.students.add(student);
		this.studentTotalCount++;
	}
}

分析上面代码,可以看出 studentTotalCount 是通过 方法addStudent 累加维护的,但是又提供了 setStudentTotalCount方法,很容易造成数据不一致。还有,此类提供了成员变量 students 的 getter 方法,代码调用端可以不用调用 addStudent 方法就可以修改班级里的学生,直接维护getter 方法返回的可变List 数据结构,因此,我们看到滥用 setter和getter 方法一不小心就打破了我们封装的本意。

  • 滥用全局变量和全局方法

首先,我们在面向对象语言Java中哪些变量是全局变量?

在面向编程中,常用的全局变量有单例类对象、静态成员变量、常量。单例类在系统中只要一个对象,所以我们把它也当值全局变量。静态成员变量属于类级别上的数据,类的所有对象共享,在一定意义也算全局变量,而常量是面向对象编程中最常见的全局变量,在实际项目中,基本上都有名为 Constants 的类,其中定义很多常量。在一个系统中维护一个常量类有什么问题呢?主要有以下三个问题:1.多人维护一个类容易造成庞大的常量类,难维护也易造成冲突 2.增加编译时间,如果是一个代码量超大的系统,改变常量类,会导致引用它的类重新编译 3.不易移植复用,其它的系统可能只需要其中一部分常量,其它不需要的也一直被移植过来。

说到全局方法,最常见的是静态方法了,一般系统中都会有***Utils 命名的类,静态方法需要传入静态数据或者外部数据,这样做打破了面向对象封装特性。

  • 定义数据和方法分离的类

此类面向过程的代码也是我们经常编写的,比如常用的MVC 三层架构的代码,一般会定义 Controller、Service、Dao 三层,这三层的类定义基本只包含方法,没有成员变量。为了三层之间的解耦,我们有延伸出多种概念的POJO,比如VO(View Object),BO(Business Object)/Enetiy,DO(Database Object) ,这类POJO 只是包含业务数据,没有业务方法定义。此类也是打破了面向对象的封装特性。

在此,我们在思考下,为什么会轻易写出面向过程的代码?

回想下我们日常思考解决问题的步骤,一般都是先做什么,再做什么,最后做什么,是一步一步按顺序来进行解决问题,面向过程编程正好符合人的流程化思维方式,而面向对象恰恰相反,面向对象是一种自底向上的思考方式,不是按照流程来思考问题,而是把问题分解为很多小块(就是我们说的类),然后组合这些小块串联起来解决我们的问题。如何把这些类设计好,不是那么容易做,需要一定的面向对象设计功底和一些设计原则作为支撑,比如 SOLID 原则

总结

本文主要讲了面向过程、面向对象编程的特点,以及之间的差异,列举了一些在面向对象编程中写出面向过程代码的常见样例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值