Hey Java programmer, feeling challenged? We've got some design patterns for your OOP architecture that we'll implement in Java and Kotlin to help you out.

Design patterns are well known in software engineering as repeatable solutions to common problems. There are problems, regardless of the language with which you're building your software program, that you will run into in every project you work on. In most cases, design patterns will help you address such problems with established solutions.

Composition and Inheritance

A common question that comes up with object-oriented products is when to use composition and when to use inheritance. Let's start by reminding ourselves of the difference between the two through these helpful quotations from Design Patterns: Elements of Reusable Object-Oriented Software:

"The two most common techniques for reusing functionality in object-oriented systems are class inheritance and object composition."

"Object composition is an alternative to class inheritance. Here, new functionality is obtained by assembling or composing objects to get more complex functionality... This style of reuse is called black-box reuse, because no internal details of objects are visible. Objects appear only as 'black boxes.'"

"Object composition is defined dynamically at run-time through objects acquiring references to other objects. Composition requires objects to respect each others' interfaces, which in turn requires carefully designed interfaces that don't stop you from using one object with many others. But there is a payoff. Because objects are accessed solely through their interfaces, we don't break encapsulation. Any object can be replaced at run-time by another as long as it has the same type. Moreover, because an object's implementation will be written in terms of object interfaces, there are substantially fewer implementation dependencies."

"Ideally, you shouldn't have to create new components to achieve reuse. You should be able to get all the functionality you need just by assembling existing components through object composition. But this is rarely the case, because the set of available components is never quite rich enough in practice. Reuse by inheritance makes it easier to make new components that can be composed with old ones. Inheritance and object composition thus work together."

To summarize, use composition when you feel that doing so will create a polymorphic behavior and code reuse. Evaluate the benefits and drawbacks of each design pattern applied to your architecture design and go with the option that gives you the best trade-off. With the brief background above, let's take a look at implementations for both composition and inheritance in Java and Kotlin.

Before jumping into the code, let's imagine a scenario where the following business criteria are requested (AC stands for acceptance criteria):

AC 1: A retail organization has its brand(s). Let's expose brand's name;
AC 2: After we've developed AC1,  we are asked to expose brand's profit for a specific year;
AC 3: After we've developed AC1 and AC2,  we are told that the created brand should be part of the GreenCompany group.  

Inheritance Implementation
AC1: creating AvenueCodeKids class (brand) and exposing its name: 

A brand (interface) exposing its name was created, as well as AvenueCodeKids (class) returning its name.

AC2: creating profit method:

With some code changes, we now have the profit calculation in place. 

AC3: becoming part of the Green Company group:

Later on down the road, AvenueCodeKids became a Green Company, and then we implemented its functionality accordingly.

Imagine how large and complex this can get if we keep adding interfaces to AvenueCodeKids. We should Keep It Simple.  Let's try a composition implementation now.

Composition Implementation
AC1: creating AvenueCodeKids class (brand) and exposing its name: 

Nothing new at this point, right? Keep going.

AC2: creating profit method:

Now you can see that you have separated the profit calculation from the brand ecosystem, which has many advantages.

Does this sound more complicated? It might, but it gives flexibility and loose coupling, which guarantees maturity to your software. 

AC 3:

Look at AvenueCodeKids now. Look at how decoupled it got.

AvenueCodeKids is much cleaner and ready to be adapted now, don't you think? Imagine more business logic like "ProfitCalculator" or "GreenCompany" added to your class. Following this approach, your code will be more "S.O.L.I.D." Feel like using the Delegate design pattern? Try to improve the above snippet using Lombok Delegate.

Alright. That looks great.

What about code in Kotlin now? Let's implement the same business criteria specified above. (For those who are unfamiliar with Kotlin, it's "an OSS statically typed programming language that targets the JVM, Android, JavaScript and Native. It’s developed by JetBrains. The project started in 2010 and was open source from very early on. The first official 1.0 release was in February 2016.")

Inheritance Implementation
AC1, AC2 and AC3: let's implement all three acceptance criteria at once for simplification:

The syntax is not that different. Above, we have three interfaces and one class (AvenueCodeKids) inheriting all of them.

Composition Implementation
AC1, AC2 and AC3: again, we'll implement all three acceptance criteria at once for simplification:

It looks just like a case of Delegate Design Pattern usage, except it uses "by" as a keyword, and indeed it is!

Closing Thoughts

There's no unique solution/language/design pattern for software development. Design patterns are a big topic in software engineering. You will notice that the more seniority you get, the more design patterns you need to know. There are so many approaches and design pattern combinations that we can use when refactoring or even creating code from scratch. By the way, did you notice that our final version of Composition Java code was 13 lines and Kotlin was just 3? 

If you're interested in further reading, I recommend the following articles and books that I used as references:


Martin Fowler's Design Patterns

Design Patterns: Elements of Reusable Object-Oriented Software


Fabio Zoroastro

Fabio Zoroastro is a Software Engineer at Avenue Code. He's worked with Java ecosystem since 2004. Mathematics, design patterns, DevOps, enterprise applications, mobile, IoT, and front and backend are ares he's used to working with. He is always looking to learn new paradigms and technologies and to spread ideas.

How Java Garbage Collection Works and Why You Should Care - Part 2


How Java Garbage Collection Works and Why You Should Care - Part 1


Introducing JetBrains Academy


What You Need to Know about JShell