Extension Object
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
-
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
Transactionclass, you can create separateSummaryReportExtension,DetailedReportExtension, andAuditReportExtensionclasses. TheTransactionclass can then accept anExtensionobject specifying which report to generate, achieving flexible reporting without code modification to the centralTransactionclass. -
Django REST Framework (DRF) Serializers: DRF utilizes a similar concept when handling serialization options. While the core
Serializerclass 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,ModelSerializerextends the baseSerializerto automatically provide data based on a Django model, and further customizations can be achieved through combination of different serializers and extensions.
Specimens
15 implementationsThe 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
}
The Extension Object pattern adds functionality to an existing class without using inheritance or modifying the original class. It achieves this by defining a separate class that contains extension methods, which are methods that operate on instances of the target class. These methods are typically defined as implicit conversions, allowing them to be called as if they were members of the original class. This is particularly useful in Scala for adding domain-specific language (DSL) features or enriching core classes without altering their original definition.
// Original class
class StringExtensions(val str: String) {
def isPalindrome: Boolean = {
str.toLowerCase == str.toLowerCase.reverse
}
def countVowels: Int = {
str.toLowerCase.count(c => "aeiou".contains(c))
}
}
// Extension Object (Implicit Conversions)
object StringExtensions {
implicit def extendString(s: String): StringExtensions = new StringExtensions(s)
}
// Usage
object Main extends App {
val myString = "Racecar"
import StringExtensions._ // Import the implicit conversions
println(s"$myString is palindrome: ${myString.isPalindrome}")
println(s"$myString has ${myString.countVowels} vowels")
val anotherString = "Hello"
println(s"$anotherString is palindrome: ${anotherString.isPalindrome}")
println(s"$anotherString has ${anotherString.countVowels} vowels")
}
The Extension Object pattern allows you to add functionality to an existing object without modifying its core class. This is achieved by creating a separate “extension” object that holds the new functionality and operates on the original object. It promotes the Open/Closed Principle by allowing extension without modification.
The PHP example defines a Document class with basic properties. The DocumentFormatter extension object adds formatting capabilities (e.g., bolding) without altering the Document class itself. The formatter receives a Document instance in its constructor and exposes methods that operate on that document. This approach is idiomatic PHP as it leverages loose coupling and allows for flexible, modular code. It avoids inheritance, which can become rigid, and favors composition.
<?php
class Document
{
private string $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
class DocumentFormatter
{
private Document $document;
public function __construct(Document $document)
{
$this->document = $document;
}
public function bold(): string
{
return '<b>' . $this->document->getContent() . '</b>';
}
public function italic(): string
{
return '<i>' . $this->document->getContent() . '</i>';
}
}
// Example Usage
$doc = new Document("This is some text.");
$formatter = new DocumentFormatter($doc);
echo $formatter->bold() . "\n";
echo $formatter->italic() . "\n";
?>
The Extension Object pattern allows you to add new functionality to existing classes without modifying their original source code, using techniques like monkey patching or reopening classes. This is particularly useful when dealing with third-party libraries or core Ruby classes where direct modification isn’t feasible or desirable. The example extends the String class with a to_camel_case method. Ruby’s open class system makes this pattern very natural and idiomatic. Reopening a class is a common practice, and defining methods within the reopened class directly adds functionality as if it were originally part of the class.
# frozen_string_literal: true
class String
def to_camel_case
return self if empty?
words = self.split('_')
camel_cased = words.shift.downcase
words.each do |word|
camel_cased += word.capitalize
end
camel_cased
end
end
# Example Usage:
string1 = "some_property_name"
string2 = "another_example"
string3 = "alreadyCamelCase"
puts string1.to_camel_case # Output: somePropertyName
puts string2.to_camel_case # Output: anotherExample
puts string3.to_camel_case # Output: alreadyCamelCase
The Extension Object pattern allows you to add functionality to an existing class without subclassing or using categories (as in Objective-C). It achieves this by creating a separate object that encapsulates the new behavior and holds an instance of the original class. The original class remains untouched, adhering to the Open/Closed Principle. This is particularly useful when you need to add functionality that doesn’t logically belong in the original class or when you can’t modify the original class directly.
In Swift, this is naturally implemented using extensions and composition. The extension defines the new functionality, and the original object is held as a property within the extension object. The extension object then delegates calls to the original object, augmenting its behavior. This approach is idiomatic Swift because extensions are a core language feature for adding functionality to existing types in a clean and type-safe manner.
// Original object
class Product {
var name: String
init(name: String) {
self.name = name
}
func describe() -> String {
return "This is a \(name)."
}
}
// Extension object
extension Product {
var discount: Double {
get { return 0.0 }
}
func applyDiscount(price: Double) -> Double {
return price * (1 - discount)
}
}
// Specialized extension object
class DiscountedProduct {
private let product: Product
let discountRate: Double
init(product: Product, discountRate: Double) {
self.product = product
self.discountRate = discountRate
}
func describe() -> String {
return product.describe() + " It's on sale!"
}
func discountedPrice(originalPrice: Double) -> Double {
return originalPrice * (1 - discountRate)
}
}
// Usage
let product = Product(name: "Awesome Widget")
print(product.describe())
let discountedProduct = DiscountedProduct(product: product, discountRate: 0.2)
print(discountedProduct.describe())
print("Original price: 100, Discounted price: \(discountedProduct.discountedPrice(originalPrice: 100))")
The Extension Object pattern allows you to add new behavior to an existing class without modifying its source code, using a separate “extension” object. This is achieved by having the extension object hold an instance of the original class and delegate method calls to it, potentially adding pre- or post-processing logic. It’s a form of delegation and promotes the Open/Closed Principle.
In Kotlin, this is elegantly implemented using delegation via the by keyword. The extension object implements the same interface as the original class, then delegates all calls to the wrapped instance. This provides a clean and concise way to extend functionality without inheritance or modification of the original class. The example demonstrates extending the String class to provide a method for counting vowels, without altering the String class itself.
// Original class (no modification allowed)
class StringProcessor(private val str: String) {
fun process(): String {
return str.uppercase()
}
}
// Extension Object
class VowelCounterExtension(private val stringProcessor: StringProcessor) : StringProcessor by stringProcessor {
fun countVowels(): Int {
val vowels = "AEIOUaeiou".toRegex()
return stringProcessor.process().count { vowels.contains(it) }
}
}
// Usage
fun main() {
val original = StringProcessor("Hello World")
val extended = VowelCounterExtension(original)
println(original.process()) // Output: HELLO WORLD
println(extended.countVowels()) // Output: 3
println(extended.process()) //Delegated call to original class
}
The Extension Object pattern allows adding new functionality to existing objects without modifying their core code. This is achieved by creating a separate “extension” object that holds the new methods and data, and then associating it with the original object. In Rust, this is naturally implemented using structs and traits. The original object implements a trait that the extension object also implements, allowing the extension to be treated as part of the original object through trait objects or generics. This avoids inheritance and promotes composition.
// Define the core object (e.g., a Point)
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
fn distance_from_origin(&self) -> f64 {
((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()
}
}
// Define the extension trait
trait PointExtension {
fn translate(&mut self, dx: i32, dy: i32);
fn scale(&mut self, factor: f64);
}
// Implement the extension
struct MutablePointExtension {
point: &mut Point,
}
impl PointExtension for MutablePointExtension {
fn translate(&mut self, dx: i32, dy: i32) {
self.point.x += dx;
self.point.y += dy;
}
fn scale(&mut self, factor: f64) {
self.point.x = (self.point.x as f64 * factor) as i32;
self.point.y = (self.point.y as f64 * factor) as i32;
}
}
// Function to apply the extension
fn extend_point(point: &mut Point) -> MutablePointExtension {
MutablePointExtension { point }
}
fn main() {
let mut p = Point::new(1, 2);
println!("Original point: {:?}", p);
let mut extended_p = extend_point(&mut p);
extended_p.translate(3, 4);
println!("Translated point: {:?}", p);
extended_p.scale(2.0);
println!("Scaled point: {:?}", p);
}
The Extension Object pattern allows you to add functionality to an existing class without modifying its code directly. This is achieved by creating a separate “extension” class that holds the new functionality and collaborates with the original class. The original class is often referred to as the “host” object.
The Go implementation uses composition. The Core struct represents the original object, and the Extension struct adds functionality by having a Core as a field. Methods on the Extension delegate to the Core when necessary, and then add their own behavior. This approach is idiomatic Go because it favors composition over inheritance, promoting flexibility and avoiding the rigid coupling inherent in inheritance-based extension. Interfaces can be used to further decouple the core and extension.
package main
import "fmt"
// Core represents the original object with basic functionality.
type Core struct {
data string
}
// NewCore creates a new Core object.
func NewCore(d string) *Core {
return &Core{data: d}
}
// CoreMethod is a method of the Core object.
func (c *Core) CoreMethod() string {
return "Core says: " + c.data
}
// Extension adds functionality to the Core object.
type Extension struct {
*Core // Composition: Extension *has a* Core
}
// NewExtension creates a new Extension object.
func NewExtension(c *Core) *Extension {
return &Extension{Core: c}
}
// ExtensionMethod adds new functionality.
func (e *Extension) ExtensionMethod() string {
return "Extension adds: " + e.Core.CoreMethod() + " - Enhanced!"
}
func main() {
core := NewCore("Hello")
extension := NewExtension(core)
fmt.Println(extension.ExtensionMethod())
fmt.Println(extension.CoreMethod()) // Can still access Core methods directly
}
The Extension Object pattern allows adding new functionality to an existing object without modifying its core code. This is achieved by creating a separate “extension” object that holds the new functionality and operates on the original object. The original object typically exposes a minimal interface for the extension to interact with. In C, this is often implemented using function pointers or a structure containing function pointers, allowing the extension to override or augment the original object’s behavior. This approach promotes modularity and avoids the fragility of directly modifying existing code.
#include <stdio.h>
#include <stdlib.h>
// Original Object
typedef struct {
int value;
void (*operation)(struct OriginalObject *, int);
} OriginalObject;
void defaultOperation(OriginalObject *self, int operand) {
printf("Default Operation: Value = %d, Operand = %d\n", self->value, operand);
self->value += operand;
}
// Extension Interface
typedef struct {
void (*extendedOperation)(OriginalObject *self, int);
} Extension;
// Concrete Extension
void customOperation(OriginalObject *self, int operand) {
printf("Custom Operation: Value = %d, Operand = %d\n", self->value, operand);
self->value *= operand;
}
// Function to apply the extension
void applyExtension(OriginalObject *obj, Extension *ext) {
if (ext && ext->extendedOperation) {
obj->operation = ext->extendedOperation;
}
}
int main() {
OriginalObject obj = {10, defaultOperation};
Extension ext = {customOperation};
printf("Initial Value: %d\n", obj.value);
// Use default operation
obj.operation(&obj, 5);
printf("Value after default operation: %d\n", obj.value);
// Apply the extension
applyExtension(&obj, &ext);
// Use extended operation
obj.operation(&obj, 3);
printf("Value after custom operation: %d\n", obj.value);
return 0;
}
The Extension Object pattern allows you to extend the functionality of an existing class without modifying its code directly. This is achieved by creating a separate “extension” class that holds the new functionality and collaborates with the original class. The original class remains untouched, adhering to the Open/Closed Principle.
This C++ example demonstrates this by adding a ‘format’ capability to a simple ‘Document’ class using an ‘HtmlFormatter’ extension. The Document class doesn’t know about HTML formatting; it simply accepts an Formatter object and delegates the formatting task. This keeps the Document class focused on its core responsibility and allows for easy addition of other formatters (e.g., PlainTextFormatter) without altering Document. The use of polymorphism via the base Formatter class is a standard C++ approach for achieving extensibility.
#include <iostream>
#include <string>
// Original class - Document
class Document {
public:
Document(const std::string& content) : content_(content) {}
std::string getContent() const { return content_; }
std::string format(class Formatter& formatter) const {
return formatter.format(content_);
}
private:
std::string content_;
};
// Extension Interface - Formatter
class Formatter {
public:
virtual std::string format(const std::string& content) = 0;
virtual ~Formatter() = default;
};
// Extension Class - HtmlFormatter
class HtmlFormatter : public Formatter {
public:
std::string format(const std::string& content) override {
return "<html><body><p>" + content + "</p></body></html>";
}
};
// Client Code
int main() {
Document doc("This is the document content.");
HtmlFormatter htmlFormatter;
std::string formattedDocument = doc.format(htmlFormatter);
std::cout << formattedDocument << std::endl;
return 0;
}
The Extension Object pattern allows adding functionality to existing classes without modifying their source code, using a separate “extension” class that holds the new methods. This is particularly useful when you can’t or don’t want to alter the original class, perhaps because it’s from a third-party library or part of a core system. In C#, this is naturally implemented using Extension Methods. The example extends the string class with a method to count the number of vowels. This fits C# style as extension methods are a first-class language feature, providing a clean and type-safe way to add functionality.
// ExtensionObjectExample.cs
using System;
using System.Linq;
public static class StringExtensions
{
public static int CountVowels(this string str)
{
if (string.IsNullOrEmpty(str))
{
return 0;
}
return str.ToLower().Count(c => "aeiou".Contains(c));
}
}
public class Example
{
public static void Main(string[] args)
{
string myString = "Hello World";
int vowelCount = myString.CountVowels();
Console.WriteLine($"The string \"{myString}\" contains {vowelCount} vowels.");
string emptyString = "";
Console.WriteLine($"The string \"{emptyString}\" contains {emptyString.CountVowels()} vowels.");
}
}
The Extension Object pattern allows adding new functionality to an existing object without modifying its core code. This is achieved by creating a separate “extension” object that holds the new methods, and then composing the original object with the extension. This promotes the Open/Closed Principle – open for extension, closed for modification.
The TypeScript implementation uses object composition. The extend function takes the base object and one or more extension objects, returning a new object that inherits properties from all inputs. This is idiomatic TypeScript as it leverages the language’s flexible object model and avoids inheritance-based modification. The use of object literals and the spread operator (...) for composition are standard TypeScript practices for creating new objects with combined properties.
// Base object
const baseObject = {
name: "Base",
describe(): string {
return `This is ${this.name}`;
},
};
// Extension object 1
const extension1 = {
version: "1.0",
getDetails(): string {
return `Version: ${this.version}`;
},
};
// Extension object 2
const extension2 = {
author: "John Doe",
authorDetails(): string {
return `Author: ${this.author}`;
},
};
// Function to extend the object
function extend(base: any, ...extensions: any[]): any {
return Object.assign({}, base, ...extensions);
}
// Extend the base object
const extendedObject = extend(baseObject, extension1, extension2);
// Use the extended object
console.log(extendedObject.describe()); // Output: This is Base
console.log(extendedObject.getDetails()); // Output: Version: 1.0
console.log(extendedObject.authorDetails()); // Output: Author: John Doe
console.log(extendedObject); // Output: { name: 'Base', describe: [Function: describe], version: '1.0', getDetails: [Function: getDetails], author: 'John Doe', authorDetails: [Function: authorDetails] }
The Extension Object pattern allows you to add new functionality to objects without modifying their original code. This is achieved by creating a separate object that contains the new methods, and then using techniques like Object.assign or spreading to merge the extension object’s properties into the target object. This promotes modularity and avoids potential conflicts when extending core objects.
The JavaScript code below demonstrates this by extending a Calculator object with a power function via an AdvancedCalculator extension. This approach is idiomatic JavaScript because it leverages the dynamic nature of objects and utilizes modern object manipulation techniques like Object.assign for clean and concise extension. It avoids inheritance, which can be less flexible for adding optional features.
// Core Calculator object
const Calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => a / b,
};
// Extension object for advanced features
const AdvancedCalculator = {
power: (base, exponent) => Math.pow(base, exponent),
};
// Extend the Calculator object
const calculatorWithPower = Object.assign({}, Calculator, AdvancedCalculator);
// Usage
console.log(calculatorWithPower.add(5, 3)); // Output: 8
console.log(calculatorWithPower.power(2, 3)); // Output: 8
// Another example - extending a single instance
const myCalculator = {
value: 10
};
const addValueExtension = {
addValue: (amount) => {
myCalculator.value += amount;
},
getValue: () => myCalculator.value
};
Object.assign(myCalculator, addValueExtension);
myCalculator.addValue(5);
console.log(myCalculator.getValue()); // Output: 15
The Extension Object pattern allows adding new functionality to an existing class without modifying its code. This is achieved by creating a separate “extension” class that holds the new methods and data, and then composing the extension into the original object. This promotes the Open/Closed Principle – software entities should be open for extension, but closed for modification.
The Python implementation uses composition. The ExtendedObject class takes an instance of the original OriginalObject in its constructor and delegates calls to it. New functionality is added as methods within ExtendedObject. This approach is idiomatic Python as it leverages the language’s flexible object model and avoids inheritance-based extension which can lead to brittle designs. It also allows for multiple extensions to be applied to the same original object.
# original_object.py
class OriginalObject:
def __init__(self, data):
self.data = data
def existing_method(self):
return f"Original data: {self.data}"
# extension_object.py
class ExtendedObject:
def __init__(self, original_object):
self._original_object = original_object
def existing_method(self):
return self._original_object.existing_method()
def new_method(self):
return f"Extended data: {self.data_extension()}"
def data_extension(self):
return self._original_object.data + " - extended"
# main.py
from original_object import OriginalObject
from extension_object import ExtendedObject
original = OriginalObject("Initial Value")
extended = ExtendedObject(original)
print(extended.existing_method())
print(extended.new_method())
The Extension Object pattern allows adding functionality to an object without modifying its core class. It achieves this by creating a separate “extension” object that holds the additional behavior and is associated with the original object. This is particularly useful when you have a class with many responsibilities and want to keep it focused, or when you need to add functionality that isn’t universally applicable.
The Java implementation uses composition. The Original class holds a reference to an Extension object. Methods requiring the extended functionality delegate to the Extension instance. This avoids bloating the Original class and promotes the Open/Closed Principle. The use of interfaces (Extension) allows for multiple, independent extensions to be created and swapped easily, fitting Java’s preference for interfaces and loose coupling.
// Define the interface for extensions
interface Extension {
String extend(String data);
}
// The original class with core functionality
class Original {
private final String data;
private Extension extension;
public Original(String data) {
this.data = data;
this.extension = new NullExtension(); // Default: no extension
}
public String getData() {
return data;
}
public String processData() {
return "Original: " + data;
}
public void setExtension(Extension extension) {
this.extension = extension;
}
public String extendedProcessData() {
return extension.extend(processData());
}
}
// A default extension that does nothing
class NullExtension implements Extension {
@Override
public String extend(String data) {
return data;
}
}
// A concrete extension that adds a suffix
class SuffixExtension implements Extension {
private final String suffix;
public SuffixExtension(String suffix) {
this.suffix = suffix;
}
@Override
public String extend(String data) {
return data + " - Extended";
}
}
// Example Usage
public class ExtensionObjectExample {
public static void main(String[] args) {
Original original = new Original("Hello");
System.out.println(original.processData()); // Output: Original: Hello
System.out.println(original.extendedProcessData()); // Output: Original: Hello
SuffixExtension suffixExtension = new SuffixExtension("!");
original.setExtension(suffixExtension);
System.out.println(original.extendedProcessData()); // Output: Original: Hello - Extended
}
}