Refactoring: Object Orientation Abuser

Pemrograman


The common determinant of smell in Object Orientation Abuser is the partial application of object-oriented concepts. For instance, the extensive and intricate use of the switch operator or the abundant and sequential use of the if operator, which ideally should be replaced with the application of object-oriented concepts. The use of temporary fields can also indicate that such fields should be declared within a method rather than within the scope of a class.

The types of bad code smell in the Object Orientation Abuser category are:

  1. Switch statement

  2. Temporary Field

  3. Refused Bequest

  4. Alternative classes with different interfaces

 

1. OO Abuser: Switch Statement

Switch statements can be categorized as object orientation abusers when their implementation becomes overly complex. Often, a switch statement is broken down into several other components within a program.

Image

When a new condition is added, the switch code needs to be modified again. Several approaches can be used to address the issue of switch statements:

 

1.1. Switch Statement: Move Method

Move Method can be utilized when a method is used more frequently in other classes than in its own class. Therefore, it needs to be moved to the class where it belongs. Here is an illustration of moving aMethod() from Class1 to Class2.

Image

 

1.2. Switch Statement: Replace Conditional with Polymorphism

When there's a conditional structure that indicates varying actions depending on the type of object or properties. Here's an example of a Bird class that has getSpeed(), where the method contains conditions based on the type of bird determining the speed calculation. Such cases can be refactored into polymorphism, by applying override methods for getSpeed() in each subclass of the superclass Bird. Here is the detailed code example of the scenario described above:

Image

 

1.3. Switch Statement: Replace Parameter with Explicit Method

Replace Parameter with Explicit Method is a refactoring technique where a method that has multiple parts executed depending on the value of a parameter is replaced by separating each part into separate methods. Each new method replaces the logic previously controlled by parameters in the original method. This technique is done to improve code readability and reduce the complexity of methods depending on parameters.

Image

 

1.4. Switch Statement: Introduce Null Object

Introducing a Null Object. In some methods that return values, the returned value might be null. This can be replaced by creating a null object that will be returned instead of the null value itself. Here's an example of a null object from the Customer class named NullCustomer, declared to represent the null value of an object.

Image

 

 

2. OO Abuser: Temporary Field

Temporary fields are only used to hold a value under certain conditions, so outside of the mentioned conditions, the value of the field is empty. The use of temporary fields can also indicate that such fields should be declared within a method rather than within the scope of a class.

Image

Often, temporary fields are created for use in algorithms that require many inputs. Consequently, many parameters are created in that method, where some declared fields are only used as temporary value holders. Thus, when the algorithm finishes running, these fields become useless. Refactoring techniques that can be used to fix the condition of temporary fields are:

 

2.1. Extract Class

Problem:

Temporary fields are only used under certain conditions and only for specific algorithms.

Solution:

Move the temporary field and its logic into a separate class.

Example Before Refactoring:

class Order {
   private String tempField;
   
   void processOrder(String value) {
       this.tempField = value;
       // Algorithm logic using tempField
       System.out.println("Processing order with tempField: " + tempField);
       // After completion, tempField is no longer used
       this.tempField = null;
  }
}

Example After Refactoring:

class OrderProcessor {
   private String value;
   
   OrderProcessor(String value) {
       this.value = value;
  }

   void process() {
       // Algorithm logic using value
       System.out.println("Processing order with value: " + value);
  }
}

class Order {
   void processOrder(String value) {
       OrderProcessor processor = new OrderProcessor(value);
       processor.process();
  }
}

 

2.2. Replacing Method with Object Method

Problem:

The method has too many parameters or temporary fields.

Solution:

Move the method logic to an object method that will handle all parameters and temporary fields.

Example Before Refactoring:

class Order {
   private String tempField1;
   private String tempField2;
   
   void processOrder(String value1, String value2) {
       this.tempField1 = value1;
       this.tempField2 = value2;
       // Algorithm logic using tempField1 and tempField2
       System.out.println("Processing order with tempField1: " + tempField1 + " and tempField2: " + tempField2);
       // After completion, tempField is no longer used
       this.tempField1 = null;
       this.tempField2 = null;
  }
}

Example After Refactoring:

class OrderProcessor {
   private String value1;
   private String value2;
   
   OrderProcessor(String value1, String value2) {
       this.value1 = value1;
       this.value2 = value2;
  }

   void process() {
       // Algorithm logic using value1 and value2
       System.out.println("Processing order with value1: " + value1 + " and value2: " + value2);
  }
}

class Order {
   void processOrder(String value1, String value2) {
       OrderProcessor processor = new OrderProcessor(value1, value2);
       processor.process();
  }
}

 

2.3. Introducing Null Object

Problem:

Temporary fields are often checked for null before being used.

Solution:

Use a null object as a replacement for null to avoid null condition checks.

Example Before Refactoring:

