1

I'm developing a full-stack Rest application following a narrative description of a working context. There is a class Job with two subclasses Job_A and Job_B. Job_A produces Report_A while Job_B produces Report_B. I guess there is an abstract superclass Report even if it is not stated explicitly. Each report must be examined and goes through a verification chain built depending on the type of the report itself.

uml-1


I'm tempted to add an abstract class Report and draw a relationship between Job and Report. With this design (UML below) it seems possible to assign any document to any job, which is wrong. I'm also afraid of ending up in a spiral of if document_type to manage documents. If this is the way, is there a design pattern or idiom to formally define constraints on subtypes while keeping a reference to a super-type ?

uml modified


Maybe i should merge the previous diagrams by adding the abstract Report and keeping the relationships of the first design.

3
  • 1
    "I guess there is an abstract superclass Report" - not necessarily; even though both of these types are conceptually reports, abstract classes (or interfaces) do not model arbitrary conceptual hierarchies - it has to be about behavior, where there's a piece of logic that can somehow treat both kinds of reports the same without doing type checks. E.g., maybe your verification chain is the same for all in the abstract? It would help if you provided more info on how you expect these classes to be used w/ respect to the considerations above. 1/2 Mar 26, 2022 at 16:34
  • Now, there is a creational pattern that connects parallel hierarchies, but it introduces a lot of complexity, so I would make sure that it's really needed before applying it - it's called Abstract Factory (1, 2); the abstract Products would correspond to your Job and Report classes. You can also compose Jobs and Reports through the factory (get them out bundled). The assumption is that you can make some use of Job and Report through the base class interfaces. 2/2 Mar 26, 2022 at 16:34
  • 1
    From a business process perspective, does a job really produce the report? Or is the job the subject of the report? Mar 26, 2022 at 20:10

2 Answers 2

2

I'm not quite sure how to visualize this in UML, but in code (I picked C# here) this can be achieved using a generic type:

public abstract class Job<TReport> where TReport : Report
{
    public abstract TReport GetReport();
}

Each derived job can then define its own concrete report type.

public class JobA : Job<ReportA>
{
    // ...
}

This ensures that the specific report type is known when interacting with the report:

var myJob = new JobA();
var myReport = myJob.GetReport();

myReport will be of type ReportA, no casting or guessing necessary.

2

Good news: it's easy to model

If you want to specify that Job A produces Report A,and Job B Report B, you could specify some kind of "covariance" using for example UML subsetting or redefinition:

enter image description here

You could also use association specialization, which is the better approach imho (yes! an association can inherit from another association, even if this is not well known!):

enter image description here

I used simple associations instead of composition, as I assume that reports shall survive the jobs that created them. You'll find more explanations about the suggested alternatives in this fascinating research paper. You may also see some practical illustrations in this question on SO (it's about association classes, but the arguments apply as well for associations in general).

Bad news: it's difficult to implement nicely

Unfortunately, once you have specified the rule in your UML model, you might face some challenges for the implementation: Some languages do not support covariance very well.

Even worse, in the kind of covariance you have, there's no way to deduce covariance of Report A from Job A except the name of the classes. How would this work if you'd opted for Job A with Report 1? So unless you can find some construct with generics (e.g. Job<Report X>) you might be obliged to use some ugly casting.

Some more thoughts about the design

A first question that comes to my mind is, whether it is a good idea that the Report X has also the responsibility to drive the workflow to check and validate it.

A second point, is that a report is a report. Instead of an abstract Report that is specialized, you could instead prefer composition over inheritance. In this case, every Job would produce a Report, but each report would itself be composed of different (eventually several) contents/components:

enter image description here

This will make a much more uniform handling of the reports and will simplify the design. If deemed useful, you could consider a GoF builder pattern in each Job to assemble the reports and their components. But it is not certain imho that you'll really need the sophistication of this pattern.

2
  • In the case where covariance is not supported, I think one can make JobA and JobB both abstract classes and override their Report returning methods by calling an abstract method that returns the corresponding desired Report type. Nov 12, 2023 at 12:30
  • 1
    @MehdiCharife Indeed, this is a valid approach as well! Some languages such as curent C# or Java will let you do covarianc. In C++, it works only with raw pointers. In all other cases, you'll have to return a smart pointer (or a collection) to the common Report ancestor and use polymorphism. In Python, there is a similar restriction as soon as you use a collection return. Of course in those languages you could use an abstract factory for returning families of related objects (e.g. JobA and ReportA, or JobA and a builder of ReportA). Nevertheless, you'd rely on good behavior of the objects.
    – Christophe
    Nov 12, 2023 at 13:48

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.