3

This question stems from more concrete problem asked here

The problem I would like to discuss here seems to be a very common pattern in software engineering. Many applications make use of two-layered architecture - business layer and a UI layer on top of it. I would like to know what is the most clean OOP way of mapping business objects to their UI presenters (or views, if you like). I will be using Java to illustrate my struggles.

Say we have a graphical framework where the bussiness layer contains shapes like Circle, Rectangle etc. All are derived from a common interface Shape. In the UI we have CirclePresenter, RectanglePresenter etc. all derived from ShapePresenter. The question is how to map an instance of Shape to an instance of ShapePresenter. I would like the system to be extendible so that users of the framework can add their own shapes and presenters without the need for recompilation.

I have seen and also can imagine several approaches but all of them seem to me as hacks which destroy the beauty of good OOP:

Approach 1: Use visitors

If we have a visitor like this

interface ShapeVisitor<T> {
  public T visitCircle(Circle circle);
  public T visitRectangle(Rectangle rect);
}

we can use it to create presenter factories like this:

class Factory implements ShapeVisitor<ShapePresenter> {
  public ShapePresenter visitCircle(Circle circle) {
    return new CirclePresenter(circle);
  }

  ...
}

This pattern does not allow for the framework to be extendible since you would need to add new methods to the visitor every time new shape is added. I was unable to come up with an extendible visitor implementation myself, so this approach seemed useless. However, here is an attempt to create reflective visitor, which claims to solve this exact problem. If this is not the hackiest hack of all Java hacks, then I do not know what is...

Approach 2: Inspired by Eclipse

Eclipse's diagramming frameworks such as GEF and GMF face this exact issue of shapes and presenters. GMF, relies on code generation where you have to specify the shapes and presentors in a different language and then the generator spits out Java classes with tons of instanceof checks. This solution does not seem very OOP clean to me, not to mention the generator limits your expresivity. GEF, on the other hand, keeps track of the shape->presenter mapping using HashMaps. When you add new presentor, you have to add an entry to the central registry which is managed by some all-encapsulating god object such as PresentationLayer.

The GEF approach makes the most sense to me but I am still not happy with it since it adds too many mamagement code to the framework. It seems like 20% of the code focuses on the problem at hand - defining and drawing shapes, and the other 80% is there to unsure extendibility. So in this case the most of the code consists of transcendental classes like AbstractFactoryExtensionPoint, CompositeAdapterServiceLocator or GenericDecoratorRegistry. I only naivly wanted to draw some rectangles and circles in an extendible way and I ended up with bunch of AbstractAdaptableCompositeRegistryServiceLocators which noone can understand without reading through hundred-page documentation.

Any ideas?

5
  • 1
    Wow, that's a very thorough question. Could you perhaps summarize how this question differs from your previous question Clean OOP way of mapping an object to its presenter?
    – amon
    May 25, 2017 at 19:47
  • 1
    The previous question was too concrete and people were giving answers not to the general problem but to its particular instance and those answers were in some cases not applicable to the general problem.
    – lishaak
    May 25, 2017 at 19:51
  • Can you define what you mean by "business layer?" IMO this is the chief point of confusion in most discussions of this nature. Speaking as a web UI dev, how a circle is drawn, has nothing to do with business logic. May 27, 2017 at 3:28
  • Exactly. How a circle is drawn is the matter of UI layer. The business layer only specifies the parameters of the shapes. Does my question suggest otherwise somewhere?
    – lishaak
    May 27, 2017 at 9:02
  • Use Ruby instead. Add interfaces to classes in separate modules so that there is just one type with one shared state but many features which observe or manipulate it but do not otherwise interfere with each other. Not certain how I would do that outside Ruby, especially in Java. Perhaps have one private class that keeps the state and another public one that constructs interfaces of other types that are able to point to it? Java just makes everything hard.
    – sqykly
    Jun 2, 2017 at 17:32

2 Answers 2

2

I have read your original question also, and it seems to start from a faulty assumption, that presentation and "business logic" needs to be completely separated.

There is no such rule in object-orientation. In fact, a good argument could be made that completely separating these things is actually an (often repeated) anti-pattern. The argument being, that you break cohesive parts out of the object.

Normally, to present something (in your example a Circle, Rectangle, etc.) you need to know exactly what it is, what its properties are, what it means. In other words, it needs information otherwise only available in the object itself. It is almost impossible to create a "CirclePresenter" outside of the Circle object, without opening up the Circle (making its internals available, defeating encapsulation).

