DEV Community

Aleksi Kauppila
Aleksi Kauppila

Posted on

Composition over encapsulation

A big problem with inheritance is that it easily gets out of hand a becames an unmaintainable mess. It very quickly becomes a tree that reaches out to all different directions. Also, when you change a class, it may have unexpected side-effects on inheriting objects.

Many times you hear that you should prefer composition over inheritance. I definitely get that. It tackles a lot of problems what inheritance causes.

But what about encapsulation?

abstract class Report
{
    abstract protected function title(): string;
    abstract protected function contents(): string;

    public function display(): string
    {
        return sprintf('%s\n%s', this.title(), this.contents());
    }
}

class SalaryReport extends Report
{
    private string title;
    private array salaries;

    public function constructor(string title, array salaries)
    {
        this.title = title;
        this.salaries = salaries;
    }

    protected function title(): string
    {
        return this.title;
    }

    protected function contents(): string
    {
        return this.salaries.join('\n');
    }
}

If i'd choose composition over inheritance, i would have to loosen the encapsulation of SalaryReport.title() and SalaryReport.contents() to public. The code would turn into this.

interface ReportData
{
    public function title(): string;
    public function contents(): string;
}

class Report
{
    private ReportData reportData;

    public function constructor(ReportData reportData)
    {
        this.reportData = reportData
    }
    public function display(): string
    {
        return sprintf('%s\n%s', this.reportData.title(), this.reportData.contents());
    }
}

class SalaryReportData implements ReportData
{
    private string title;
    private array salaries;

    public function constructor(string title, array salaries)
    {
        this.title = title;
        this.salaries = salaries;
    }

    public function title(): string
    {
        return this.title;
    }

    public function contents(): string
    {
        return this.salaries.join('\n');
    }
}

Now SalaryReport isn't a real object anymore since it really hasn't got any notable behaviour, it's just data. Of course you could argue that Report doesn't do much either, but for the sake of the example let's go with this.

How can i use composition over inheritance without weakening encapsulation and turning my objects into data structures? It seems both approaches have severe drawbacks.

Top comments (7)

Collapse
 
stereobooster profile image
stereobooster

Define encapsulation

  1. the action of enclosing something in or as if in a capsule.
  2. the provision of an interface for a piece of software or hardware to allow or simplify access for the user.

If we take "1" then inheritance breaks the encapsulation, because other class knows some details for parent class.

If we take "2" then composition doesn't break it - you can provide interfaces and use composition.

Collapse
 
aleksikauppila profile image
Aleksi Kauppila

Am i correct to make the conclusion that #2 supports the idea that Kasey Speakman introduced in his response about DTOs?

Thanks for this response! I think this is turning into a real ”aha moment”! 👍👍

Collapse
 
stereobooster profile image
stereobooster

Maybe to some extent.

The main question is what you try to isolate from what? There are different ways to build isolations, in some PL you can restrict access to inside of the module, ot you can use closures to hide some data, or something like wrapper object which takes callback-functions (like monad).

I'm not sure I fully understand your initial question.

Collapse
 
kspeakman profile image
Kasey Speakman

A report is a compilation of data, almost by definition. It seems like it is more of a produced artifact than something with behavior. Sometimes things are just data. And sometimes functions from one type of data (the report) to another (the view) is sufficient.

I recently made a post that touched on that here.

Collapse
 
aleksikauppila profile image
Aleksi Kauppila

Which solution would you prefer in this example and why? Or would you have some other solution? Thanks!

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

For me the solution would be to make the report "just data" (probably immutable) -- a DTO. If you provide it as data, it can be readily adapted to a specific view technology at render time. Having a "view" behavior could probably never work if it is used in more than one UI technology anyway.

I probably would choose to go WET with this report... no composition or inheritance. Once you need the same set of report fields again for the same reason, then you could worry about refactoring them into their own data class and use composition.

Remember the practices (like SOLID) and paradigms (like OO) should serve the purpose of the code, not the other way around.

Thread Thread
 
aleksikauppila profile image
Aleksi Kauppila

Very nice, thanks for your input 👍