Object Seams Using Inheritance

Introduction

Let's keep going! In our previous lessons, we explored functional seams, focusing on using functions as parameters and feature flags to enhance code flexibility and testability. This time, we will explore object seams using inheritance, a powerful technique in object-oriented programming that allows us to modify and extend the behavior of existing code without altering its original structure. This lesson will guide us through the process of leveraging inheritance to create seams, enabling us to introduce new functionalities and improve testability in our codebase.

Understanding Inheritance in Object-Oriented Programming

Inheritance is a fundamental concept in object-oriented programming that allows a class to inherit properties and behaviors from another class. This mechanism enables the creation of a hierarchy of classes, promoting code reuse and extension. By using inheritance, we can define a base class with common functionality and extend it through derived classes to introduce specific behaviors. This approach not only reduces code duplication but also enhances the maintainability and scalability of the codebase.

Challenges in Established Codebase

Working with an established codebase often presents challenges such as rigidity, fragility, and difficulty in testing. These issues can make it hard to introduce new features or refactor existing code without risking unintended side effects. Existing systems may lack the flexibility needed to adapt to changing requirements, leading to increased maintenance costs and technical debt.

Leveraging Inheritance for Object Seams

Inheritance can be used to create object seams, which are strategic points in the code where behavior can be modified without altering the original implementation. By defining a base class with an overridable method, we can create subclasses that override this method to introduce new functionality.

This approach allows us to substitute behavior at runtime, enabling testing and prototyping without affecting the existing system. For example, consider the following code snippet:

import java.time.LocalDateTime

open class OrderProcessor:
  def processOrder(order: Order): (Boolean, Order) =
    val totalAmount = totalCalculation(order.items)
    (true, order.copy(
      processedAt = Some(LocalDateTime.now),
      orderTotal = totalAmount
    ))

  protected def totalCalculation(items: List[OrderItem]): BigDecimal =
    items.map(item => item.price * item.quantity).sum

class DiscountOrderProcessor extends OrderProcessor:
  override protected def totalCalculation(items: List[OrderItem]): BigDecimal =
    super.totalCalculation(items) * 0.9 // 10% discount

In this example, OrderProcessor is the base class with a method totalCalculation that can be overridden. The DiscountOrderProcessor class extends OrderProcessor and overrides totalCalculation with adjusted logic to apply a discount. The seam is established by marking the totalCalculation method as protected and overridable.

This design choice allows extension without exposing it as part of the public API, limiting overrides to subclasses only, which is safer and more maintainable than public overrides. This demonstrates how inheritance can be used to introduce new behavior without modifying the original class.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal