2013-05-28 3 views
3

У меня есть общий Person класс, и два типа людей Student и Teacher что расширяет Person класс. У меня также есть учебные занятия, в которых будет храниться список студентов и список учителей, которые будут в этом сеансе.Перегруженные методы, которые обрабатывают подклассы некоторого абстрактного класса

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

Идея заключается в том, что какой-либо другой код будет иметь список людей и просматривать список, чтобы зарегистрировать их в соответствующих сессий по мере необходимости. Однако метод enroll в настоящее время явно проверяет тип объекта, который для меня нежелателен и кажется плохим дизайном.

Я пытался писать enroll метод, как это с помощью перегрузкой метода, который выглядит намного чище

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

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

, но кажется, что код, который перебирает список Person объектов должны были бы тогда выяснить, является ли текущий человек является экземпляром студента или преподавателя и соответствующим типом, прежде чем передать его методу enroll.

Есть ли способ для меня сконструировать это так, чтобы мне не приходилось ссылаться на instanceof в любой момент времени в моем собственном коде?

+0

Просто для полноты, и не особенно хороший способ это сделать, но я должен упомянуть только о том, чтобы просто добавить его и добавить «ClassCastException». Вы сделали бы это, полагая, что учеников больше, чем учителей, поэтому учителя - это те, кто бросает исключение. –

+0

В чем вред при использовании Generics здесь? – NINCOMPOOP

+0

Да, я обновил этот пример, чтобы отразить идею о том, что в списке учеников будут храниться экземпляры «Студент», а список учителей будет содержать только экземпляры «Учитель». – MxyL

ответ

7
  1. Session должен иметь перегруженный метод регистрации, как в вашем вопросе.
  2. Добавить abstract enroll метод в Person класс, который принимает Session в качестве параметра

    public abstract void enroll (Session s);

  3. Teacher и Student каждый переопределение enroll

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

Мне это нравится. Обратите внимание, что в 'Session' вам все равно будет' enroll (Person p) ', который был бы вызовом' {p.enroll (this); } ' –

+0

Я не думал о добавлении метода' enroll' в класс 'Person'. Фактически, это, вероятно, было бы лучше, потому что Person определял бы, как он будет «регистрироваться» в сеансе (например, возможно, он должен заплатить плату сначала перед тем, как выбрать сеанс), а сеанс определит, как он «регистрирует», человека на его сеанс (например: добавление его в список). – MxyL

+0

Это правильное решение. Обратите внимание, что это фактически _is_ (выделенный экземпляр) шаблона посетителя, предложенного @millimoose в его/ее комментарии к исходному вопросу. – Arend

0

Вы можете делать то, что вы предлагаете с перегруженным методом в реализации бетона Person, и в дополнении к этому добавить тип к списку:

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

Затем в итерационной логике вы знаете тип.

+0

Вы пробовали? Я сделал это до того, как вы опубликовали свой ответ и не работал. В противном случае я настоятельно рекомендую вам опубликовать код, который поддерживает ваш предложенный ответ. –

+0

Пробовал что? Создание нового списка <>()? Изменено это на конкретный ArrayList :) –

+0

Пробовал перегруженные методы и добавлял элементы 'List ', используя эти методы.Кстати, «Список » будет содержать экземпляры ссылок объекта «Студент» и «Учитель». –

0

Вы можете повернуть его вокруг:

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); 
    } 
} 
+0

-1: Это не является основанием для расширения 'ArrayList'. –

+0

@LuiggiMendoza У меня есть причина. Это заставляет все это работать. Почему думаешь, что это не нужно? –

+0

Кстати, ваш код даже не компилируется и не показывает плохой дизайн. –

1

Самый простой способ сделать это, чтобы добавить isTeacher() метод к классу человека.

Вот основной РЕАЛИЗАЦИЯ (с частями, то вы уже писали слева) из

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

Если вы хотите, чтобы избежать приведение типа, попробуйте это ...

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

Эта реализация имеет незначительная слабость, которую он предполагает, что Учителя и Студенты являются единственными типами людей, но распространение их на несколько типов довольно просто.

1

Проблема с p instanceof Student является то, что вам чан e поведение, основанное на чем-то, что не является вызовом метода, поэтому будущие изменения будут сложными (т.е. если вы добавите еще один подкласс Person).

Есть несколько способов, чтобы смотреть на эту проблему дизайна:

  • Почему вам нужно создать иерархию Person? В вашей системе возможно, что кто-то, кто является учеником, может учиться на другом курсе? Если это так, у вас есть информация о человеке, и каковы изменения в роли этого человека для курса. Так что не удобно использовать наследование для этого, используйте композицию и создайте объект, который представляет роль Person в курсе.

  • Вы можете добавить метод isTeacher или использовать шаблон для разбивки коллекции. Это будет немного лучше, чем instanceof. Но, на мой взгляд, проблема все еще находится в неправильной иерархии классов.

Например, используя "роли":

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

Используя метод isTeacher:

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

Как вы можете видеть, имея метод isTeacher для всех лиц выглядит неуклюжим. И решение роли отражает лучшее временное свойство быть студентом или учителем.

Вы можете использовать гостя, и это спасет вас от уродливого isTeacher в Person; но для меня это решение сложнее, и вы переводите «уродство» на интерфейс Visitor.

+0

Интерфейс посетителя - это место, где это уродство принадлежит. «Человеку» разрешено делать что-то, основанное на том, является ли это «Учителем» или «Студентом», вот в чем весь смысл полиморфизма. Не сказать, что нет лучшего решения, но я бы сказал, что посетители явно лучше, чем замена 'instanceof' методом, который делает то же самое. – millimoose

+0

Сначала иерархия ошибочна, это моя основная мысль. Вторым вызовом метода isTeacher является также полиморфизм: ответ зависит от реализации получателя (не в определенном классе, например, с 'instanceof'). Посетитель закончит делать то же самое, но более общим образом. Но если вы планируете использовать другие роли Person, проблема в том, что вам необходимо обновить интерфейс «Visitor» и все разработчики по мере роста модели системы. – Diego

Смежные вопросы