0

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.

Alex
  • 21,273
  • 10
  • 61
  • 73
aliteralmind
  • 19,847
  • 17
  • 77
  • 108

3 Answers3

2

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...

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • This is an interesting idea. Thank you for the insight. – aliteralmind Jun 10 '14 at 22:33
  • @aliteralmind: You still have the circular **type** dependency, but that's trivial to eliminate by defining interfaces, and then having `Foo` and `Bar` implement them. – Oliver Charlesworth Jun 10 '14 at 22:34
  • Actually, I've come up with a completely different approach, which I've posted in an answer, but this idea will be helpful with some other things. Thanks again. – aliteralmind Jun 10 '14 at 23:55
1

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • I guess he means that there is a circular dependency between the classes (source files). Some code quality tools may complain about this, especially if they are in different packages. But indeed I don't think it's really a problem in this case. – herman Jun 10 '14 at 22:03
  • I avoid circular dependencies as a matter of principle. It's just something I choose to do. It makes it much easier to manage a large project when the dependency chain is well defined. Circular dependencies (yes, between source files) is a nightmare, although I do understand it is reasonable in some situations. – aliteralmind Jun 10 '14 at 22:29
  • @aliteralmind: One possible way to avoid circular dependencies between source files would be to define your types as being static types within some other class, thereby avoiding the need to have them in different files. – supercat Jun 10 '14 at 22:51
  • I'm not sure what you mean. All classes in the hierarchy, including the abstract base class, are needed externally, so they all must be public. How do you allow that if they're all in one file? Doesn't that mean only one can be public? – aliteralmind Jun 10 '14 at 23:03
  • @aliteralmind: Classes can contain nested public classes; if class `Foo` contains a class `Bar` within it, then the nested class must be referred to as `Foo.Bar`, which can be a little irksome, but if `Bar` is very strongly associated with `Foo`, even though it's used elsewhere, such declarations can still be helpful. More to the current point, one can have within a single file two such classes that are dependent upon each other. – supercat Jun 11 '14 at 13:37
  • I see. I misunderstood "within" to mean "within the same source file", not "inner". – aliteralmind Jun 11 '14 at 15:03
  • @aliteralmind: I don't know to what extent that would be considered a "proper" use of inner classes, but I don't like the idea of rigidly splitting source files by class. If certain classes are designed to be used together, I think the source code should be arranged so as to indicate that. – supercat Jun 11 '14 at 15:24
  • I agree. If a class has no need for direct public/external use, I would make it private/package-protected in the same source file where it's used. Aside from builders, I haven't use inner classes too much. FYI, I just rewrote my own answer. – aliteralmind Jun 11 '14 at 15:33
  • 1
    @aliteralmind: Non-public classes don't need to be declared as inner classes to allow them to be located conveniently in source. I was thinking more of things which are expected to be *generated* only by a particular class, but which may be used widely once generated. Such types would need to be public (even if the constructor was package-private), but would seem like they belong in the source file with the class which generates them, rather than in a separate source file. – supercat Jun 11 '14 at 15:57
0

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.

aliteralmind
  • 19,847
  • 17
  • 77
  • 108