提问者:小点点

Java类的构造函数,其字段由最终用户提供


我的问题是:用通过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();
}

然后:

  1. 我可以在覆盖默认构造函数的构造函数中使用这样的setter吗?
  2. 这是编写此类构造函数的最佳方式吗?
  3. 将我在每个setter中创建的Scanner对象移动到构造函数并将其作为setter的参数是否更好

例如:

public void setEmpName(Scanner s) {
    ...
    this.empName = s.next();
}

如您所见,这可能是一个设计问题,而不仅仅是“编码”。

非常感谢你的帮助。


共3个答案

匿名用户

实际上,您不依赖于使用特定构造函数来填充对象字段的方法,而是使用无参数构造函数。
您确实选择了一种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();