DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • On-Call That Doesn’t Suck: A Guide for Data Engineers
  • Inheritance in PHP: A Simple Guide With Examples
  • From Engineer to Leader: Scaling Impact Beyond Code
  • How to Grow Fast From an Intern to Senior Software Engineer

Trending

  • Cloud Security and Privacy: Best Practices to Mitigate the Risks
  • Building Reliable LLM-Powered Microservices With Kubernetes on AWS
  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Strategies for Securing E-Commerce Applications
  1. DZone
  2. Culture and Methodologies
  3. Career Development
  4. Code Smell Series: Parallel Inheritance Hierarchies

Code Smell Series: Parallel Inheritance Hierarchies

If you want to keep your code smelling fresh, beware parallel inheritance hierarchies and the problems they can cause when making additions to your code.

By 
Shamik Mitra user avatar
Shamik Mitra
·
Updated Sep. 26, 16 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
22.1K Views

Join the DZone community and get the full member experience.

Join For Free

Code smells are similar in concept to development-level anti-patterns. Sometimes, in our code, we unintentionally introduce code smells and make our design fragile.

Definition of a Code Smell

Code smells, also known as a bad smell, in computer programming refer to any symptom in the source code of a program that possibly indicates a deeper problem.

Or, as Martin Fowler puts it, "a code smell is a surface indication that usually corresponds to a deeper problem in the system."

Code smells create a lot of problems when introducing new features or maintaining the codebase. Often, developers have to write repeatable code, breaking encapsulation, breaking abstraction, etc.

So, always refactor code smells while developing.

In this article, we discuss the “Parallel Inheritance Hierarchies” code smell.

Parallel Inheritance Hierarchies occur when an inheritance tree depends on another inheritance tree by composition, and they maintain a special relationship where one subclass of a dependent inheritance must depend on one a particular subclass of another Inheritance.

Think about engineers — just engineers in general. Computer engineers work on computers and deliver projects, whereas civil engineer work on structures. From a design perspective, there are two parallel hierarchies:

  • Engineers

  • Milestones

The different engineers have different milestones, and each engineer has a specified milestone (special relation).

The problem is that every time you add a new engineer in the Engineer inheritance, you have to introduce a new Milestone in Milestone hierarchy.

Causes of the Parallel Inheritance Hierarchies Smell

  • Failing to understand the responsibility, often due to misunderstandings (breaking the single responsibility principle)
  • Overenthusiasm to break each function down as a separate interface.
  • Failure to introduce proper design patterns.
  • Lots of duplicate code.
  • The wrong relationship sets (client-side).
  • Unmaintainable code base.

Refactor Strategy

We can do it via the “Move Method” and “Move Field” techniques.

Now, take an example where parallel inheritance hierarchies are present. We will implement the Engineer and set the Milestone for him.

package com.example.codesmell.parallelinheritence;

public interface Engineer {

    String getType();
    void setType(String type);
    int getSalary();
    void setSalary(int salary);
    MileStone getMileStone();
    void setMileStone(MileStone mileStone);
}

package com.example.codesmell.parallelinheritence;

public interface MileStone {
    public String work();
    public String target();
}

package com.example.codesmell.parallelinheritence;

public class ComputerEngineer implements Engineer {
    private String type;
    private int salary;
    private MileStone mileStone;
    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public void setMileStone(MileStone mileStone) {
        this.mileStone = mileStone;
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return mileStone;
    }

