Search code examples
javainheritanceenumscomposition

How to simplify java code by moving from inheritance to composition


I wrote the bottom level classes of multilevel inherited class but confused how to combine them to a single program. Someone suggested me to use composition instead of inheritance, and someone suggested to use enum to create anonymous subclass instead of complex inheritance structure. How can we use composition in place of inheritance or use enum instead of the inheritance that I used? What Should I do to simplify problem and How should it be done?

I am using Java to write a code to calculate tax. I am having base class TaxPayer. Taxpayer can have multiple incomeSource. There can be many types of TaxPayer or IncomeSource. There can be many income headings for each income source which is used to calculate taxableIncome. taxRate will be different for different type of taxPayer and taxableIncome. Final result will be taxRate applied to taxableIncome.

Base class Taxpayer is defined as

public abstract class TaxPayer {
    private List<IncomeSource> incomeSource;
    double taxRate;
    Address address;
    other attributes here;

    public Double getTaxRate(){
        return 0.25; //default tax rate
     }
}

public abstract class IncomeSource {
    private String incomeSourceName;
    private Double incomeHeading1, incomeHeading2, incomeHeading3, ...;
    private Double taxableIncome = incomeHeading1 + incomeHeading2 - incomeHeading3;
}

There can be some subclass of IncomeSource with different income headings. Similarly tax payer type can be modeled into following inheritance structure

Base Class: Taxpayer
    * IndividualPerson
        * Male, Female, OldAge
    * Business
        * Bank, ITIndustry, HydroElectricIndustry
    * TaxFree
        * SocialOrganization, ReligiousOrganization, PoliticalParty etc.

The subclasses of TaxPayer generally modifies the taxRate to be applied to taxableIncome and sometimes changes taxableIncome with some logic. For an example:

abstract class IndividualPerson extends TaxPayer{
    if (incomeSource.taxableIncome > 250000) taxRate = ratex;
    if (incomeSource.taxableIncome > 500000) taxRate = ratey;
    @override
    public getTaxRate() {
        return taxRate;
    }
}
class Female extends IndividualPerson {
    if (incomeSource.getNumberOfIncomeSource() > 1) taxRate = taxRate + rate1;
    else taxRate = taxRate - rate2
    if (address.isRural() = true) taxRate = taxRate - rate3;
    if (attributeX = true) taxRate = taxRate + rate4;
    if ("Some other attribute" = true) taxableIncome = taxableIncome - someAmount;
}

We have to check attributes of Taxpayer and IncomeSource to determine the taxRate. Sometimes, taxableIncome can be modified according to taxPayer type and other conditions.

I am confused how to combine bottom level classes together to make a useable code. Someone suggested me not to use complex multilevel inheritance, instead, use composition or enum to achieve the desired result with less complexity. How to do this? Please give answers with enough details for beginners to understand.


Solution

  • Ok, i wont program your case out, but i will give you some guidlines to what composition is, and how to solve it with enums. Your problem seems to be, that you have a TaxPayer, and the only thing that is shared, is the default tax rate.

    With composition, instead of using inheritance, you have a instance of TaxPayer in your class:

    public class TaxPayer {
        private List<IncomeSource> incomeSource;
        double taxRate;
        Address address;
        other attributes here;
    
        public Double getTaxRate(){
            return 0.25; //default tax rate
         }
    }
    
    public class Male {
       private TaxPayer taxPayer = new TaxPayer();
    
       public Double getTaxRate(){
          return taxPayer.getTaxRate();
       }
    }
    

    So instead of abstraction, you link the methods through that you want to use of TaxPayer.

    With enums the solution would probally look something like this:

    public enum TaxPayer {
      MALE,
      FEMALE {
        public Double getTaxRate(){
            return 1; //custom tax rate
        }
      };
    
      public Double getTaxRate(){
          return 0.25; //default tax rate
      }
    
    }
    

    Now you can use:

    TaxPayer.MALE.getTaxRate();
    

    in your code!

    A third option would be defining an interface and define the defaultTaxRate as a constant.

    public interface TaxPayer {
      Double getTaxRate();
    }
    
    public class Male implements TaxPayer {
      public Double getTaxRate(){
        return Constants.DEFAULT_TAX_RATE;
      }
    }
    
    public class Constants {
      public static final double DEFAULT_TAX_RATE = 0.25;
    }
    

    And now for the important bit, why would you any of these over extention.

    First off, you can only extend 1 class, so it makes your class less versitile, and harder to use. The enum and interface solve this for you (since you can still apply the Strategy Pattern to it).

    public void foo(TaxPayer anyImplementationOfTaxPayer){
       bar(anyImplementationOfTaxPayer.getTaxRate());
    }
    

    As you see, by using an interface or an enum, its far more extendible than an abstract class.

    Secondly, if you add a method to the abstract class, ALL the classes that extend from it will inherit this method, and are forced to deal with/implement behaviour of this extra method, or have a partly unusable API.