不使用instanceof的类设计

时间:2018-12-24 23:41:55

标签: java

我目前正在研究类层次结构。我有可以成为工程师,销售员和经理的员工。他们都可以有经理,但是只能将一个经理分配为经理。还有一个EmployeeAdmin类,它基本上容纳雇员。我考虑使用接口进行设置,因为这不涉及强耦合,还是在这里犯了错误?该界面仅定义EmployeeAdmin类中所需的方法。

ArrayList<IEmployee> employees = null;

对于类层次结构,我使用抽象基类,因为它们都具有所有子类都需要的方法。这些方法是:getId(),getManager(),setManager(...),payAmount()。基类的名称是Employee。然后,我从此Employee类派生了一些类:Engineer,Salesman和Manager。

我的问题是我不知道如何正确设置它。据我所知,使用接口可确保您没有直接耦合。因此,实现IEmployee的任何人都可以成为雇员并添加到列表中。我不知道这种思维方式是否正确。另外,setManager(...)方法也给我带来麻烦,如果我使用IEmployee,则需要使用instanceof通过使用另一个接口或类来确定它是否是管理器,对吗?最重要的是,EmployeeAdmin类还具有setManager(...,...)方法。

有人愿意帮助我正确设置此问题并提出自己的想法吗?我读到,向下转换和instanceof通常是不良/不良设计的迹象,因此我将需要一些帮助。

EmployeeAdmin类:

public class EmployeeAdmin {
  private ArrayList<IEmployee> employees = null;
  public boolean setManager(IEmployee e, IEmployee m) {
    // Code to add manager to employee.
    e.setManager(m);
  }
}

通过使用接口,我将如何设置它,以便我可以从列表中提取一些员工,但也可以将其作为IManager粘贴进来?

3 个答案:

答案 0 :(得分:3)

由于您说过只有管理者可以被接受为管理者,所以一种解决方案是让管理者成为其自己的接口,并使setManager方法仅接受管理者。 考虑以下基类:

public class Employee{
    private Manager manager;
    public final setManager(Manager manager){
        this.manager = manager;
    }
    public final Manager getManager(){
        return manager;
    }
    //... other methods and logic common to all employees
 }

现在定义管理器界面:

public interface Manager{
    public void registerEmployeeAsSubordinate(Employee employee);
    public void someOtherMethodCommonToAllManagers();
}

现在让我们来执行区域经理:

public class RegionalManager extends Employee implements Manager{
    private void ArrayList<Employee> employees = new ArrayList<>();
    public void someRegionalManagerMethod(){
         //... this logic belongs to this specific manager
    }
    @Override
    public void registerEmployeeAsSubordinate(Employee employee){
        employees.add(employee);
    }
    @Override
    public void someMethodCommonToAllManagers(){
         //... some logic
    }
}

现在,给首席执行官

public class CEO extends Employee implements Manager{
    private void ArrayList<Employee> employees = new ArrayList<>();
    public void someCEOMethod(){
         //... this logic belongs to this specific manager
    }
    @Override
    public void registerEmployeeAsSubordinate(Employee employee){
        employees.add(employee);
    }
    @Override
    public void someMethodCommonToAllManagers(){
         //... some logic
    }
}

还有一名工程师

public class Engineer extends Employee{
    public void someEngineerMethod(){
        //...
    }
}

所有管理或员工实施都是分离的,如果更改CEO上的逻辑,则无需重新编译除其本身以外的任何其他类。与RegionalManager

相同

在此示例中,如果您尝试在setManager中使用非Manager类,则该代码甚至不会编译,因为它希望提供Manager。

因此,您发现使用接口的本能是正确的,希望该示例有所帮助。

编辑: 现在,对于您的特定实施,实际上是相反的,当您接受员工的接口实施时,您会将代码耦合到该IEmployee合同。你想做的是:

 public class EmployeeAdmin {
  private ArrayList<Employee> employees;
  public boolean setManager(Employee e, Manager m) {
    // Code to add manager to employee.
    e.setManager(m);
  }
 }

这样,您不必检查所使用的员工界面类型,如果必须检查,则可能不会充分利用多态性。

其他编辑:您可能想了解有关Liskov Substitution的信息,以获取最多的子类。一开始很难理解,但是良好的子分类将在编程接口而不是实现时帮助您。 Head First Design Patterns是一种很好的方法,可以很好地遵循良好的编码原则,但是如果您是顽固的人,请直接咨询Robert“ Uncle Bob” Martin的Clean Code

Liskov替换基本上意味着您必须能够与子类进行对话,就像与父类进行对话一样,这就是有效的子类化的全部含义。如果在某个时候您需要以超类不会理解或导致错误的方式与子类对话(即需要使用instanceof),那么您需要的是其他类,而不是子类。

尝试找到使用继承和多态的方法,例如,在基类中,您可以使用名为performMainJob()的方法,并使用多态,将其定义为对方法{{ 1}}用于工程师子类,并调用方法solveProblem()用于会计子类。

这样,您就可以调用verifyBooks()并对其进行多态调度,而不必担心特定的子类实现。就是这样,您知道自己有一个符合Liskov替换的子类,与employee.performMainJob()Engineer对话与与其父类Accountant对话相同,它们可以彼此替代。

多态性应该足以部署解耦的子类,如果您需要的灵活性更大,请查看Employee以便能够使用对象来实现不同且更灵活的行为。再次,来自O'Reilly的Head First Design Patterns是宝贵而友好的资源,请尝试一下。

答案 1 :(得分:0)

首先一般性评论,请谨慎谨慎,不要因为听到不应该紧密耦合而过度抽象代码。

通过正确应用OOP原则,管理器不是接口。经理是员工的一种。

您将在界面上找到正确的位置。但是要忠实于接口的原始设计意图,您想要的接口不是完全“雇员”,而是可雇佣

“任何员工均可就业”。

您想要的抽象类(适用于所有雇员的常见行为)是Employee,并且为了实施公司中所有Employable的必需行为,它实现Employable接口。也许将来承包商也可以实现Employable接口。看到?它不是全职员工,但必须提供某些义务才能就业。

无论如何,经理,推销员和工程师类都扩展了Employee类。他们都是员工类型。

例如,要满足员工必须拥有经理的要求,推销员和工程师将包含私有的Manager属性/变量(带有get设置,或者可能只是在构造函数中设置),指定其经理的身份。对于Manager,如果不是所有Manager都拥有自己的Manager,则可以将其设为可选。

以此类推。

答案 2 :(得分:0)

您可以创建一个新界面 IManager ,该界面扩展了 IEmployee 。这将有助于在员工列表中容纳经理。

IEmployee.java

public interface IEmployee {
  //other methods
  public void setManager(IManager mgr);
}

IManager.java

public interface IManager extends IEmployee {
   //Manager specific methods
}

Employee.java

public abstract Employee implements IEmployee  {
}

Manager.java

public abstract Manager extends Employee implements IManager{
}

EmployeeAdmin.java

public class EmployeeAdmin {
  private ArrayList<IEmployee> employees = null;
  public boolean setManager(IEmployee e, IManager m) {
    // Code to add manager to employee.
    e.setManager(m);
  }
}

CEO.java

public class CEO extends Manager {
}

Engineer.java

public class Engineer extends Employee {
}

希望这会有所帮助。