    @Override
    public String toString() {
        return "ComputerEngineer [type=" + type + ", salary=" + salary
        + ", mileStone=" + mileStone + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class ComputerMileStone implements MileStone {

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String toString() {
        return "ComputerMileStone [work()=" + work() + ", target()=" + target()
        + "]";
    }
}


package com.example.codesmell.parallelinheritence;

public class CivilEngineer implements Engineer {

    private String type;
    private int salary;
    private MileStone mileStone;

    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public void setMileStone(MileStone mileStone) {
        this.mileStone = mileStone;
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return mileStone;
    }

    @Override
    public String toString() {
        return "CivilEngineer [type=" + type + ", salary=" + salary
        + ", mileStone=" + mileStone + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class CivilMileStone implements MileStone {

    @Override
    public String work() {
        // TODO Auto-generated method stub
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        // TODO Auto-generated method stub
        return "Has to be completed in 2 years";
    }

    @Override
    public String toString() {
        return "CivilMileStone [work()=" + work() + ", target()=" + target()
        + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {
    public static void main(String[] args) {

        Engineer comp = new ComputerEngineer();
        comp.setType("Computer Engineer");
        comp.setSalary(50000);
        comp.setMileStone(new ComputerMileStone());

        Engineer civil = new CivilEngineer();
        civil.setType("Civil Engineer");
        civil.setSalary(60000);

        civil.setMileStone(new CivilMileStone());

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}


Output : 

ComputerEngineer [type=Computer Engineer, salary=50000, mileStone=ComputerMileStone [work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD]]
********************
CivilEngineer [type=Civil Engineer, salary=60000, mileStone=CivilMileStone [work()=Create  Twin Towers, target()=Has to be completed in 2 years]]


We create two interfaces — Engineer and Milestone — and create subclasses for them, but the thing to notice is that every engineer has his own special milestone, so as we expose the setMileStone method to the client, it may be possible for the client to set the wrong Milestone for an Engineer.

Another thing to note is that if we want to add a new Engineer, we also need to add a new Milestone for him/her. That's a very difficult problem to fix. In fact, if we try, we might break the SRP (Single Responsibility Principle).

There are three possible way we can deal with it.

Solution 1

Keep the parallel hierarchies open and get used to it.

Pros

  • Better way to maintain SRP.

  • The code will be flexible.

Cons

  • To add a new feature, we have to create two classes every time.

  • Hierarchies are coupled. Changes in one might necessitate changes in the other.

  • Harder to maintain.

Solution 2

Make them into partial hierarchies so we can open provision them for parallel hierarchies.

Pros

  • Only maintain one hierarchy.

  • When you are not sure about responsibility, try to adopt it.

  • Provide flexibility.

Cons

  • May break SRP

Technique

Make a concrete class and implement both interfaces. The client got the instance of this class via the static factory method.

Let's see the solution:

package com.example.codesmell.parallelinheritence;

public class PartialComputerEngineer implements Engineer,MileStone {

    private String type;
    private int salary; 

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
}

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return this;
    }

    @Override
    public void setMileStone(MileStone mileStone) {
        throw new UnsupportedOperationException("Not Supported");
}

    @Override
    public String toString() {
        return "PartialComputerEngineer [type=" + type + ", salary=" + salary
        + ", work()=" + work() + ", target()=" + target()
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
         + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class PartialCivilEngineer  implements Engineer,MileStone {
    private String type;
    private int salary; 

    @Override
    public String work() {
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        return "Has to be completed in 2 years";
    }

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
}

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return this;
    }

    @Override
    public void setMileStone(MileStone mileStone) {
        throw new UnsupportedOperationException("Not Supported");
    }

    @Override
    public String toString() {
        return "PartialCivilEngineer [type=" + type + ", salary=" + salary
        + ", work()=" + work() + ", target()=" + target()
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class EngineerFactory {

    public static Engineer getEngineer(Class clazz) throws InstantiationException, IllegalAccessException
    {
        return (Engineer) clazz.newInstance();
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {

        Engineer comp = EngineerFactory.getEngineer(PartialComputerEngineer.class);
        comp.setType("Computer Engineer");
        comp.setSalary(50000);

        Engineer civil = EngineerFactory.getEngineer(PartialCivilEngineer.class);
        civil.setType("Computer Engineer");
        civil.setSalary(50000);

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}


Output :
PartialComputerEngineer [type=Computer Engineer, salary=50000, work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD, getType()=Computer Engineer, getSalary()=50000]
********************
PartialCivilEngineer [type=Computer Engineer, salary=50000, work()=Create  Twin Towers, target()=Has to be completed in 2 years, getType()=Computer Engineer, getSalary()=50000]


Solution 3

Collapse a hierarchy.

Pros

  • Only maintain One hierarchy

  • Easy to maintain

Cons

  • Breaks SRP fairly often.

Technique

Make a common interface and move methods from another interface.

Let's see the solution:

package com.example.codesmell.parallelinheritence;

public interface EngineerMileStone {

    String getType();
    void setType(String type);
    int getSalary();
    void setSalary(int salary);
    public String work();
    public String target();
}

package com.example.codesmell.parallelinheritence;

public class RefactorComputerEngineer implements EngineerMileStone {

    private String type;
    private int salary; 

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String toString() {
        return "RefactorComputerEngineer [type=" + type + ", salary=" + salary
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + ", work()=" + work() + ", target()=" + target() + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class ReFactorCivilEngineer implements EngineerMileStone {

    private String type;
    private int salary; 

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public String work() {
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        return "Has to be completed in 2 years";
    }

    @Override
    public String toString() {
        return "ReFactorCivilEngineer [type=" + type + ", salary=" + salary
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + ", work()=" + work() + ", target()=" + target() + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {

        EngineerMileStone comp = new RefactorComputerEngineer();
        comp.setType("Computer Engineer");
        comp.setSalary(50000);

        EngineerMileStone civil = new ReFactorCivilEngineer();
        civil.setType("Civil Engineer");
        civil.setSalary(60000);

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}

Output

RefactorComputerEngineer [type=Computer Engineer, salary=50000, getType()=Computer Engineer, getSalary()=50000, work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD]
********************
ReFactorCivilEngineer [type=Civil Engineer, salary=60000, getType()=Civil Engineer, getSalary()=60000, work()=Create  Twin Towers, target()=Has to be completed in 2 years]


Inheritance (object-oriented programming) Code smell Engineer Milestone (project management)

Opinions expressed by DZone contributors are their own.

Related

  • On-Call That Doesn’t Suck: A Guide for Data Engineers
  • Inheritance in PHP: A Simple Guide With Examples
  • From Engineer to Leader: Scaling Impact Beyond Code
  • How to Grow Fast From an Intern to Senior Software Engineer

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!