0

I have a base class PlanItem which all other subclasses extend.

  • An Objective extends PlanItem.
  • A Strategy extends PlanItem.
  • A Tactic extends PlanItem.
  • A Task extends PlanItem.

The catch:

  • An Objective has a list of Strategy objects. Call it its child item list.
  • A Strategy has a child list of Tactics.
  • A Tactic has a child list of Tasks.

I could have made 4 completely cloned classes, word for word, but that would make editing a total pain. I opted to use inheritance, but then comes the problem of circular dependency.

Currently I am:

Letting the base PlanItem class have a field:

private ObservableList<? extends PlanItem> childPlanItems = FXCollections
        .observableArrayList();

I have an FXML with Controller, ViewPlanItemController, that displays a particular item's statistics and child list in a TableView.

public class ViewChildItemsController<E extends PlanItem> {}

That TableView has options for adding, editing, viewing, and removing child items.

If I want to view a child item, then we recursively load another ViewPlanItem.fxml with controller. The problem is, every time I want to load another ViewChildItem.fxml, I need to provide it with the child type so that it can view them.

If I were trying to view an Objective, I would init the controller so that it knows to display items of type Strategy.

Here is the controller's init method, if needed:

/**
 * Initializes the values of the items to be displayed.
 *
 * @param list The list of child items to display.
 * @param defCtor Default ctor of each child item.
 * @param copyCtor Copy ctor of each child item.
 */
public void initValues(ObservableList<E> list, Supplier<E> defCtor, UnaryOperator<E> copyCtor) {
    childItemsTableView.setItems(list);
    statisticsPanelController.init(list);
    this.defCtor = defCtor;
    this.copyCtor = copyCtor;
}

How would I do this? If I am within the ViewPlanItemController class itself, how do I know the type of class the child item is? Seeing as it goes from Objective->Strategy->Tactic->Task, there needs to be some class-level recursion.

Toni_Entranced
  • 969
  • 2
  • 12
  • 29
  • 2
    Why do you need to know the type of class the child item is? Why does the controller need to be told that? Why can't you just treat them uniformly? Is it that the controller needs to create children? (I see you're passing ctors in.) Could you just add a `canHaveChildren` predicate and a `makeChild` factory method, and then the controller wouldn't need to know, and you could treat them uniformly? – David Conrad Aug 26 '14 at 22:28
  • The first item the controller class loads is the child item of an Objective--a Strategy. Within that screen that displays the list of Strategies, you have the option of selecting a strategy and viewing ITS children--a list of Tactics. And so on all the way down. The controller requires a default constructor for the option of adding a new item, however far down we are. It also requires a copy constructor for editing an existing selected item (since the PlanItem is immutable). – Toni_Entranced Aug 26 '14 at 22:39
  • Are you suggesting that I mark Objective, Strategy, and Tactic as being able to have children? Then, the ViewChildItem controller can use a predicate to determine if it should allow further drill down? – Toni_Entranced Aug 26 '14 at 22:40
  • 2
    In part, yes, but also, why not defer the constructing of new or edited items to the PlanItem? It knows what type it is, it can implement the appropriate operation. It seems natural to push that responsibility from the controller onto the PlanItem itself. The fact that the controller would need to have type information provided to it so it could do it suggests that's not the right place for that functionality. – David Conrad Aug 26 '14 at 22:44
  • One of the classes utilizing a passed in ctor is: public class EditPlanItemController {}. This is where you go to edit an existing PlanItem or sub-item. The initValues method of that class uses the copyCtor like so: copyCtor.apply(toEdit); in order to have a new value for adding later. I obviously cannot use new E(), as generic constructors are impossible. That's why I passed in the copyCtor as a UnaryOperator. Should I make the controller class of type PlanItem instead then? This runs the risk of lossy down casting if the item is of a different subclass branch. – Toni_Entranced Aug 26 '14 at 23:03

1 Answers1

2

Since Task instances don't (shouldn't) have child items, it doesn't make sense to put a List of children in one of its superclasses. It would perhaps be better to put that in a different class, maybe:

abstract class ParentPlanItem <E extends PlanItem> extends PlanItem {
    // Moved here from PlanItem:
    private ObservableList<E> childPlanItems = FXCollections.observableArrayList();
    // You might also need these:
    private Supplier<E> childConstructor;
    private unaryOperator<E> childCopyConstructor;
}

That can then be the superclass of Objective, Strategy, and Tactic. It has enough information to properly call the controller's init method. Where you get the values for the childConstructor and childCopyConstructor is a separate question. I might consider making them final static fields of the relevant classes, but the individual class constructors are still going to need to set them. Probably. You might be able to be cleverer.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157