views:

357

answers:

8

Not sure how to title this...

So I've got three child classes of Event: WeightEvent, TimedEvent, RepEvent. Through whatever means, I get an object of one of the children. Now I want to send that child event to a method in another object so it can pull the data from it with the getSavedEvents() method. The method only exists in the children since pulling the data is specific to the type of event.

I started with

public void setEvent(Event e) {

but that cast my child object to an Event (parent) object.

Is there any way around this short of writing three different methods. One each for the children?

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

Thanks for any advice.

-John

+7  A: 

Even though the reference is cast, it doesn't change the type of the actual object. When you pass the reference on, it will still be a reference to an instance of the child object. Normally this would be enough, with appropriate abstract methods in the parent type if necessary.

However, if the methods you want are specific to the types of the children and you can't come up with an appropriate abstraction which all of them can implement generically, then either you've got to use instanceof within your setEvent code or you do have to overload your method... because you're going to have to call different bits of code depending on the exact type of the event.

This is all a bit vague because we can't see any of your code except a couple of method signatures. If you could give us more details about what you're trying to do, particularly in terms of what setEvent needs to achieve and what the different methods in the child classes are, we may be able to help more.

Jon Skeet
Thanks. That works for the getSavedEvents() method since all of the children implement it. I added an "answer" below with some more issues, though. A WeightEvent object has methods getWeight() and getReps() while a TimedEvent object has a getTime() method. In my setEvent(Event e) method, e is cast to Event, so calling e.getWeight() or e.getTime() throws an error. If I make those two methods abstract, now I have to implement a getTime() method in a WeightEvent, which makes no sense (time is not relevant to a WeightEvent). Thanks for the help. -John
JHolmes763
A: 

You could cast the Event e object to the child classes -- I think the instanceof operator in Java will help you.

Conrad Meyer
Java doesn't have a typeof operator. Perhaps you meant instanceof or getClass().
Laurence Gonsalves
Java has instanceof operator, not typeof.
Juha Syrjälä
My mistake. Fixed.
Conrad Meyer
A: 

You could abstract the problem out behind an interface

interface IEvent
{
    abstract public void doSomething();
}

Then have all your event classes implement it, e.g.

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

Then you only need a single method and don't need to do any type checking

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

HTH

Simon
+1  A: 

You could use generics to do this.

Define the Event class as follows:

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

This defines a class that expects to be created with any type that extends Event.

Then in your child classes you implement something like this using the child class as the generic type:

class WeightEvent extends Event<WeightEvent>
{

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

}
Robert Christie
+1  A: 

I think your probem is calling getSavedEvents() when having an Event variable.
If so, add an abstract getSavedEvents() method to Event, which must also be declared abstract :

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

since Eventis abstract you can not create an instance of it; it must be subclassed to be used. If that is a problem, throw an Exception or do anything reasonable for your application (nothing at all, just return null) in Event.getSavedEvents():

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

now you can call the getSavedEvents() method in your other object:

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

the method implemented by the real class of e will be used, e.g. if e is a TimedEvent, the method in that class will be called.

Carlos Heuberger
Why the downvote? not much helpful downvoting without commenting...
Carlos Heuberger
A: 

May be you can use a Visitor pattern.

Nikolay Ivanov
A: 

Using abstract helped with the getSavedEvents() method, since all of the children implement that method.

Here's the code for 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++;
 }
}
}

This code works after I added "abstract" to the Event class and defined an abstract method called getSavedEvents().

The next problem is the getWeight(), getReps() and getTimeInHMS() methods. They are specific to the type of child event and again don't exist in the parent Event class. If I make them abstract in Event, now I have to define them in each child, even though getReps() has no context for a 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); }
}

Abbreviated code, obviously. WeightEvents have a date, weight and reps associated with them. TimedEvents have a date and length of time associated with them. RepEvents have a date and number of reps associated to them. The date methods are all in the parent since they are common across events.

If I don't make getWeight(), getReps() abstract and only declare them in the child where they are relevant, here's the error I get from EventTable in the above copied setEvent() method:

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

-John

JHolmes763
+1  A: 

Instead of switching on the type you should call a method on the event that's defined differently for each type of event type. This is called the Template method pattern. (It has nothing to do with C++ templates, BTW)

Using this pattern, your EventTable class becomes something like this:

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

Note that the entire switch has been replaced with a single call to getTempData(). This method is then abstract in Event, just like getSavedEvents:

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

}

Then you define the getTempData() method in each subclass. For example:

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()
    };
  }
}
Laurence Gonsalves
Man, I wish you'd have posted this a few days earlier. :) Project is already in, but I think this would have been the best solution. I wasn't thinking about returning arrays directly. Not every value in the returned array is an INT (some are Strings), but I think I would have been able to handle that. Thanks for this - it may come in handy in the future.
JHolmes763