【问题标题】:Java Constructor of a class with fields that are are end user fed具有最终用户提供的字段的类的 Java 构造函数
【发布时间】:2018-02-18 19:34:07
【问题描述】:

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

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

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. 我可以在覆盖默认值的构造函数中使用这样的设置器吗 构造函数。
  2. 这是编写此类构造函数的最佳方式吗?
  3. 将我在每个 setter 中创建的 Scanner 对象移动到构造函数并将其作为 setter 的参数是否更好

例如:

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

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

非常感谢您的帮助。

【问题讨论】:

  • 您的二传手不应使用Scanner。将要设置的数据传递到方法中。如果您想根据文件中的某些数据设置字段怎么办?还是来自互联网?强制使用 Scanner 确实限制了您的课程的有用程度。
  • setter 应该获取要设置为方法参数的值。用户输入应该在之前的另一点进行。在设计用于获取所有用户输入的指定方法或类中。这样你就可以轻松地在之后交换输入源,并且基类不需要任何更改,它更加模块化。
  • 非常感谢。明白了:)

标签: java design-patterns constructor


【解决方案1】:

实际上,您依赖于使用特定构造函数来填充对象字段的方式,而不是无参数构造函数。
在调用 new Employe() 之后,您确实选择了一种 setter 方法来填充 Employee 实例的字段。
但是这种 setter 方法很复杂,因为您混合了太多职责:接受用户输入和设置对象的状态。

我可以在覆盖默认值的构造函数中使用这样的设置器吗 构造函数。

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

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

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

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

如果您的对象被设计为一旦创建就不可变,那么这是有意义的。

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


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

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

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

【讨论】:

  • 答案也很好。 1+
  • 啊哈。另一个很好的答案。非常感谢您的澄清。
  • 我同意。很好,有人在周末保持精神。
  • @GhostCat 嘿!灵魂 ?从一个新鲜的传奇中听到它真是一种荣幸:)
  • @Hassan Shahin 非常欢迎你。如果它可以帮助你,那就太好了:) 正如你所说,你开始了。当你开始做某事时,似乎很可能会落入陷阱:)
【解决方案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 东西——扫描器类等等都放在你的驱动程序类的其他地方。

旁注:当您使用基于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();
    }
}

【讨论】:

  • 非常感谢。说得通。所以这是一个设计问题,但不是构造函数的问题,而是整个“解决方案”及其模块化的问题。
  • @HassanShahin:是的。请参阅编辑以将员工代码与 I/O 代码一起使用的示例。
  • @DontKnowMuchBut 越来越好 非常好的答案(+1)
【解决方案3】:

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

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

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

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

这是有问题的,因为:

  1. 将 null 传递给函数可能会让您非常头疼。

  2. 此代码不那么可读。

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

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

【讨论】:

  • 静态构建器模式,不错。 1+
  • @Petar Petrov:非常感谢您在为我的问题创建解决方案时捕捉到另一个设计方面。正如我在另一条评论中所说,一些初学者(有远大的梦想,并且非常渴望建立一个大项目)在弄清楚如何设计他们的应用程序/解决方案时会面临如此重大的决定。我想知道是否有关于“如何在 Java 中设计大型项目”的参考资料,他们在其中讨论此类设计问题。但再次非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-26
  • 1970-01-01
  • 1970-01-01
  • 2019-04-11
  • 1970-01-01
  • 2018-07-08
  • 1970-01-01
相关资源
最近更新 更多