Reputation: 20163
Heirachy:
LineEntityType
SingleLineEntity
BlockEntity
Blocks can contain other blocks or single line entities. All entities can have a parent, which is always a BlockEntity
I want to do something like this:
BlockEntity rootBlock = new BlockEntity(...);
block.assignChildren(new LineEntityType[] {
new SingleLineEntity(...)
new BlockEntity(...)
new BlockEntity(...)});
so the parent block object (rootBlock
) duplicates each child (defensive copies) and at the same time adds itself as the parent:
BlockEntity(LineEntityType[] children) {
for(LineEntityType[] children) {
//Duplicate the array
childEntitiesWithParentAssigned = Arrays.copyOf(children, children.length);
//Duplicate each child, adding "this" as the parent
for(int i = 0; i < children.length; i++) {
child = childEntitiesWithParentAssigned[i];
childEntitiesWithParentAssigned[i] = child.getCopyWithParentAssigned(this);
}
}
}
This is what I have so far, but it's unacceptable because the parent class, LineEntityType
has multiple references to the child type, BlockEntity
(circular dependency).
public abstract class LineEntityType {
private final BlockEntity parent;
public LineEntityType(...) {
this(..., null); //Root entity
}
public LineEntityType(..., BlockEntity parent) {
this.parent = parent;
}
...
public abstract LineEntityType getCopyWithParentAssigned(BlockEntity parent);
}
The only other thing I've come up with is to explicitely assign the parent before assigning its children:
//null: No parent
BlockEntity rootBlock = new BlockEntity(..., null);
LineEntityType children = new LineEntityType[] {
new SingleLineEntity(..., rootBlock)
new BlockEntity(..., rootBlock)
new BlockEntity(..., rootBlock)});
rootBlock.setChildren(children);
But this requires the children field to be mutable.
Any ideas on how to rethink this, so the parent-field can be assigned by the parent, yet be immutable and avoid circular dependencies?
Thanks.
Upvotes: 0
Views: 197
Reputation: 20163
After reading the comments and answers, and then thinking about it for an hour or so in the car, I decided ChildEntity
and ParentEntity
interfaces would make it work, although it does require a circular dependency between the interfaces.
interface ChildEntity {
int getLevelsBelowRoot();
ParentEntity getParent();
ParentEntity getTopParent();
ChildEntity getCopyWithParentAssigned(ParentEntity parent);
}
interface ParentEntity extends ChildEntity {
int getChildCount();
}
public abstract class AbstractLineEntity implements ChildEntity
class SingleLineEntity extends AbstractLineEntity
class BlockEntity extends AbstractLineEntity implements ParentEntity
Now everything can be immutable, which is the primary goal. As others have suggested, I'm not sure it's possible to avoid the circular dependencies, since all children have a parent, and parents can also be children. For now, I'm going to stick with it and see how it works. It's the only circular dependency so far in the three libraries I'm developing.
Thanks to @supercat and @OliCharlesworth for the input.
Upvotes: 0
Reputation: 272657
It sounds like you're basically saying "I want Foo to point to Bar, and Bar to point to Foo, but also want both to be immutable". Those are mutually contradictory requirements, in general (one object must be created first, at which point it's too late).
The only way around this is to create one during the constructor of the other. You could use some variant of the builder pattern to keep this sane:
// Immutable
class Foo {
private final Bar bar;
public Foo(BarBuilder builder) { this.bar = builder.create(this); }
}
// Immutable
class Bar {
private final Foo foo;
public Bar(Foo foo) { this.foo = foo; }
}
class BarBuilder {
public Bar create(Foo foo) { return new Bar(foo); }
}
Foo foo = new Foo(new BarBuilder());
Bar bar = foo.bar;
Applying this to your particular design is left as an exercise for the reader...
Upvotes: 2
Reputation: 81217
If parent
is a final
field of LineEntityType
, then the constructor for LineEntityType
will have to receive the reference that should be stored there. I'm not quite clear on what you seeing as being a "circular dependency" problem, since the constructor of a subnode can be called from within the constructor of a parent node which is itself under construction, and receive a reference to the parent object being constructed. It shouldn't actually try to do anything with the parent node identified by that reference until after the parent is fully constructed, but that doesn't mean it can't store the reference.
Upvotes: 1