Recently I have fallen in a situation like this. I'm generalizing the problem because I think it relates more to the structural design than the specific problem.
General problem
There is a hierarchy of classes: an abstract base class Base
and some concretions D1
, D2
, D3
that inherit from it. The class A
contains an object's collection of type Base
. A
requires a computation from some service-class B
but B.process()
method accepts only a collection of type D1
. Let's say that is important because if the input collection contains any other type the value returned is just wrong.
A
have an interface that allows clients to add elements to the internal collection, which is not exposed in any other way. The classes in the hierarchy can be constructed for the same clients and pass the new values to A
; A
have not enough context to construct them itself.
Attempts, questions and thoughts
The major concern for me was the need to determine at runtime the type of each element in the A
collection, so can filter the right ones and pass to B.process()
. Even if it is possible (it is in my particular problem, more later on) it just seems wrong! I think the object who contains references to the abstract base class shouldn't have to know the concrete instances it holds.
I try to:
- Change the parameter type to
B.process(c: Base[])
soA
doesn't have to downcast the type, but it doesn't solve anything:A
still needs to filter the elements or the computation will be wrong. - Pass the complete collection
Base[]
toB.process()
but just defer the problem of selection/downcasting toB
. - Put a
process()
method inBase
soD1
can override the behavior (well known polymorphism). The problem here is that aprocess()
returning aSomeValue
type just have sense forD1
. - Separate the interface that add elements so a more specific
A.addD1Element(e: D1)
method could allow putD1
objects in a different collection and pass that toB
. It should work but also looks... don't know, weird. If method overload based on parameter type is possible at least the process won't be so cumbersome for clients of the class. - Just separate the
D1
class of the hierarchy. This is a more aggressive variation of the previous one. The issue is thatD1
seems related to the whole hierarchy except for the specific requirements ofB
.
Those were some of my thoughts on the problem.
For instance, the language used have support to check the type of an object at runtime (instanceof
) and it is easy to filter the collection based on that check. But as I say my question is more related to the paradigm. What about a language, say for instance C++, where is less handy to make a check like that?
So what could be a solution to this kind of problem? What kind of refactoring or design pattern could be applied so the problem is easy to treat with or simply fades away?
This question looks related, but I believe this is more general (although I provide a more specific context). The most upvoted answer suggest to split in different collections. This is also a think i'm considering, but that forces to change A
implementation every time a new type is added.
Context (problem in action)
I'm asking in a general way because it really intrigues me on that way, but I know most of the time a design can be analyzed only with the context of the particular problem it tries to solve.
The problem at hand is similar to this:
A
is a class (some kind of entity, like a DDD entity) that models a sort of agreement or debt a customer incurs for a service. It has different costs including a monthly pay. Base
and related classes are Payments of different types. They share a lot in common, although most of it is data (date, amount, interests, etc); but there is at least one type of payment that have different, additional information: the monthly payment (D1
). Those payments need to be analyzed carefully so a different class (B
) is responsible for that, using more contextual information and all the payments of that type at once. The service needs the additional data that is specific to those payments so cannot receive an abstract Payment
type (at least not in that design). Other payments doesn't have the specific information MonthlyPayment
does and so they cannot generates the values that business requires and B
is generating (doesn't have sense in other payment types).
All payments are stored in the same collection so other methods of the class can process all payments in a generic way.
This is mostly the context. I think the design is not the best, but I fail to see a better one.
Maybe separating only MonthlyPayment
(D1
) in a different collection as described earlier? But it is not the only payment that requires additional processing (it is the most complex, though), so I could end with different collections for every payment type and no hierarchy at all. Right now there are four payments types and two of them requires additional, specific analysis, but more types can be added later and the issue of need to modify the implementation every time a new type is added persists.
Is this, more discrete approach of different collections by type, a better one here? The abstract base class Payment
can still be used for payments that can be manipulated trough the common interface. Also I can use a layer super type or something like that to allow reutilization of common functionality (the language allows a kind of mixing as well) and stop using the base class as root from a hierarchy.
Uf. I am sorry for the length of the text. I hope it is at least readable and clear. Thank you very much in advance.
Aucun commentaire:
Enregistrer un commentaire