0

I have an architecture based on a domain with multiple object types. These objects (many and of disparate types) must reside together with each other because they make up a use-case. The types are not more than 20-30, but might be added along the way, and I do not know if type-safety is worth the maintenance. Let me make this more clear.

My architecture has to support various, (well...more or less) well-defined use cases for those objects, for example visualization, custom hierarchical relationships etc, which are, however, either strictly domain-knowledge (e.g. hierarchy) or strictly foreign to the domain (e.g. visualization styling). I am considering using a general repository (weakly-typed, with "objects") and provide the additional infrastructure-based behavior for each object through suitable decorators (e.g. VisualizationDecorator, HierarchyDecorator) that wrap the domain relationships (e.g. hierarchy) or, generally, the expected behavior for the various use-cases (e.g. visualization). However, the decorators will have to be created from objects, so multiple dispatch will be necessary.

Right now I am thinking of creating Decorator classes for every use-case (e.g. KnifeVisualizationDecorator would wrap the image of a knife, potentially with some color, dimensions etc., based on the properties of an actual Knife object, a DrawerHierarchyDecorator wraps a Drawer object and provides the Children property as a list of objects, known to the Decorator to be the Knives, Spoons and Forks of the strongly-typed Drawer object, etc.). Then, general Decorator-provider classes will perform the dispatch and create and dispatch suitable decorators for each use case. For example a VisualizationDecoratorProvider checks the type of an object and returns a KnifeVisualizationDecorator, or a SpoonVisualizationDecorator etc. The consumer class will have no idea about the specific type of the decorator, all it cares about is how to visualize the spoon, knife, or fork, so the visualization decorator interface only contains a suitable visual (or appropriate properties to construct one thereof, based on the requirements of the underlying infrastructure).

As a consequence of this approach, any infrastructure "service" class will require a means to retrieve decorators for various objects passed to it. With that in mind, I will have to create decorator abstract factories, perhaps, which will, of course, be the materialization of the corresponding decorator providers. The problem is that it is necessary to "register" the various specifics for each separate object type the entire architecture will have to be able to serve. This way, I am dangerously approaching a Service Locator lookalike which will be the overall provider that checks the type of an object, invokes a suitable decorator-provider to wrap it into a decorator and hands it out to the requester.

This may not look like the exact Service Locator pattern at first, but if I supply the general "decorator-provider-provider" (decorator-provider resolver, actually) to many classes, this will be it. I can avoid this if I only supply specific decorator providers based on the needs of each infrastructure class and avoid the overall resolver.

So, my question is, is it a good architecture to unify a set of behaviors by extending weakly-typed objects based on their run-time type through corresponding decorators, which will be resolved based on run-time type-based dispatch by corresponding resolver classes injected to the various infrastructure classes that need them?

EDIT: To clarify my question a bit, to put this architecture in place, I have to create Decorator Services that supply suitable decorators based on the object type. So, a class requiring a VisualizationDecorator from an object will have to be handed a VisualizationDecoratorService upon construction (or VisualizationDecoratorProvider, if you like) and request the VisualizationDecorator from that as in:

VisualizationDecorator visual = visualizationDecoratorProvider.Decorate(obj);

These Decorator-providers will have to be handed to each infrastructure class (e.g. a visualization service). Is this going to pose a significant problem in the long-run, which I might be missing at the moment?

4
  • What practical problems does this architecture solve in your specific application? Are the benefits you will derive by using this architecture worth the costs? Commented Jul 24, 2019 at 20:25
  • This way, I can keep the domain libraries clean from details such as, for example, visualization. I understand that my domain objects should not know how they will be visualized in one environment or another. They do contain one piece of the "truth" (their properties, which definitely affect visualization), but the rest part of the truth is part of the requirements and has to be flexible. I thought I should use decorators to bridge the "truths" and provide the complete visualization logic. This way, if requirements change or domain is affected in any way, I replace decorators with new ones. Commented Jul 24, 2019 at 20:47
  • Why a mess of decorators and factories and providers? Skimming this it seems more like you want something like an entity-component architecture, where your concrete objects can have a bunch of additional capabilitied with implementation details specific to an entity type. If how the “visualization” is done is specific to a knife, then just have a knife be able to return a suitable implementation of a Visualizable interface.
    – millimoose
    Commented Jul 25, 2019 at 3:15
  • It seems to me like you’re adding at least two extraneous layers of indirection to something where the coupling of Entity and EntityCapabilityDecorator seems self-evident, and you should be able to get the separation you want using just ECS/Traits.
    – millimoose
    Commented Jul 25, 2019 at 3:18

1 Answer 1

1

Hmm, nothing mentioned here smells like service locator to me. Sure you have a pile of un-decorated objects that need to be wrapped in decorators (for a reason I'm not sure has been fully specified). But that doesn't have to be implemented with a service locator like thing.

There is one question I can ask you that will tell me if you're headed in the service locator direction:

Do the clients that use these decoratorated objects KNOW how to get them?

That is do they reach out and ask for them or build them themselves rather than politely allow the decorated doohickeys to be handed to them? That is, passed in.

If the clients don't KNOW about this decorating thing then you aren't making a service locator. You're rolling your own mini DI container. Use it in main (or any method that is only called once) and have it build you a lovely decorated object graph. Then grab one object and call start() and get the whole thing ticking.

This pattern is exactly what many DI containers have you do. It also works when you roll your own.

Just don't let the container spread around. Spread around a DI container and it becomes a service locator. The more code that knows about it the worse this smells.

9
  • As an example, assume a "Home" domain object, which, among other things, contains its surrounding layout rectangle (akin to a bounding box viewed from top). My visualization engine should not know what Home means, because if I later create a Boat, I have to add specific visualization for this too. A VisualizationDecoratorProvider takes a Home object and builds a suitable HomeVisualDecorator that exposes a bounding box the visualization engine can now understand. But for this, the VisualizationDecoratorProvider must know what "Home" means and here also comes type-based dispatching. Commented Jul 24, 2019 at 20:55
  • Also, the decorators must be created at run-time because that is when objects are created and make their way towards the visualization engine. Conversion code from domain to visualization decorator is there, but it has to be executed at run-time, I cannot create decorators up-front (i.e. in the composition root), only put the "decorator-factories" in place there and have them supply. This is why I thought about abstract-factory-like such providers. Commented Jul 24, 2019 at 20:59
  • The composition root runs at run time. I think you mean you need them when some event has occurred. This is fine. You can inject a decorator-factory in whatever is called on that event. I've done this to inject timestamps of all things. What you shouldn't do is let the clients using the decorators ask for them. Don't inject your decorator-factory into them. Just inject the decorated doohickeys into them. This means that whatever detects the event should NOT be the client. That event detecting thing is a mini composition root here. Separate construction and use. Limit knowledge of the container Commented Jul 24, 2019 at 21:13
  • Yes, I meant that decorators are created at run-time past and beyond the composition root. By the time of creation, everything has to be in place. So what you are saying is that I should let the consumer only handle the ready-made decorators (use method injection), do not pass plain objects to the consumer (e.g. visualization engine) and let it use the decorator provider to convert them to decorated objects that it can understand. This means that a class created somewhere else will have to bridge this gap, i.e. decorate an object and pass the decorated object to the visualization class, right? Commented Jul 24, 2019 at 21:22
  • Sounds right. Not sure that the decorators have to be pre-made. My central point is you'll avoid service locator problems so long as the things using the wrappers have them passed to them. In their constructors or in their setters. Just don't make them call something to get them. The clients shouldn't even have a reference to the decorator-factory. Commented Jul 24, 2019 at 22:11

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.