068
behavioral object-oriented

Extension Object

Extension Object — class diagram
Plate 068 class diagram

The Extension Object pattern addresses the problem of adding functionality to existing classes without modifying their core structure. It achieves this by encapsulating varying behaviors into separate extension classes, which are then passed to the client class to execute specific operations. This promotes the Single Responsibility Principle and allows for flexible and dynamic behavior modification.

This pattern is particularly useful when a class has many optional behaviors, and including them all directly would lead to a bloated and complex design. It allows you to add new functionality without altering the original class, making it more maintainable and extensible. It’s a common technique for handling variations in reporting, validation, or other auxiliary processes.

Usage

The Extension Object pattern is commonly used in scenarios where:

  • A class needs to support a variety of optional behaviors.
  • You want to avoid a large number of boolean flags or conditional statements within a class.
  • You anticipate that new behaviors will be added frequently.
  • You want to keep the core class focused on its primary responsibilities.
  • It helps decouple the primary logic from supporting concerns and makes testing easier by enabling focused unit tests on extensions.

Examples

  1. Report Generation in a Financial System: Consider a financial system where you need to generate various reports (e.g., Summary, Detailed, Audit). Instead of adding report-generation logic directly into the Transaction class, you can create separate SummaryReportExtension, DetailedReportExtension, and AuditReportExtension classes. The Transaction class can then accept an Extension object specifying which report to generate, achieving flexible reporting without code modification to the central Transaction class.

  2. Django REST Framework (DRF) Serializers: DRF utilizes a similar concept when handling serialization options. While the core Serializer class defines the basic data mapping, additional behaviors like related field expansion or nested serialization are often implemented in separate extension classes (though not explicitly named “Extension Objects”). These extensions are then applied during the serialization process, providing a powerful and modular system for building APIs. For instance, ModelSerializer extends the base Serializer to automatically provide data based on a Django model, and further customizations can be achieved through combination of different serializers and extensions.

Specimens

15 implementations
Specimen 068.01 Dart View specimen ↗

The Extension Object pattern adds new functionality to an existing class without modifying its code, using a separate “extension” class. This is achieved by holding an instance of the original class within the extension and delegating method calls to it. In Dart, this is directly supported by the extension keyword, making it a natural fit. The example extends the String class to provide a method for counting vowels. This avoids polluting the original String class with potentially less-used functionality and promotes code organization. The Dart implementation is concise and leverages the language’s built-in extension support for a clean and readable solution.

// Define the original class
class Person {
  final String name;

  Person(this.name);

  String greet() => 'Hello, my name is $name.';
}

// Define the extension object
extension PersonExtensions on Person {
  String introduce() => 'Meet ${name}, a wonderful person.';
}

// Example Usage
void main() {
  final person = Person('Alice');
  print(person.greet());
  print(person.introduce()); // Accessing the extended method
}