5

Assuming we have polymorphic entities such as the following, with constructors enforcing invariants (assume there could be lots of sub-classes). What would be an effective/elegant approach to construct each concrete instance type while avoiding excessive code duplication?

abstract class Something {
   Data fieldA;
   Data fieldB;

   Something(Data fieldA, Data fieldB) {
    ...
   }
}

class SomethingA extends Something {
   Data fieldC;
   Data fieldD;

   Something(Data fieldA, Data fieldB, Data fieldC, Data fieldD) {
    ...
   }
}

class SomethingB extends Something {
   Data fieldE;
   Data fieldF;

   Something(Data fieldA, Data fieldB, Data fieldE, Data fieldF) {
    ...
   }
}

For instance, assume fieldA and fieldB are complex to resolve, we'd want to avoid:

createSomethingA(dto) {
   fieldA = fieldAResolver.resolveFor(dto); //this will get duplicated everywhere
   fieldB = fieldBResolver.resolveFor(dto);
   return new SomethingA(fieldA, fieldB, dto.fieldC, dto.fieldD); //mapping logic as well
}

createSomethingB(dto) {
   fieldA = fieldAResolver.resolveFor(dto);
   fieldB = fieldBResolver.resolveFor(dto);
   return new SomethingB(fieldA, fieldB, dto.fieldE, dto.fieldF);
}

When entities don't have to be constructed in a valid state, it's easy as you can just start with an empty entity, and then apply a series of transformations. But that can't be done for always-valid entities. The only approaches I could think of right now are:

  1. Group fine-grained data bits into more coarse-grained bits. For instance, we could group fieldA and fieldB into a BasicInfo value object, and then do something like basicInfo = basicInfoFactory.from(dto), reducing the number of duplicate calls, but there would still be at the very least N duplicated calls for N concrete sub-classes.

  2. Change the whole constructor signature to take a single SomethingData class instance which is mutable. This allows us to construct SomethingData through a series of transformations instead. In the end, the Something classes would have null checks to perform, or Optional checks to ensure the data they need is present or else throw. (Same as #1 really, but with mutability).

Are there other alternatives or patterns for that?


EDIT:

Just to give a bit more context, it's a request management system with disparate request types. The requests have a bit of common info and workflow behaviors, but have very distinctive shapes and rules overall. Some of the rules may span the entire request's data. It's a CRUD-based system for the most part or at least this seemed to be the simplest route.

Perhaps inheritance is a mistake. Another drastically different approach could have been to use a schema-driven design, where we shape requests through linked data components. Components would just be associated (no composition) and most likeky changed together in a single tx for consistency.

11
  • Sounds like a good case for statefull abstract factories – Bwmat Feb 2 at 4:21
  • I wouldn't tag this one under Domain-Driven Design. When modeling business domains in DDD style, the situation presented here is very likely to be an anti-pattern. Something like Customer extends Person is a possible situation in theory, but in practice, it will lead to undesired coupling. – ZioBrando Feb 2 at 11:30
  • Sounds like a use case for a DI container. – Doc Brown Feb 2 at 13:29
  • @ZioBrando Well, how would you approach the design with DDD tactical patterns? The use case is pretty clear in the EDIT section. Assume there could be hundreds of conceptual request specializations. I'm open to suggestions! – plalx Feb 2 at 18:25
  • The biggest red flag here is the phrase "always-valid" entities. Validate behaviors not data. That is the perspective DDD seeks to provide. Here is a similar question: softwareengineering.stackexchange.com/questions/372338/… – king-side-slide Feb 3 at 15:33
1

Let us assume you don't want to turn SomethingData into a mutable class (of course, a blurry name like SomethingData isn't particular helpful to evaluate such design decisions, but let us assume this makes most sense for this case).

Without introducing a DI container, your solution #1 is the straightforward solution to this I would recommend. The basicInfo object can now be initialized step-by-step ("through a series of transformations"), and any duplicate code in this initializations can be refactored out into separate functions (or, as the question demonstrates it, into some basicInfoFactory class).

but there would still be at the very least N duplicated calls for N concrete sub-classes.

Yes, but these calls are just delegation calls, they don't duplicate any real logic, hence they are not a DRY violation. They have actually the opposing purpose of reusing non-repeated logic.

When you have code which deals with N different subclasses, you will need at least N different constructor calls. If those subclasses are immutable, there will be some individual initialization logic required for each of those subclasses which cannot be put into member functions of that class, since all the initialization must be completed before an object is constructed.

There is indeed another solution, as I mentioned at the beginning: making use of a DI container. For this, you would introduce an interface IBasicInfo into your constructor parameters, safe the factory calls, and let the container figure out the necessary constructor calls.

1
  • Yeah I guess that's the only solutions there is without a complete redesign using composition instead of inheritance or representing the large cluster as individual associated data components (schema-driven), which are changed in a single TX for consistency. – plalx Feb 3 at 12:37
0

Depends of course, but I would try to extract the polymorphic behavior in its purest form, preferably into an interface. This would avoid inheritance, which is a tricky and hard to get right anyway.

So basically, extract the thing that needs those common values into its own thing. Try to find an abstraction that makes sense in your domain. Then what's left is the polymorphic stuff that is now unburdened with all those constructor parameters.

4
  • Wouldn't that be similar to my suggestion #2? In our situation it's a case management system where there's a multitude of concrete cases. We could have used composition instead of polymorphism I guess (e.g. Request--Type--DataSchema) where the type defines the data requests can have, but it seemed a little too abstract for now. – plalx Feb 1 at 20:29
  • I don't think so. Both of your solutions essentially just group the data, which the objects still need. I'm suggestion to move not just the data, but its behavior with it, so the polymorphic logic is not burdened by it. – Robert Bräutigam Feb 1 at 20:52
  • Could you give an example. Imagine large CRUD document entities. There's a core bloc of data they all have. Then all documents have their own shape and behaviors according to the data they are composed of. For instance, an entity may be a PrinterRepairRequest where another could be an AccountPasswordChangeRequest. Requests have little in common except some core data & workflow behaviors. However, some rules may span across many of their data components making difficult to break the large clusters. – plalx Feb 2 at 1:38
  • I've always been a big supporter of always valid entities and small cluster ARs, but DDD tactical patterns don't seem to be a good fit here. – plalx Feb 2 at 1:48

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.