Java: как написать метод для принятия дочернего элемента без приведения к родителю?

Не знаю, как это назвать...

Итак, у меня есть три дочерних класса Event: WeightEvent, TimedEvent, RepEvent. Какими бы то ни было средствами я получаю объект одного из детей. Теперь я хочу отправить это дочернее событие методу в другом объекте, чтобы он мог извлечь из него данные с помощью метода getSavedEvents(). Этот метод существует только в дочерних элементах, поскольку извлечение данных зависит от типа события.

я начал с

public void setEvent(Event e) {

но это привело мой дочерний объект к Event (родительскому) объекту.

Есть ли способ обойти это, если не писать три разных метода. По одному для детей?

public void setEvent(WeightEvent e) {
public void setEvent(TimedEvent e) {
public void setEvent(RepEvent e) {

Спасибо за любой совет.

-Джон


person JHolmes763    schedule 17.12.2009    source источник
comment
Что именно setEvent делает с e, который требует, чтобы у вас был точный класс?   -  person Laurence Gonsalves    schedule 17.12.2009
comment
Некоторым помогло использование abstract. Я добавил ответ ниже с еще несколькими проблемами, с которыми я сталкиваюсь, и кодом, показывающим, что происходит. Спасибо!   -  person JHolmes763    schedule 17.12.2009
comment
Только что понял, что вместо getReps(), getWeight() и т. д., возможно, мне следует использовать общий метод getData(), который возвращает данные. getData(вес), getData(reps), getData(formattedtime) и т.д. Возможно, с некоторыми константами или ENUMS вместо строк...   -  person JHolmes763    schedule 17.12.2009


Ответы (8)


Вместо включения типа вы должны вызвать метод для события, который определен по-разному для каждого типа типа события. Это называется Шаблон метода шаблона. (Кстати, это не имеет ничего общего с шаблонами C++)

Используя этот шаблон, ваш класс EventTable становится примерно таким:

public class EventTable {
  public void setEvent(Event e) {
    int x = 0;
    columns = e.getFields();
    Event[] savedEvents = e.getSavedEvents();
    for(Event ev : savedEvents) {
      tempdata[x] = ev.getTempData();
      x++;
    }
  }
}

Обратите внимание, что весь переключатель был заменен одним вызовом getTempData(). Затем этот метод является абстрактным в Event, как и getSavedEvents:

public abstract class Event {
  public Date getDate() { return(_date); }
  public abstract Event[] getSavedEvents();
  public abstract int[] getTempData();
  public int[] getFormattedDate() {
    ...

}

Затем вы определяете метод getTempData() в каждом подклассе. Например:

public class WeightEvent extends Event {
  public int getWeight() { return(_weight); }
  public int getReps() { return(_reps); }
  public int[] getTempData() {
    return new int[]{
      getFormattedDate()[0],
      getWeight(),
      getReps()
    };
  }
}

public class TimedEvent extends Event {
  public String getTimeInHMS() { return(_timeString); }
  public int[] getTempData() {
    return new int[]{
      getFormattedDate()[0],
      getTimeInHMS()
    };
  }
}

public class RepEvent extends Event {
  public int getReps() { return(_reps); }
  public int[] getTempData() {
    return new int[]{
      getFormattedDate()[0],
      getReps()
    };
  }
}
person Laurence Gonsalves    schedule 19.12.2009
comment
Чувак, я бы хотел, чтобы ты опубликовал это на несколько дней раньше. :) Проект уже есть, но я думаю, что это было бы лучшим решением. Я не думал о возврате массивов напрямую. Не каждое значение в возвращаемом массиве является INT (некоторые из них являются строками), но я думаю, что смог бы справиться с этим. Спасибо за это - это может пригодиться в будущем. - person JHolmes763; 20.12.2009

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

Однако, если нужные вам методы специфичны для типов дочерних элементов, и вы не можете придумать подходящую абстракцию, которую все они могут реализовать в общем, то либо вы должны использовать instanceof в своем коде setEvent, либо вы < em>do придется перегрузить ваш метод... потому что вам придется вызывать разные фрагменты кода в зависимости от точного типа события.

Это все немного расплывчато, потому что мы не можем видеть ваш код, кроме пары сигнатур методов. Если бы вы могли предоставить нам более подробную информацию о том, что вы пытаетесь сделать, особенно с точки зрения того, чего должен достичь setEvent, и какие существуют различные методы в дочерних классах, мы могли бы помочь больше.

person Jon Skeet    schedule 17.12.2009
comment
Спасибо. Это работает для метода getSavedEvents(), поскольку его реализуют все дочерние элементы. Однако я добавил ответ ниже с некоторыми другими проблемами. Объект WeightEvent имеет методы getWeight() и getReps(), а объект TimedEvent имеет метод getTime(). В моем методе setEvent(Event e) e приводится к Event, поэтому вызов e.getWeight() или e.getTime() вызывает ошибку. Если я сделаю эти два метода абстрактными, то теперь мне придется реализовать метод getTime() в WeightEvent, что не имеет смысла (время не имеет отношения к WeightEvent). Спасибо за помощь. -Джон - person JHolmes763; 17.12.2009

Для этого можно использовать дженерики.

Определите класс Event следующим образом:

public abstract class Event<T extends Event> {
    public abstract void setEvent(T e);
}

Это определяет класс, который должен быть создан с любым типом, расширяющим Event.

Затем в своих дочерних классах вы реализуете что-то подобное, используя дочерний класс в качестве универсального типа:

class WeightEvent extends Event<WeightEvent>
{

    @Override
    public void setEvent(WeightEvent e) {
        ...
    }

}
person Robert Christie    schedule 17.12.2009

Я думаю, что ваша проблема вызывает getSavedEvents() при наличии переменной Event.
Если это так, добавьте абстрактный метод getSavedEvents() к Event, который также должен быть объявлен abstract :

    public abstract class Event {
        public abstract Events getSavedEvents();
        ...
    }

поскольку Event является абстрактным, вы не можете создать его экземпляр; он должен быть подклассом для использования. Если это проблема, создайте исключение или сделайте что-нибудь разумное для вашего приложения (вообще ничего, просто верните ноль) в Event.getSavedEvents():

    public class Event {
        public Events getSavedEvents() {
            throw new UnsupportedOperationException("must be called in a child class");
            // OR return null;
        ...
    }

теперь вы можете вызвать метод getSavedEvents() в другом объекте:

    public class OtherObject {
        private Event event;
        public void setEvent(Event e) {
            event = e;
            ...
            Events events = event.getSavesEvents();

будет использоваться метод, реализованный реальным классом e, например. если e является TimedEvent, будет вызван метод в этом классе.

person user85421    schedule 17.12.2009

Вы можете абстрагироваться от проблемы за интерфейсом

interface IEvent
{
    abstract public void doSomething();
}

Затем реализуйте его во всех ваших классах событий, например.

class WeightedEvent implements IEvent
{
    public void doSomething()
    {
        // do something
    }
}

Тогда вам нужен только один метод и не нужно выполнять проверку типов

public void setEvent(IEvent e)
{
    e.doSomething();
}

ХТН

person Simon    schedule 17.12.2009

Возможно, вы можете использовать шаблон посетителя.

person Nikolay Ivanov    schedule 17.12.2009

Использование abstract помогло с методом getSavedEvents(), так как все дочерние элементы реализуют этот метод.

Вот код для setEvent():

public class EventTable {
public void setEvent(Event e) {
 int x = 0;
 int type = e.getEventType();

 columns = e.getFields();
 Event[] savedEvents = e.getSavedEvents();
 for(Event ev : savedEvents) {
  tempdata[x][0] = ev.getFormattedDate()[0];
  switch(type) {
   case EVENTTYPE.WEIGHT:
    tempdata[x][1] = ev.getWeight();
    tempdata[x][2] = ev.getReps();
   break;
   case EVENTTYPE.TIMED:
    tempdata[x][1] = ev.getTimeInHMS();
   break;
   case EVENTTYPE.REP:
    tempdata[x][1] = ev.getReps();
   break;
  }
  x++;
 }
}
}

Этот код работает после того, как я добавил «abstract» в класс Event и определил абстрактный метод с именем getSavedEvents().

Следующая проблема связана с методами getWeight(), getReps() и getTimeInHMS(). Они специфичны для типа дочернего события и опять же не существуют в родительском классе Event. Если я сделаю их абстрактными в Event, теперь мне придется определять их в каждом дочернем элементе, даже если getReps() не имеет контекста для TimedEvent.

public class Event {
 public Date getDate() { return(_date); }
}
public class WeightEvent extends Event {
 public int getWeight() { return(_weight); }
 public int getReps() { return(_reps); }
}
public class TimedEvent extends Event {
 public String getTimeInHMS() { return(_timeString); }
}
public class RepEvent extends Event {
 public int getReps() { return(_reps); }
}

Сокращенный код, очевидно. С событиями WeightEvents связаны дата, вес и количество повторений. TimedEvents имеют дату и продолжительность, связанные с ними. RepEvents имеют дату и количество повторений, связанных с ними. Все методы даты находятся в родительском элементе, поскольку они являются общими для событий.

Если я не сделаю getWeight(), getReps() абстрактными и объявлю их только в дочернем элементе, где они актуальны, вот ошибка, которую я получаю от EventTable в скопированном выше методе setEvent():

EventTable.java:124: cannot find symbol
symbol  : method getWeight()
location: class Event
     tempdata[x][1] = ev.getWeight();

-Джон

person JHolmes763    schedule 17.12.2009

Вы можете привести объект Event e к дочерним классам — я думаю, вам поможет оператор instanceof в Java.

person Conrad Meyer    schedule 17.12.2009
comment
В Java нет оператора typeof. Возможно, вы имели в виду instanceof или getClass(). - person Laurence Gonsalves; 17.12.2009
comment
В Java есть оператор instanceof, а не typeof. - person Juha Syrjälä; 17.12.2009