我的问题是:用通过stdin
初始化的字段编写Java类的构造函数的最佳方法是什么?
例如,假设我有一个员工
类,如下所示:
Public class Employee {
private int empID;
private String empName;
private List<Role> empRoles;
{....}
}
我可以为这个类编写所有的setter和getter。当然,Role
类将有自己的文件。
还假设我为前两个字段设置如下设置器,以使最终用户能够初始化字段:
public void setEmpID() {
System.out.println("Please enter the employee ID");
Scanner s = new Scanner (System.in);
this.empID = s.nextInt();
public void setEmpName() {
System.out.println("Please enter the employee name");
Scanner s = new Scanner (System.in);
this.empName = s.next();
}
然后:
Scanner
对象移动到构造函数并将其作为setter的参数是否更好例如:
public void setEmpName(Scanner s) {
...
this.empName = s.next();
}
如您所见,这可能是一个设计问题,而不仅仅是“编码”。
非常感谢你的帮助。
实际上,您不依赖于使用特定构造函数来填充对象字段的方法,而是使用无参数构造函数。
您确实选择了一种setter方法来填充员工
实例的字段,在调用新雇主()
之后。
但是这种setter方法很复杂,因为您混合了太多的职责:获取用户输入和设置对象的状态。
我可以在覆盖默认构造函数的构造函数中使用此类setter吗?
不,这没有意义:构造函数和setter是两种不同的方式,你不能用另一种方式覆盖一种。
然而,您可以通过依赖Scanner
实例从构造函数中调用setter来获取用户输入,但与您实际的setter方法类似,这似乎是一种尴尬的方法,因为它赋予构造函数太多的责任。
这是编写此类构造函数的最佳方式吗?
使用填充所有字段的构造函数,即:
Employee emp = new Employe(id, name, roles)
如果您的对象在创建后被设计为不可变,则是有意义的。
在您的实际情况下,如果您的对象不是使用构造函数或setter设计为不可变的,则有效,但在任何情况下,您都应该提供setter。
因此,为了回答您的问题,您应该分离职责(接受用户输入和设置对象状态),并根据您对员工
实例的要求使用setter或构造函数方法:
Employee emp = new Employe(id, name, roles)
或
Employee emp = new Employe();
emp.setId(...);
emp.setName(...);
emp.setRoles(...);
我认为你可能混淆了用户输入/输出和程序模型。这里的关键是你应该把两者完全分开。员工类应该完全不知道正在做什么类型的UI或输入/输出来使用它,因为这样它可以在GUI、控制台程序或任何需要它的地方使用。
因此,您的员工构造函数应该只接收创建员工对象所需的数据,而不管其来源如何,对于您的字段获取器也是如此。
所以你的获取者看起来一点也不像你发布的,而是更简单,更愚蠢或更无知的用户输入/输出(扫描仪、System.in等)
public void getEmpID (int empID) {
this.empID = empID;
}
其他领域也是如此。
所有I/O的东西——扫描仪类等都在您的驱动程序类的其他地方。
附带说明:当您使用基于System.in
的扫描仪时,您的程序应该创建一个并且只有一个这样的野兽,在需要时创建它,然后只有在程序完全完成时才关闭和处置它。否则,过早关闭连接可能会破坏系统输入。这是在创建多个扫描仪对象时不使用建议的代码的另一个原因。
例如……
import java.util.ArrayList;
import java.util.List;
public class Employee {
private int empID;
private String empName;
private List<Role> empRoles;
public Employee(int empID, String empName) {
super();
this.empID = empID;
this.empName = empName;
empRoles = new ArrayList<>();
}
public int getEmpID() {
return empID;
}
public void setEmpID(int empID) {
this.empID = empID;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public List<Role> getEmpRoles() {
return empRoles;
}
public boolean addEmpRole(Role role) {
return empRoles.add(role);
}
public boolean removeEmpRole(Role role) {
return empRoles.remove(role);
}
}
然后,您可以在其他地方使用它,如下所示:
import java.util.Scanner;
public class TestEmployee {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("Enter employee ID: ");
int empID = scan.nextInt();
scan.nextLine(); // handle dangling end of line token
System.out.print("Enter employee Name: ");
String empName = scan.nextLine();
Employee employee = new Employee(empID, empName);
// if we are **totally** done with the Scanner, now we may close it
scan.close();
}
}
已经有两个很好的答案了,但是我想给你一个解决问题的方法。正如@davidxx已经说过的,如果你的对象应该是不可变的,所有参数构造函数是一个更好的方法,而不是设置器,但是让我们考虑一下你有更多字段的情况。例如,你的员工有工资、经验和其他。你的构造函数开始看起来像这样:
Employee employee = new Employee(id, name, roles, salary, experience, ... );
如您所见,构造函数开始变得太长。这称为伸缩构造函数。让我们考虑一下您的员工有2-3个必填字段而其他字段不是必需的情况。要创建此对象,您必须编写如下代码:
Employee employee = new Employee(id, name, roles, null, null, 0, ... );
这是有问题的,因为:
>
将null传递给函数可能会让您很头疼。
这段代码可读性不高。
您可以添加只接收您需要的字段的构造函数,但是每次需要传递不同的参数组合时,您都必须添加一个新的构造函数(打破开闭原则)。解决这种情况的方法是使用构建器模式:
public class Employee {
private int id;
private String name;
private List<Role> roles;
private Employee() {
roles = new ArrayList<>();
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public List<Role> getRoles() {
return roles;
}
public static class EmployeeBuilder {
private Employee employee;
public EmployeeBuilder() {
employee = new Employee();
}
public EmployeeBuilder withId(Integer id) {
employee.id = id;
return this;
}
public EmployeeBuilder withName(String name) {
employee.name = name;
return this;
}
public EmployeeBuilder withRole(Role role) {
employee.roles.add(role);
return this;
}
public Employee build() {
return employee;
}
}
}
然后你可以像这样创建你的对象:
Employee employee = new Employee.EmployeeBuilder()
.withId(1)
.withName("John")
.withRole(role1)
.withRole(role2)
.build();