So, what is an OO alternative? Well, the object should contain at least some abstract notion of its presentation. For example:

public interface Shape {
    void display(Canvas canvas);
}

A Shape can draw itself.

There are of course a lot of alternatives how you can do this, depending on what the exact requirements are for your application. The display() could return vertices, or triangles for 3D presentation, an image, vectors, javascript, html code, etc.

The common theme is, that there should be no external agency that actually knows or cares about the internals of specific shapes, and while the specific shapes themselves may not know the specifics of the presentation (like colors used, size, etc.), they do know some suitable abstract notion of presentation.

6
  • This is extremely interesting thought and it touches on the very fundamentals of OO philiosophy. I feel both excited and discouraged. Excited because I really want to dive into the realm of good OOP design and really understand what good design is. On the other hand I am starting to feel that no such thing as "good OO design" a "the right principles of OOP" really exist.
    – lishaak
    Jun 7, 2017 at 8:30
  • ... everybody has his own take on OO design, what is a good pattern for one is an anti-pattern for another, there are many rules such as SOLID but I haven't every seen a piece of software which would strictly obey them all and I haven't seen a language which would allow to obey those rules without need for compromises, hacks and tricks.
    – lishaak
    Jun 7, 2017 at 8:30
  • You're absolutely right, there is no consensus, even in pretty basic things like what constitutes a good OO design. That however does not mean that every idea is equally good or bad. Until we have proper studies, I guess we just have to rely on experience. What decisions result in more maintainable code, easier change, more readability, maybe even less bugs... For example do it both ways, and see which one is easier to maintain, add new features, etc.... Jun 7, 2017 at 8:53
  • @RobertBräutigam While I think this answer is useful (+1) in showing that some objects such as shapes can have some abstract notion about how they can be represented, this doesn't seem to be the case, at least in my understanding, for all objects. For instance, I don't see how can a number draw itself. I don't think that numbers have any inherent notion of how they can be represented. It seems like they could be represented in any arbitrary number of ways. Sep 12, 2023 at 15:09
  • 1
    @MehdiCharife Good observation. Not all objects need to present themselves, it all comes down to the requirements. Specifically for numbers however, in most languages those can present themselves into string form, which turns out is pretty flexible and is used all the time for "displaying" a number into the log, onto a web UI, and even GUIs. Sep 12, 2023 at 19:30
1

I can relate to problems you run into with Approach 1 in a framework and agree with you that is not an extensible approach for a framework. It breaks the Open/Closed Principle. That's one of the main reasons to avoid it.

I am not familiar with Eclipse but I get the basic idea of the GEF approach from your description. From my experience of working in a large scale C++ framework, I think it's the best approach for a framework.

The list of issues that need to be addressed in an extensible framework:

  1. Identify an extensible set of types.
  2. Identify an operation (get something, set something, do something, etc.) that can be applied on any instance of the leaf level types.
  3. Provide a mechanism in the framework to allow registration of an agent (class or function) that can perform the operation for a leaf level type.
  4. Add a new type.
  5. Add the agent (class or function) that can perform the operation for the new type.
  6. Register the agent with the framework.
  7. Implement a function in the framework to perform the operation on an object. The implementation makes sure that the right agent is used for the object.

When it comes to code artifacts, you need to have code in the framework for 1, 2, 3, and 7 and code in the extension for 4, 5, and 6.

No matter what you do, you cannot avoid 4 and 5. Also, you cannot reduce the amount of code in 4 and 5. Your goal needs to be to make code for 6 as simple as possible.

In C++, we use a pattern that has served my team well.

  1. We use a namespace that is specific to a .cpp file.
  2. In that namespace, we define a class called Initializer.
  3. We define a file scoped static instance of Initializer.
  4. The constructor of Initializer contains code that takes care of initialization that is appropriate for the .cpp file. Many times, it registers a class/function with the framework for the type associated with the .cpp file.

For instance, in Square.cpp, you could use something along the lines of:

namespace Square_NS
{
   struct Initializer
   {
      Initializer();
   };

   void drawSquare(Square& square)
   {
      // Do the needful to draw a square.
   }
}
using namespace Square_NS;

static Initializer initializer;

Initializer::Initializer()
{
   ShapePresenter::registerFunction<Square>(drawSquare);
}

Hope you can translate that appropriately to your language of choice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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