Reduce code duplication without subclass inheritance
Asked Answered
N

2

6

I'm playing around with subclassing vs Interfaces and composition. I end up getting confused about a few things when it comes to the code duplication. As we all know there are alot of scenarios where subclassing and inheritance is just not the way to go, however it's effective in terms of reducing code duplication.

Interfaces are strong and gives great readability if done properly but i cant wrap my head around the fact that it's really not helping me with the reducing of code duplicaton. We can end up in scenarios where subclassing is not effective.. But the possibilities to extend the program is big and whenever we do so, trying to maintain the Open closed principal, we end up doing Realizations / Implementations of Interfaces in absurd amounts of copy paste code, where it's probably avoidable with subclassing (in terms of code duplication).

How do we build up great strategies with Interfaces and Composition where we avoid writing the same methods over and over again? In such a way that we can keep the modularity and stick with the open closed principle at the same time. Do we have any guidelines to how we can fast and effective decide if the code duplication in fact will be worth it in the end?

Cheers

< /wallOfText>

Nubilous answered 29/1, 2018 at 19:16 Comment(2)
There are good books that are covering the question. I would advise you to read "Design Patterns Elements of Reusable Object-Oriented Software" and "Clean code". I found them very interesting.Aerotherapeutics
If you find yourself adding same logic for every implementation of same interface then consider using abstract class that implements that interface and implements most of the common logic in it.Unpeopled
I
4

Object Oriented modelling is very subjective, but the only thing I can thing of here is the old Inheritance vs. Composition discussion: https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose

Based on your argumentation, I believe you often try to extract a superclass from two or more classes with similar code, so they can all share the same inherited methods instead of just duplicate them. Although technically this gives you exactly what you want, you should also take care of the inheritance semantics since it will denote an is-a relationship (i.e., car is-a vehicle, dog is-a mammal, report-screen is-a read-only-screen). Since Java doesn't offer multiple inheritance, you may end up limited and confused if you class hierarchy grows.

So, before starting to extract superclasses for reuse, have in mind that you can also extract this code-i-want-to-reuse units to be part-of other classes (composition).

Sorry for my conceptual example, but here it goes: Both Dog and Lion are mammals and hunters. They should naturally inherit Mammals superclass (with a lot of reusable code among mammals). As not all mammals hunt, we don't want to define a new hunt() method on Mammals class.

At this very point, you may be thinking of creating a new inheritance level: Mammals <- HuntingMammals. But think: if you continue doing this for every distinctive aspect of animals, you'll have dozens of classes in a tricky and puzzled hierarchy. Besides that, we also know that some reptiles and birds also hunt, so, we'd better isolate all the hunting thing elsewhere.

As a healthy alternative to inheritance, we can define a separate Hunter class. To reuse it's contents, all we need to do is put a Hunter object as member of both Dog and Lion (a field). If we need to treat dogs and lions as hunters together (polymorphically), we can define a CanHunt interface to group them.

Check the example below:

class Hunter {
   void hunt(){
       System.out.println("i'm hunting...");
   }
}

interface CanHunt{
   Hunter getHunter();
}

class Dog extends Mammals implements CanHunt{
   ...
   Hunter hunter = new Hunter();
   
   @Override
   Hunter getHunter(){
       return hunter;
   }      
   ...
}


class Lion extends Mammals implements CanHunt{
   ...
   Hunter hunter = new Hunter();
   
   @Override
   Hunter getHunter(){
       return hunter;
   }      
   ...
}

And here we have a polymorphic sample code that ask both dog and lion to do their hunting stuff:

...
List<CanHunt> hunters = new LinkedList();
hunters.add(new Dog());
hunters.add(new Lion());

for(CanHunt h:hunters){
  h.getHunter().hunt(); //we don't know if it's a dog or a lion here...
}
...

I hope this simple example gives you some inspiration. And it can get quite complex if we keep evolving it towards a more detailed though flexbile design. For instance, Hunter class could be abstract with different implementations, since dog hunts differentlty from lions, but they share some common behaviour.

Immolation answered 29/1, 2018 at 20:51 Comment(0)
S
0

I find this question quite interesting as most of us don't have enough time to read and re-read all the classes we could inherit and that could lead us to write very inefficient code and sometimes to "reinvent the wheel" which in the end would cost us more time than if we've taken a look at other classes.

My suggestion to solve this problem is to create microservices, small mini-apps that perform special pieces of work, that way you wouldn't have to use inheritance as much as now and you'll know the exact output you're getting. Furthermore, you would be able to re-use those "microservices" in other apps.

As a side note, check the books suggested by @AntoineDubuis I found they would be very helpful.

Stringendo answered 29/1, 2018 at 19:30 Comment(2)
Not sure what microservices have anything to do with code reuse. You don't have to write mini apps in order to reuse. In most cases you can just reuse Java classes to achieve the same.Unpeopled
It's for using classes created by yourself or by others in further apps, so you don't have to write them again.Manteau

© 2022 - 2024 — McMap. All rights reserved.