Program slicing with example in Java
What is Program Slicing
Program slicing is essentially extracting portions of the code into separate constructs. These constructs can be mere functions or classes making the code more granular. It’s a general concept that can be applied in monoliths as well as microservices.
Advantages of Program slicing
- Modular and maintainable code
- It enables separation of concerns
- Makes the code easier to extend with the use of abstraction
- Enables reuse of code and reduces duplication
- Flexible and Encapsulated
- You can even enable configurations at runtime. For example, setting a log level at runtime.
- Hides internal details
Disadvantages
- Program flow might become harder to understand as code structure is non-linear
- Improper separation can make the code tightly coupled and thereby making brittle.
Linear code flow that doesn’t utilize slicing
Let’s look at a pseudocode example which can be used to calculate taxes, we have a TaxCalculator which has a linear code structure and knows about all the implementation details. Suppose another microservice or part of the application requires a similar tax calculator with different tax rates for another country, TaxCalculator cannot be used as-is.
class TaxCalculator {
double price
double tax
ProductType productType
public double calculateTax(ProductType productType, double price) {
if(type == GOLD) {
//apply gold tax rate
}
}
}
Program Slicing example in Java
We will slice down the portions of TaxCalculator,
- First, we’ll make the tax calculation abstract - TaxCalculationStrategy interface will have a
calculateTax
method which will be implemented by classes (GoldTaxCalculator and PetrolTaxCalculator). - Second we will create a simple factory which will return the appropriate tax calculator class instance. Note: Simple Factory is just used for simplicity, its not a GoF pattern; the factory pattern is a better choice for production implementations.
- Then the TaxCalculator will expose a calculateTax method which will fetch an instance from factory and then call calculateTax(using the GoldTaxCalculator and PetrolTaxCalculator implementations of the tax rates).
- Finally the Main class or the runner will use the TaxCalculator and pass in the input details.
package org.example.programslicing;
public enum ProductType {
GOLD("gold"),
PETROL("petrol");
private String value;
ProductType(String value) {
this.value = value;
}
}
package org.example.programslicing;
public interface TaxCalculationStrategy {
double calculateTax(double price);
}
public class GoldTaxCalculator implements TaxCalculationStrategy{
@Override
public double calculateTax(double price) {
return price * 0.10;
}
}
public class PetrolTaxCalculator implements TaxCalculationStrategy{
@Override
public double calculateTax(double price) {
return price * 0.05;
}
}
public class SimpleTaxCalculatorFactory {
public static TaxCalculationStrategy getTaxCalculator(ProductType product) throws Exception {
if(product == ProductType.GOLD) {
return new GoldTaxCalculator();
} else if(product == ProductType.PETROL) {
return new PetrolTaxCalculator();
} else {
throw new Exception("invalid type");
}
}
}
public class TaxCalculator {
public double calculateTax(ProductType productType, double price) throws Exception {
return SimpleTaxCalculatorFactory.getTaxCalculator(productType).calculateTax(price);
}
}
public class TaxCalculatorRunner {
public static void main(String[] args) throws Exception {
TaxCalculator taxCalculator = new TaxCalculator();
double goldTax = taxCalculator.calculateTax(ProductType.GOLD, 25000);
System.out.println("gold tax: " + goldTax); // 2500.0
double petrolTax = taxCalculator.calculateTax(ProductType.PETROL, 100);
System.out.println("petrol tax: " + petrolTax); // 5.0
}
}