简体   繁体   中英

Overloaded methods that handle subclasses of some abstract class

I have a generic Person class, and two types of people Student and Teacher that extends the Person class. I also have teaching sessions which will store a list of students and a list of teachers that will be in that session.

class Person {}
class Student extends Person {}
class Teacher extends Person {}

class Session {
  List<Student> students = new ArrayList();
  List<Teacher> teachers = new ArrayList();

  // add a person to the teaching session.
  // They should be placed into the appropriate lists
  public void enroll(Person p)
  {
    if (p instanceof Student)
      students.add((Student) p)
    else if (p instanceof Teacher)
      teachers.add((Teacher) p)
  }
}

The idea is that some other code will have a list of people and iterate the list to enroll them into the appropriate sessions as needed. However, the enroll method currently explicitly checks the object's type, which to me is undesirable and seems like bad design.

I have tried writing the enroll method like this using method overloading, which looks a lot cleaner

public void enroll(Student p)
{
    students.add(p)
}

public void enroll(Teacher p)
{
    teachers.add(p)
}

but it seems like the code that iterates through the list of Person objects would need to then figure out whether the current person is a student or teacher instance, and type-cast appropriately before passing it to an enroll method.

Is there a way for me to design this so that I do not need to invoke instanceof at any point in time in my own code?

  1. Session needs to have an overloaded enroll method as in your question.
  2. Add an abstract enroll method to Person class that takes Session as a parameter

    public abstract void enroll (Session s);

  3. Teacher and Student each override enroll

     public void enroll (Session s) { s.enroll(this); } 

The simplest way to do this is to add an isTeacher() method to the person class.

Here is a basic implemention (with the parts you already wrote left out)

abstract class Person {
  public abstract boolean isTeacher();
}

class Teacher extends Person {
  public boolean isTeacher() { return true; }
}

class Student extends Person {
  public boolean isTeacher() {return false; }
}

abstract class Session {
  public void enroll(Person p) {
    if (p.isTeacher()) teachers.add((Teacher)p);
    else student.add((Student)p);
  }
}

If you also want to avoid the typecast, try this...

abstract class Person {
  public abstract Teacher asTeacher();
  public abstract Student asStudent();
}

class Student extends Person {
  public Student asStudent() { return this; }
  public Teacher asTeacher() { return null; }
}

class Teacher extends Person {
  public Student asStudent() { return null; }
  public Teacher asTeacher() { return this; }
}

class Session extends Person {
  public void enroll(Person p) {
    Teacher t = p.asTeacher();
    if (t != null) teachers.add(t);
    else students.add(p.asStudent());
  }
}

This implementation has a minor weakness that it assumes Teachers and Students are the only types of people, but extending it to multiple types is fairly straightforward.

The issue with p instanceof Student is that you change the behavior based on something that is not a method invocation, so future changes will be hard (ie. if you add another sub-class of Person).

There are a couple of ways to look at this design problem:

  • Why you need to create a hierarchy of Person ? It's possible in your system that someone that is a student can be a teacher in another course? If that is the case, you have some information about a person, and what changes is the role of that person for a course. So is not convenient to use inheritance for that, use composition and create an object that represents the role of a Person in a course.

  • You can add a isTeacher method, or use a visitor pattern to split the collection. That will be a little bit better than the instanceof . But in my opinion the problem is still in the incorrect class hierarchy.

For example, using "roles":

enum Role { TEACHER, STUDENT; }

class Session {
   List<SessionEnrolment> enrollments = new ArrayList<>();

   public void enroll(Person p, Role role)
   {
       enrollments.add(new SessionEnrollment(p, role));
   }

   public List<Person> getTeachers() {
      List<Person> result = new ArrayList<>();
      for (SessionEnrolment e : enrollments) {
            if (e.isTeacher()) { result.add(e.getPerson()); }
            return result;
      }
   }
}

Using a isTeacher method:

public void enroll(Person p)
{
    if (p.isTeacher()) { teachers.add(p); } else { students.add(p); }
}

As you can see having a isTeacher method for all persons looks awkward. And the role solution reflects better the temporal property of being a student or teacher.

You can use a visitor, and it will save you from the ugly isTeacher in Person ; but to me that solution is overcomplicated, and you'll translate the "uglyness" to the Visitor interface.

You can do what you propose with an overloaded method per concrete Person implementation, and in addition to that add a Type to the List:

List<Student> students = new ArrayList<>();
List<Teacher> teachers = new ArrayList<>();

Then in your iteration logic you know the type.

You can turn it around:

class Session {
  StudentList students = new StudentList();
  TeacherList teachers = new TeacherList();

  // add a person to the teaching session.
  // They should be placed into the appropriate lists
  public void enroll(Person p)
  {
     p.addMe(students, teachers);
  }
}

public class StudentList extends ArrayList<Student> {
}

public class TeacherList extends ArrayList<Teacher> {
}

public abstract class Person {
    public abstract void addMe(StudentList sList, TeacherList tList);
}

public class Student extends Person {
    public void addMe(StudentList sList, TeacherList tList) {
        sList.add(this);
    }
}

public class Teacher extends Person {
    public void addMe(StudentList sList, TeacherList tList) {
        tList.add(this);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM