2

I have a class called Canvas that acts as the container for a number of Rectangle.

The Rectangles have the following behavior:

  1. They can be moved within the Canvas.
  2. They can be resized.
  3. They may not overlap, move over each other, or move past the boundaries of the Canvas.
  4. The side of a rectangle can be attached to the side of another rectangle. The attached side of the Rectangle will be equal to the position of the side it was attached to for as long as it is attached to it. This is a one way relationship in that the Rectangle it was attached to will not change due to any change in the attached Rectangle.

To my mind, the position of a Rectangle is a property of that Rectangle and changing the Rectangle's position should consist of setting that property. With that in mind, due to rules 3 and 4, every Rectangle must be able to determine the position of the every other Rectangle on the same Canvas and the dimensions of the Canvas itself, which means that it must have a reference to the Canvas it's contained in. This seems bad due to the circular reference created between every Rectangle and its Canvas.

I've also played with the idea that the Rectangles should not contain the logic that dictates their movement/sizing and to instead have all that logic in the Canvas. The Canvas seems to become somewhat of a god object, containing the attachment mappings for all Rectangles and updating positions due to them when a Rectangle moves as well as exposing an interface that allows you to move, add, and attach Rectangles. The Rectangles do nothing but hold data.

My biggest point of confusion here is that it seems like this type of problem must have been encountered before, but for the life of me I can't seems to describe it generally enough to find another example. Are there any accepted patterns one could use to address this or am I going to have to learn to love the circular references?

3

What you describe is similar to the logic contained in GUI layout managers.

A component in a window does not poll other components to see if it can resize: instead, the layout manager ensures the constraints are met.

Rather than addle Canvas or Rectangle with this extra logic, why not create a third class that belongs to the Canvas and is responsible for ensuring the layout is legal?

It seems that your class requirements are a little different from a typical GUI, in that rectangles are responsible for moving themselves around. What I envision is something like this:

Canvas c = new Canvas();
c.setLayoutManager(new RectangleLayoutManager());
c.add(new Rectangle()); // repeat a few times

Then in whatever code moves rectangles, you might have something like this:

Coordinates c = ...;
Rectangle r = ...;
if (!getCanvas().getLayoutManager().requestMove(this, c)) {
  // Handle the case where a move or resize is not legal
}

This has the benefit that each class has one responsibility. A Canvas is a container. Rectangles are simply rectangles. The layout manager handles the complex logic of ensuring rectangles meet the criteria of how to move. If the criteria were simple, then it might not makes sense to create a new class to handle it. But your criteria is complex enough that the overhead of another class is worth accepting.

| improve this answer | |
  • This was one of the areas that I thought must have solved the problem along with some video game collision detection stuff. I like this solution, but I always cringe a bit at the idea that you have to dot twice to change the state of the objects contained directly by the Canvas. It seems like Car.GetEngine().Start() instead of Car.Start(). Also I'm not sure how much more testable this is. In order to test the LayoutManager you must have a Canvas and likely a few dividers anyway so you might as well just be testing the Canvas directly. Have I misunderstood some part of your design? – gitbox Nov 10 '16 at 3:37
  • @gitbox nope, you got it. Unit testing of rectangles is trivial and not worth doing. Testing the layout manager necessarily involves rectangles, so you would need multiple objects because its primary concern is "manage multiple objects." At that point you are doing more of an integration test. – user22815 Nov 10 '16 at 4:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.