OOP中static的原罪

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:最近项目中使用Spring Boot,让我稍微有些水土不服,主要原因就是static。看下面两种代码风格:风格1:风格2:

最近项目中使用Spring Boot,让我稍微有些水土不服,主要原因就是static。看下面两种代码风格:

风格1:

@Autowired
MyService myservice;

// doSomething是MyService类的非静态方法
Response resp = myservice.doSomething(someParameters);

风格2:

// doSomething是MyService类的静态方法
Response resp = MyService.doSomething(someParameters);

你平时Coding喜欢哪一种?对我而言,如果 doSomething 是无状态的,我一般都是使用第二种。更广一点,对于任何无状态的,我一般都喜欢写成static的。主要原因是调用起来方便,使用者不需要创建实例即可使用。而且一般静态调用会比实例调用快一些,因为没有实例创建、初始化的动作。但在使用Spring时,这种习惯让我举步维艰。因为Spring的核心技术之一就是 依赖注入(Dependency Injection,DI) ,注入的变量其实都是各个Bean的非静态类实例。而我们都知道,在静态方法里面是无法使用非静态变量的。如果要使用这些注入的对象,最简单的方式就是把静态方法改成非静态的。这多少让我有些不适应,特别是如果这个方法是无状态的,甚至它只是个偏 工具 的方法或者类。直觉告诉我,对于一个成熟的东西,如果你用着别扭,那很大可能是因为你的用法不对。于是我尝试着Google了一下,发现和我有同样问题的人还不在少数,Stack Overflow上面也有许多牛人给出了workaround,但终归只是workaround,还是让人觉得用着变扭。再深究,发现其实在OOP里面就不应该用static,至少要少用、慎用。这便是 static的原罪:使用static方法这种编程属于procedural programming(面向过程的程序设计),而不是OOP。

先来看个例子,假设我们有一个 Person 类和一个 PersonApp 类如下:

@Getter
@Setter
public class Person {
    private int id;
    private String firstName;
    private String lastName;

    public static Person createNew() {
        Person temp = new Person();
        temp.id = -1;
        temp.firstName = "";
        temp.lastName = "";

        return temp;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                '}';
    }
}


public class PersonApp {
    public void doSomething() {
        // 调用静态方法createNew()
        Person person = Person.createNew();
        person.setId(1);
        person.setFirstName("Yanchun");
        person.setLastName("Ni");

        // 调用实例方法toString()
        System.out.println(person.toString());
    }
}

目前来看, PersonApp 可以很方便的使用 Person 中的方法,不论是静态方法 createNew() 还是非静态方法 toString() 。但随着业务发展呢,我们又接到一个新的需求,需要增加一个员工类 Employee ,并且是从 Person 继承来的。然后我们定义了如下员工类:

@Getter
@Setter
public class Employee extends Person {
    private String boss;
    private String department;

    @Override
    public String toString() {
        return "Employee{" +
                "boss='" + boss + '\'' +
                ", department='" + department + '\'' +
                ", FirstName()='" + super.getFirstName() + '\'' +
                ", FirstName()='" + super.getLastName() + '\'' +
                '}';
    }
}

可以看到,对于非静态的 toString() 方法,我们可以通过覆写父类的 toString() 来实现自己的 toString() 。和Person类一样,我们也需要提供一个 createNew() 来创建Employee实例,但遗憾的是我们无法覆盖一个静态方法,只能重新写一个 createNew() 。显然覆盖写是OOP中的编程思想,而重新写一个是面向过程里面的思想。这只是一个继承的例子。OOP中还有很多技术都依赖类实例实现,比如多态、注入、基于接口的编程(interface-driven)、基于代理的AOP等。而DI、AOP都是Spring使用的核心技术,我喜欢用static的风格自然会让我在使用Spring的时候各种水土不服。

这里只是用 Java 举了一个例子,文末我会附几个链接,都是说明为什么不应该在OOP里面使用静态方法的原因,有基于 PHP 的,有基于C#的,也有基于Java的,作者们的核心观点总结起来有两方面,一方面就是静态方法属于面向过程语言的思想,用在OOP里面会让很多OOP技术都不可用,比如上面的例子就属于这种,静态方法让程序丧失了可扩展性。另一方面就是不利于自动化的单元测试,因为单元测试是要将代码功能块隔离开测试,而代码之间都是有依赖的,如果是基于实例的,我们可以使用Mock等技术创造一些假的依赖类的实例,但如果实现为静态方法,那这种技术也就无能为力了,所以静态方法也是单元测试的噩梦。更有激进的作者认为,像 Math.abs(-5) 这种标准库的实现是因为语言设计的不对(比如Java),正确设计应该是 -5.abs() ,比如 Ruby 就是这样设计的。

本文写的比较简单,结论就是: 在OOP中,慎用静态方法 。如果还不足以说( shuo ,这个不读 shui )服你的话,可以看一下下面的几篇文章,我觉得非常不错:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法谜题

算法谜题

Anany Levitin、Maria Levitin / 赵勇、徐章宁、高博 / 人民邮电出版社 / 2014-1-1

算法是计算机科学领域最重要的基石之一。算法谜题,就是能够直接或间接地采用算法来加以解决的谜题。求解算法谜题是培养和锻炼算法思维能力一种最有效和最有乐趣的途径。 本书是一本经典算法谜题的合集。本书包括了一些古已有之的谜题,数学和计算机科学有一部分知识就发源于此。本书中还有一些较新的谜题,其中有一部分谜题被用作知名IT企业的面试题。全书可分为4个部分,分别是概览、谜题、提示和答案。概览介绍了算法......一起来看看 《算法谜题》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码