class Order {
   private Customer customer;
   
   void processOrder() {
       if (customer == null) {
           System.out.println("Processing order for guest customer");
      } else {
           System.out.println("Processing order for customer: " + customer.getName());
      }
  }
}

Example After Refactoring:

class Customer {
   String getName() {
       return "Regular Customer";
  }
}

class NullCustomer extends Customer {
   @Override
   String getName() {
       return "Guest Customer";
  }
}

class Order {
   private Customer customer;
   
   Order(Customer customer) {
       this.customer = (customer != null) ? customer : new NullCustomer();
  }
   
   void processOrder() {
       System.out.println("Processing order for customer: " + customer.getName());
  }
}

// Usage
Order orderWithCustomer = new Order(new Customer());
orderWithCustomer.processOrder(); // Output: Processing order for customer: Regular Customer

Order orderWithoutCustomer = new Order(null);
orderWithoutCustomer.processOrder(); // Output: Processing order for customer: Guest Customer

 

 

3. OO Abuser: Refused Bequest

Refused Bequest occurs when the use of inheritance is motivated by the intention to reuse code in the superclass, where the characteristics of the subclass and superclass are significantly different.

Image

Inheritance is a concept of derivation that connects the superclass and subclass through generalization and specification characteristics. Where the more general class becomes the superclass and the more specific class becomes the subclass. Some things that can be done to address this violation include replacing inheritance with delegation as shown in the example below:

Image

 

 

4. OO Abuser: Alternative Classes with Different Interfaces

As illustrated below, the condition that occurs in this code smell is when two or more classes have methods that perform the same function but with different names.

Image

This same function can be extracted into an interface that can then be implemented by the classes that need it. Here are some alternatives for extracting the required interface:

 

4.1. Rename Method

Renaming methods is a refactoring technique where methods that perform the same function but have different names are renamed to be uniform across different classes. This is done to improve code consistency and readability.

When two or more classes have methods that perform the same function but with different names, it can lead to confusion and difficulty in maintaining the code. By renaming these methods to be uniform, we can make the code more consistent and easier to understand. Once the methods have the same name, we can extract the common function into an interface that is then implemented by those classes.

Example Before Refactoring:

class ClassA {
   void fetchData() {
       // Implementation
  }
}

class ClassB {
   void retrieveData() {
       // Implementation
  }
}

Example After Refactoring:

interface DataFetcher {
   void fetchData();
}

class ClassA implements DataFetcher {
   @Override
   public void fetchData() {
       // Implementation
  }
}

class ClassB implements DataFetcher {
   @Override
   public void fetchData() {
       // Implementation
  }
}

 

4.2. Adding Parameters

Adding parameters is a refactoring technique where additional parameters are added to a method so that one method can handle several variations of functions that were previously divided into multiple methods with different names.

In some cases, methods with similar functions can be combined into one method by adding additional parameters. This way, we can reduce code duplication and simplify the method structure. After adding parameters, this method can be extracted into an interface that can be implemented by related classes.

Example Before Refactoring:

class Printer {
   void printText(String text) {
       // Implementation
  }

   void printImage(Image image) {
       // Implementation
  }
}

Example After Refactoring:

interface Printer {
   void print(Object data);
}

class TextPrinter implements Printer {
   @Override
   public void print(Object data) {
       String text = (String) data;
       // Implementation
  }
}

class ImagePrinter implements Printer {
   @Override
   public void print(Object data) {
       Image image = (Image) data;
       // Implementation
  }
}

 

4.3. Parameterized Method

Parameterized method is a refactoring technique where a method is made more flexible by using parameters to control the behavior or output of the method.

With parameterized methods, we can reduce the number of methods that have similar functions but with different names or slightly different implementations. This is done by adding parameters that determine how the method should behave. After the method has been parameterized, it can be extracted into an interface that is then implemented by relevant classes.

Example Before Refactoring:

class ReportGenerator {
   void generatePDFReport(Data data) {
       // Implementation
  }

   void generateExcelReport(Data data) {
       // Implementation
  }
}

Example After Refactoring:

interface ReportGenerator {
   void generateReport(Data data, String format);
}

class PDFReportGenerator implements ReportGenerator {
   @Override
   public void generateReport(Data data, String format) {
       if (format.equals("PDF")) {
           // Implementation for PDF
      }
  }
}

class ExcelReportGenerator implements ReportGenerator {
   @Override
   public void generateReport(Data data, String format) {
       if (format.equals("Excel")) {
           // Implementation for Excel
      }
  }
}

 

Conclusion

  • Object Orientation Abuser is the partial implementation of object-oriented concepts.

  • Types of code smells in the Object Orientation Abuser category include switch statement, temporary field, refused bequest, and alternative class with different interfaces.

 

References

Share this post

Author
Dikhi Martin

Dikhi MartinSoftware Engineer