首页 文章

具有最终用户馈送字段的类的Java构造函数

提问于
浏览
3

我的问题是:使用 stdin 初始化的字段编写Java类构造函数的最佳方法是什么?

例如,假设我有一个 Employee 类,看起来像:

Public class Employee {
    private int empID;
    private String empName;
    private List<Role> empRoles;
    {....}
}

我可以为这堂课写下所有的二传手和门手 . 当然, Role 类将拥有自己的文件 .

另外假设我为前两个字段创建了我的setter,如下所示,以便让最终用户初始化字段:

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();
}

然后:

  • 我可以在覆盖默认构造函数的构造函数中使用这样的setter .

  • 这是编写此类构造函数的最佳方法吗?

  • 将我在每个setter中创建的 Scanner 对象移动到构造函数并将其作为setter的参数更好

例如:

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

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

非常感谢您的帮助 .

3 回答

  • 5

    实际上,你 don't 依赖于一种使用特定构造函数填充对象字段的方法,但是没有arg构造函数 .
    您确实选择了一个setter方法来在调用 new Employe() 之后填充 Employee 实例的字段 .
    但是这种setter方法很复杂,因为你混合了太多的责任:获取用户输入和设置对象的状态 .

    我可以在覆盖默认构造函数的构造函数中使用此类setter .

    不,这没有任何意义:构造函数和setter是两种不同的方式,你不能用另一种方式覆盖它们 .
    然而,您可以通过依赖 Scanner 实例来获取用户输入来调用构造函数中的setter,但与实际的setter方法类似,这似乎是一种尴尬的方法,因为它给构造函数带来了太多的责任 .

    这是编写此类构造函数的最佳方法吗?

    使用填充所有字段的构造函数,即:

    Employee emp = new Employe(id, name, roles)
    

    如果您的对象在创建后被设计为不可变的,那么这是有意义的 .

    在您的实际情况中,如果您的对象不是使用构造函数设置为不可变的,或者setter是有效的,但无论如何,您应该提供setter .


    因此,要回答您的问题,您应该分离职责(获取用户输入和设置对象状态),并根据您对 Employee 实例的要求使用setter或构造函数方法:

    Employee emp = new Employe(id, name, roles)
    

    要么

    Employee emp = new Employe();
    emp.setId(...);
    emp.setName(...);
    emp.setRoles(...);
    
  • 2

    我认为您可能会将用户输入/输出与程序模型混淆 . 这里的关键是你应该把两者完全分开 . Employee类应该完全了解使用什么类型的UI或I / O来使用它,因为这样可以在GUI,控制台程序或其他任何需要的地方使用它 .

    因此,您的Employee构造函数应该只接收创建Employee对象所需的数据,无论其来源如何,并且对于您的字段getter也是如此 .

    所以你的getter看起来就像你发布的一样,而是更简单,更“哑”或“无知”的用户I / O(Scanner,System.in等)

    public void getEmpID (int empID) {
        this.empID = empID;
    }
    

    其他领域也一样 .

    所有的I / O东西 - Scanner类等都在你的驱动程序类中的其他地方 .

    旁注:当您使用基于 System.in 的扫描仪时,您的程序应该创建一个且只有一个这样的野兽,在需要时创建它,然后仅在程序完全使用它时关闭并处理它 . 否则,您可能会过早关闭连接而破坏系统输入 . 这是在您创建多个Scanner对象时不使用您建议的代码的另一个原因 .

    例如....

    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();
        }
    }
    
  • 4

    已经有两个好的答案,但我想再给你一个问题的解决方案 . 正如@davidxx已经说过,如果你的对象应该是不可变的,那么所有参数构造函数都是一个更好的方法而不是setter,但让我们考虑一下你有更多字段的情况 . 例如,您的员工有薪水,经验和其他 . 你的构造函数开始看起来像这样:

    Employee employee = new Employee(id, name, roles, salary, experience, ... );
    

    正如您所看到的,构造函数开始变得太长 . 这称为伸缩构造器 . 让我们考虑一下您的员工有2-3个必填字段而其他字段不需要的情况 . 要创建此对象,您必须编写如下代码:

    Employee employee = new Employee(id, name, roles, null, null, 0, ... );
    

    这是有问题的,因为:

    • 将null传递给函数可能会引起很多麻烦 .

    • 此代码不太可读 .

    您可以添加只接收所需字段的构造函数,但每次需要传递不同的参数组合时,您都必须添加新的构造函数(打破Open-Closed原则) . 这种情况的解决方案是使用构建器模式:

    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();
    

相关问题