首页 > 代码库 > 慎用缺省构造函数的一种场景

慎用缺省构造函数的一种场景

本文通过示例来谈谈慎用缺省构造函数的一种设计场景。——以JAVA为例展开讨论。


为了便于讨论,我们假定需要建模一个Student,包括姓名和出生地两个属性。我们看到不少下面的代码:

public class Student {
	private String name = null;
	private String birthPlace = null;
	
	public Student() {
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setBirthPlace(String birthPlace) {
		this.birthPlace = birthPlace;
	}
	
	public String getName() {
		return name;
	}
	
	public String getBirthPlace() {
		return birthPlace;
	}
}

使用的例子:

import org.junit.Test;

public class StudentTest {

    @Test
    public void test() {
        final String NAME = "John";
        final String BIRTH_PLACE = "Hawaii";
        
        Student student = new Student();
        student.setName(NAME);
        student.setBirthPlace(BIRTH_PLACE);
        
        assert(NAME.equals(student.getName()));
        assert(BIRTH_PLACE.equals(student.getBirthPlace()));
    }

}

通常的使用方法就是先用缺省构造函数new出一个对象,然后再调用每个属性的setter方法。


对于以上的设计方法,其适合的语义是:

  • 对象的每个属性可以有缺省值,而且缺省值是有意义的;
  • 对象的属性值是可变的,即可以通过setter来修改当前的属性值。


符合这种语义的对象非常多,比如表示(二维)平面上的一个点Point2d就适合用上面的设计方法。


本文讨论的是另外一种情况,即有如下语义要求:

  • 对应的每个属性没有合适的缺省值;
  • 一旦要创建一个对象,那么此时它的各个属性取值也是明确的;
  • 属性的值不可更改。


对于我们前面给出的Student例子,我们假定Name不能修改,如果有新的名字,就需要新增一个属性(如aliasName)来表示;而出生地也是固定的。对于这种情况,虽然前面给出的代码也能够较好地工作,但从设计语义上来讲,是不太合适的。


对于这种情况,通常推荐如下的做法:

  • 不提供缺省构造函数(C++中必须要定义或声明一个private的缺省构造函数);
  • 提供带参数的构造函数,参数列表包括了必须在创建对象时就要指定的属性值;
  • 对于不可修改属性值的数据成员,不能提供getter方法。


因此,前面的class Student需要重构成下面的代码:

public class NewStudent {
	private String name = null;
	private String birthPlace = null;
	
	public NewStudent(String name, String birthPlace) {
		this.name = name;
		this.birthPlace = birthPlace;
	}
	
	public String getName() {
		return name;
	}
	
	public String getBirthPlace() {
		return birthPlace;
	}
}

使用示例:

	@Test
	public void testNewStudent() {
		final String NAME = "John";
		final String BIRTH_PLACE = "Hawaii";
		
		NewStudent student = new NewStudent(NAME, BIRTH_PLACE);

		assert(NAME.equals(student.getName()));
		assert(BIRTH_PLACE.equals(student.getBirthPlace()));
	}

额外的好处:通过设计优化,我们在创建对象时,有3条语句变成了1条语句;如此,大脑的思考成本显著降低。


——要知道,通常一个项目都有大量的代码,数千上万是非常常见的事情。当遍地充斥着各种各样低质量语义的代码时,整个项目就会产生大量的无效代码,这对于项目维护是一件很可怕的事情。


我们写的每一行代码,都是在向读者(包括你自己)表达一种设计思想。可以工作的代码未必是质量优秀的代码,显然我们要追求的是后者。