Groovy Design Patterns – Singleton, Strategy, Observer with 10 Examples

Learn Groovy design patterns with 10+ tested examples. Master @Singleton, Strategy with closures, Observer, Builder, Factory, and Decorator with traits.

“Design patterns are not about being clever. They are about being clear. Groovy makes them so clear you almost forget they are patterns.”

Gang of Four, Design Patterns

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate to Advanced | Reading Time: 25 minutes

In Java, implementing a Gang of Four pattern means creating an interface, writing an abstract class, implementing three concrete classes, and wiring up a factory. Groovy design patterns collapse much of that ceremony into a few lines – the Singleton becomes a single @Singleton annotation, the Strategy becomes a closure, and the Decorator becomes a trait.

This is not about cutting corners. Groovy does not eliminate the need for design patterns — it makes them more natural and less ceremonial. The Singleton pattern becomes a single annotation. The Strategy pattern becomes a closure. The Observer pattern becomes event-driven callbacks. And the Decorator pattern becomes a trait.

In this tutorial, we will implement the most important Groovy design patterns with 10+ tested examples. You will see how Groovy’s language features map naturally to classic patterns, and when you should still use the traditional Java approach. Let us get started.

Why Patterns Look Different in Groovy

According to the official Groovy design patterns documentation, Groovy simplifies patterns through several language features that Java lacks (or gained only recently):

Java MechanismGroovy AlternativePatterns Simplified
Interfaces + classesClosuresStrategy, Command, Template Method
Abstract classesTraitsDecorator, Mixin, Strategy
Boilerplate constructors@Singleton annotationSingleton
Listener interfacesClosure callbacksObserver
Static factory methodsNamed constructors, mapsFactory, Builder
Proxy classesMetaprogrammingProxy, Decorator

The result is that many patterns that require 50+ lines in Java take 5-10 lines in Groovy. The pattern is still there — the intent and structure are identical — but the implementation is dramatically simpler. For a deeper understanding of closures that power many patterns, see our Groovy Closures tutorial.

Creational Patterns

Creational patterns deal with object creation mechanisms. Groovy simplifies these dramatically with built-in annotations and dynamic construction.

Behavioral Patterns

Behavioral patterns define how objects communicate and distribute responsibility. Closures make most behavioral patterns trivial in Groovy.

Structural Patterns

Structural patterns compose classes and objects into larger structures. Groovy’s traits and metaprogramming make composition effortless.

10 Practical Pattern Examples

Let us implement each pattern with real, tested code. Each example shows the Groovy-idiomatic way alongside a comparison to how you would do it in Java.

Example 1: Singleton with @Singleton Annotation

What we’re doing: Creating a thread-safe singleton using Groovy’s built-in @Singleton annotation — one line instead of twenty.

Example 1: @Singleton Annotation

// Groovy way: one annotation does everything
@Singleton
class AppConfig {
    String appName = 'MyApp'
    String version = '1.0.0'
    Map settings = [debug: false, maxRetries: 3]

    String toString() {
        "${appName} v${version} ${settings}"
    }
}

// Access the singleton instance
def config1 = AppConfig.instance
def config2 = AppConfig.instance

println "config1: ${config1}"
println "config2: ${config2}"
println "Same instance: ${config1.is(config2)}"

// Modify through either reference
config1.settings.debug = true
println "config2 sees change: ${config2.settings.debug}"

// Trying to construct throws an error
try {
    new AppConfig()
} catch (RuntimeException e) {
    println "Cannot instantiate: ${e.message}"
}

Output

config1: MyApp v1.0.0 [debug:false, maxRetries:3]
config2: MyApp v1.0.0 [debug:false, maxRetries:3]
Same instance: true
config2 sees change: true
Cannot instantiate: Can't instantiate singleton AppConfig. Use AppConfig.instance

What happened here: The @Singleton annotation generates a private constructor, a static instance field, and the thread-safe initialization logic. In Java, you would need to write a private constructor, a static field, a double-checked locking getInstance() method, and mark the field as volatile. Groovy does it all with one annotation.

Example 2: Lazy Singleton and Singleton with Properties

What we’re doing: Creating a lazy-initialized singleton and a singleton that accepts constructor properties.

Example 2: Lazy Singleton Variants

// Lazy singleton -- instance created on first access
@Singleton(lazy=true)
class DatabasePool {
    int maxConnections = 10
    List connections = []

    String toString() { "DatabasePool[max=${maxConnections}, active=${connections.size()}]" }
}

println "Before access: singleton not yet created"
def pool = DatabasePool.instance
println "After access: ${pool}"

// Singleton with constructor properties
@Singleton(property='instance', strict=false)
class Logger {
    String prefix = 'LOG'

    void log(String message) {
        println "[${prefix}] ${message}"
    }
}

Logger.instance.log("Application started")
Logger.instance.log("Processing request")
Logger.instance.prefix = 'DEBUG'
Logger.instance.log("Variable x = 42")

Output

Before access: singleton not yet created
After access: DatabasePool[max=10, active=0]
[LOG] Application started
[LOG] Processing request
[DEBUG] Variable x = 42

What happened here: The lazy=true option delays instance creation until the first call to .instance. The strict=false option allows a default constructor alongside the singleton access, useful when you want to initialize properties. These options give you fine-grained control over singleton behavior.

Example 3: Strategy Pattern with Closures

What we’re doing: Implementing the Strategy pattern using closures instead of interfaces and concrete classes.

Example 3: Strategy Pattern with Closures

// In Java: interface SortStrategy { List sort(List items); }
// In Groovy: just use a closure

class Sorter {
    Closure strategy

    List sort(List items) {
        strategy(items)
    }
}

// Define strategies as closures
def alphabetical = { items -> items.sort { it.toString() } }
def byLength = { items -> items.sort { it.toString().size() } }
def reverse = { items -> items.sort { a, b -> b.toString() <=> a.toString() } }
def numeric = { items -> items.sort { it as int } }

def words = ['banana', 'apple', 'cherry', 'date', 'elderberry']
def sorter = new Sorter()

// Swap strategies at runtime
sorter.strategy = alphabetical
println "Alphabetical: ${sorter.sort(words.collect())}"

sorter.strategy = byLength
println "By length:    ${sorter.sort(words.collect())}"

sorter.strategy = reverse
println "Reverse:      ${sorter.sort(words.collect())}"

// Numbers with numeric strategy
def numbers = ['42', '7', '100', '3', '25']
sorter.strategy = numeric
println "Numeric:      ${sorter.sort(numbers.collect())}"

Output

Alphabetical: [apple, banana, cherry, date, elderberry]
By length:    [date, apple, banana, cherry, elderberry]
Reverse:      [elderberry, date, cherry, banana, apple]
Numeric:      [3, 7, 25, 42, 100]

What happened here: In Java, the Strategy pattern requires an interface, multiple implementing classes, and a context class that holds a reference to the interface. In Groovy, a closure is the strategy. You assign different closures to the strategy field, and the behavior changes at runtime. Same pattern, dramatically less code.

Example 4: Observer Pattern with Event Callbacks

What we’re doing: Implementing the Observer pattern with closure-based event listeners.

Example 4: Observer Pattern

class EventEmitter {
    Map<String, List<Closure>> listeners = [:].withDefault { [] }

    void on(String event, Closure handler) {
        listeners[event] << handler
    }

    void off(String event, Closure handler) {
        listeners[event].remove(handler)
    }

    void emit(String event, Map data = [:]) {
        listeners[event].each { handler ->
            handler(data)
        }
    }
}

// Create an emitter
def emitter = new EventEmitter()

// Register observers
emitter.on('userLogin') { data ->
    println "  [Logger] User ${data.username} logged in at ${data.time}"
}

emitter.on('userLogin') { data ->
    println "  [Analytics] Login event recorded for ${data.username}"
}

emitter.on('userLogout') { data ->
    println "  [Logger] User ${data.username} logged out"
}

emitter.on('error') { data ->
    println "  [Alert] ERROR: ${data.message}"
}

// Emit events
println "=== User Login ==="
emitter.emit('userLogin', [username: 'alice', time: '10:30 AM'])

println "\n=== User Logout ==="
emitter.emit('userLogout', [username: 'alice'])

println "\n=== Error ==="
emitter.emit('error', [message: 'Database connection failed'])

println "\nRegistered events: ${emitter.listeners.keySet()}"
println "Login observers: ${emitter.listeners['userLogin'].size()}"

Output

=== User Login ===
  [Logger] User alice logged in at 10:30 AM
  [Analytics] Login event recorded for alice

=== User Logout ===
  [Logger] User alice logged out

=== Error ===
  [Alert] ERROR: Database connection failed

Registered events: [userLogin, userLogout, error]
Login observers: 2

What happened here: In Java, the Observer pattern requires an Observer interface, a Subject class, and concrete observer implementations. In Groovy, closures serve as lightweight observers. You register them with on(), and the emitter calls them when an event fires. This pattern is used everywhere in JavaScript (Node.js EventEmitter), and Groovy closures make it just as natural on the JVM.

Example 5: Builder Pattern – Fluent API

What we’re doing: Creating a fluent builder using Groovy’s dynamic properties and method chaining.

Example 5: Builder Pattern

class EmailBuilder {
    String from = ''
    String to = ''
    String subject = ''
    String body = ''
    List<String> cc = []
    List<String> attachments = []
    boolean html = false

    EmailBuilder from(String from) { this.from = from; this }
    EmailBuilder to(String to) { this.to = to; this }
    EmailBuilder subject(String subject) { this.subject = subject; this }
    EmailBuilder body(String body) { this.body = body; this }
    EmailBuilder cc(String... addresses) { this.cc.addAll(addresses); this }
    EmailBuilder attach(String file) { this.attachments << file; this }
    EmailBuilder asHtml() { this.html = true; this }

    Map build() {
        assert from, 'From is required'
        assert to, 'To is required'
        assert subject, 'Subject is required'
        [from: from, to: to, subject: subject, body: body,
         cc: cc, attachments: attachments, html: html]
    }

    String toString() {
        "Email(from=${from}, to=${to}, subject='${subject}', " +
        "cc=${cc}, attachments=${attachments}, html=${html})"
    }
}

// Fluent builder chain
def email = new EmailBuilder()
    .from('alice@example.com')
    .to('bob@example.com')
    .subject('Meeting Tomorrow')
    .body('Hi Bob, let us meet at 10 AM.')
    .cc('charlie@example.com', 'diana@example.com')
    .attach('agenda.pdf')
    .asHtml()
    .build()

println "Built email:"
email.each { key, value ->
    println "  ${key}: ${value}"
}

// Groovy shortcut: map constructor
def quickEmail = new EmailBuilder(
    from: 'admin@example.com',
    to: 'team@example.com',
    subject: 'System Update',
    body: 'Maintenance at midnight.'
)
println "\nQuick email: ${quickEmail}"

Output

Built email:
  from: alice@example.com
  to: bob@example.com
  subject: Meeting Tomorrow
  body: Hi Bob, let us meet at 10 AM.
  cc: [charlie@example.com, diana@example.com]
  attachments: [agenda.pdf]
  html: true

Quick email: Email(from=admin@example.com, to=team@example.com, subject='System Update', cc=[], attachments=[], html=false)

What happened here: The Builder pattern in Groovy works like Java fluent builders, but with a bonus: Groovy’s map-based constructors let you skip the builder entirely for simple cases. Use the fluent builder when you need validation, complex defaults, or a readable API. Use map constructors when you just need to set a few properties quickly. For more on builders, see our Groovy DSL and Builder tutorial.

Example 6: Factory Pattern

What we’re doing: Implementing the Factory pattern using Groovy maps and closures for dynamic object creation.

Example 6: Factory Pattern

// Define shapes as simple classes
class Circle {
    double radius
    double area() { Math.PI * radius * radius }
    String toString() { "Circle(radius=${radius}, area=${String.format('%.2f', area())})" }
}

class Rectangle {
    double width, height
    double area() { width * height }
    String toString() { "Rectangle(${width}x${height}, area=${area()})" }
}

class Triangle {
    double base, height
    double area() { 0.5 * base * height }
    String toString() { "Triangle(base=${base}, height=${height}, area=${area()})" }
}

// Factory using a map of closures
class ShapeFactory {
    static Map<String, Closure> creators = [
        circle:    { params -> new Circle(params) },
        rectangle: { params -> new Rectangle(params) },
        triangle:  { params -> new Triangle(params) }
    ]

    static create(String type, Map params) {
        def creator = creators[type.toLowerCase()]
        if (!creator) throw new IllegalArgumentException("Unknown shape: ${type}")
        creator(params)
    }

    static registerShape(String type, Closure creator) {
        creators[type.toLowerCase()] = creator
    }
}

// Create shapes through the factory
def shapes = [
    ShapeFactory.create('circle', [radius: 5.0]),
    ShapeFactory.create('rectangle', [width: 4.0, height: 6.0]),
    ShapeFactory.create('triangle', [base: 3.0, height: 8.0])
]

println "=== Created Shapes ==="
shapes.each { println "  ${it}" }

// Register a new shape type at runtime
ShapeFactory.registerShape('square') { params ->
    new Rectangle(width: params.side, height: params.side)
}

def square = ShapeFactory.create('square', [side: 5.0])
println "\nDynamic shape: ${square}"
println "Available types: ${ShapeFactory.creators.keySet()}"

Output

=== Created Shapes ===
  Circle(radius=5.0, area=78.54)
  Rectangle(4.0x6.0, area=24.0)
  Triangle(base=3.0, height=8.0, area=12.0)

Dynamic shape: Rectangle(5.0x5.0, area=25.0)
Available types: [circle, rectangle, triangle, square]

What happened here: The Factory pattern in Groovy uses a map of closures instead of a switch statement or a registry of concrete classes. The real power is registerShape() — you can add new types at runtime without modifying the factory class. This open/closed approach is much more flexible than the traditional Java factory, and it is only possible because Groovy closures are first-class objects.

Example 7: Decorator Pattern with Traits

What we’re doing: Using Groovy traits to compose behaviors — the Groovy-idiomatic way to implement Decorator.

Example 7: Decorator with Traits

// Base interface
interface Beverage {
    String getDescription()
    double getCost()
}

// Base class
class Coffee implements Beverage {
    String getDescription() { 'Coffee' }
    double getCost() { 2.00 }
}

// Decorator traits
trait MilkDecorator implements Beverage {
    String getDescription() { "${super.getDescription()} + Milk" }
    double getCost() { super.getCost() + 0.50 }
}

trait SugarDecorator implements Beverage {
    String getDescription() { "${super.getDescription()} + Sugar" }
    double getCost() { super.getCost() + 0.25 }
}

trait WhipCreamDecorator implements Beverage {
    String getDescription() { "${super.getDescription()} + Whip Cream" }
    double getCost() { super.getCost() + 0.75 }
}

trait VanillaDecorator implements Beverage {
    String getDescription() { "${super.getDescription()} + Vanilla" }
    double getCost() { super.getCost() + 0.60 }
}

// Compose decorators at runtime
def plainCoffee = new Coffee()
println "${plainCoffee.description}: \$${plainCoffee.cost}"

def latte = new Coffee().withTraits(MilkDecorator)
println "${latte.description}: \$${latte.cost}"

def fancy = new Coffee().withTraits(MilkDecorator, SugarDecorator, WhipCreamDecorator)
println "${fancy.description}: \$${fancy.cost}"

def premium = new Coffee().withTraits(MilkDecorator, VanillaDecorator, WhipCreamDecorator)
println "${premium.description}: \$${premium.cost}"

// Static composition
class VanillaLatte extends Coffee implements MilkDecorator, VanillaDecorator {}
def staticLatte = new VanillaLatte()
println "\nStatic composition: ${staticLatte.description}: \$${staticLatte.cost}"

Output

Coffee: $2.0
Coffee + Milk: $2.5
Coffee + Milk + Sugar + Whip Cream: $3.5
Coffee + Milk + Vanilla + Whip Cream: $3.85

Static composition: Coffee + Milk + Vanilla: $3.1

What happened here: Groovy traits are the perfect tool for the Decorator pattern. Each trait adds behavior (description and cost) on top of the base class. The withTraits() method applies decorators dynamically at runtime. You can also use static composition with implements. In Java, you would need wrapper classes for each decorator — in Groovy, traits are mixable, stackable, and reusable.

Example 8: Template Method Pattern

What we’re doing: Implementing the Template Method pattern using closures to define the variable parts of an algorithm.

Example 8: Template Method Pattern

// Template method with closures -- no abstract class needed
class DataProcessor {
    Closure readData
    Closure transformData
    Closure writeData

    void process() {
        println "Step 1: Reading data..."
        def data = readData()

        println "Step 2: Transforming data..."
        def transformed = transformData(data)

        println "Step 3: Writing data..."
        writeData(transformed)

        println "Processing complete!\n"
    }
}

// CSV processing pipeline
println "=== CSV Pipeline ==="
def csvProcessor = new DataProcessor(
    readData: {
        // Simulate reading CSV
        [['Alice', 30], ['Bob', 25], ['Charlie', 35]]
    },
    transformData: { data ->
        data.collect { row -> [name: row[0].toUpperCase(), age: row[1]] }
    },
    writeData: { data ->
        data.each { println "  ${it.name}: ${it.age}" }
    }
)
csvProcessor.process()

// JSON processing pipeline (different implementation, same template)
println "=== JSON Pipeline ==="
def jsonProcessor = new DataProcessor(
    readData: {
        [[id: 1, value: 100], [id: 2, value: 200], [id: 3, value: 300]]
    },
    transformData: { data ->
        def total = data.sum { it.value }
        data.collect { it + [percentage: "${(it.value / total * 100).round(1)}%"] }
    },
    writeData: { data ->
        data.each { println "  ID ${it.id}: ${it.value} (${it.percentage})" }
    }
)
jsonProcessor.process()

Output

=== CSV Pipeline ===
Step 1: Reading data...
Step 2: Transforming data...
Step 3: Writing data...
  ALICE: 30
  BOB: 25
  CHARLIE: 35
Processing complete!

=== JSON Pipeline ===
Step 1: Reading data...
Step 2: Transforming data...
Step 3: Writing data...
  ID 1: 100 (16.7%)
  ID 2: 200 (33.3%)
  ID 3: 300 (50.0%)
Processing complete!

What happened here: The Template Method pattern defines the skeleton of an algorithm and lets subclasses fill in the details. In Java, you would use an abstract class with abstract methods. In Groovy, you inject closures for the variable steps. Same pattern, but far more flexible — you can swap behaviors without creating new classes.

Example 9: Command Pattern with Closures

What we’re doing: Implementing the Command pattern for undo/redo functionality using a stack of closures.

Example 9: Command Pattern

class TextEditor {
    StringBuilder content = new StringBuilder()
    Stack<Closure> undoStack = new Stack<>()

    void execute(Closure doAction, Closure undoAction) {
        doAction()
        undoStack.push(undoAction)
    }

    void undo() {
        if (!undoStack.empty()) {
            undoStack.pop().call()
        } else {
            println "Nothing to undo"
        }
    }

    void insertText(String text) {
        execute(
            { content.append(text) },                            // do
            { content.delete(content.length() - text.length(), content.length()) }  // undo
        )
    }

    void deleteText(int count) {
        def deleted = content.substring(content.length() - count)
        execute(
            { content.delete(content.length() - count, content.length()) },  // do
            { content.append(deleted) }                                       // undo
        )
    }

    String toString() { content.toString() }
}

def editor = new TextEditor()

editor.insertText("Hello")
println "After insert 'Hello': '${editor}'"

editor.insertText(" World")
println "After insert ' World': '${editor}'"

editor.insertText("!")
println "After insert '!': '${editor}'"

editor.undo()
println "After undo: '${editor}'"

editor.undo()
println "After undo: '${editor}'"

editor.insertText(" Groovy")
println "After insert ' Groovy': '${editor}'"

editor.deleteText(7)
println "After delete 7: '${editor}'"

editor.undo()
println "After undo delete: '${editor}'"

Output

After insert 'Hello': 'Hello'
After insert ' World': 'Hello World'
After insert '!': 'Hello World!'
After undo: 'Hello World'
After undo: 'Hello'
After insert ' Groovy': 'Hello Groovy'
After delete 7: 'Hello'
After undo delete: 'Hello Groovy'

What happened here: In Java, the Command pattern requires a Command interface, concrete command classes like InsertCommand and DeleteCommand, and an invoker. In Groovy, each command is a pair of closures (do and undo) pushed onto a stack. The pattern is identical, but you avoid the class explosion that comes with Java’s approach.

Example 10: Chain of Responsibility

What we’re doing: Building a request processing chain where each handler decides whether to process or pass along the request.

Example 10: Chain of Responsibility

// Each handler is a closure that takes (request, next)
class Pipeline {
    List<Closure> handlers = []

    Pipeline use(Closure handler) {
        handlers << handler
        this
    }

    def process(Map request) {
        def chain = handlers.reverse().inject({ req -> req }) { next, handler ->
            { req -> handler(req, next) }
        }
        chain(request)
    }
}

// Build a request processing pipeline
def pipeline = new Pipeline()
    .use { request, next ->
        // Authentication handler
        println "  [Auth] Checking authentication..."
        if (!request.token) {
            return [status: 401, message: 'Unauthorized']
        }
        request.user = 'alice'
        next(request)
    }
    .use { request, next ->
        // Logging handler
        println "  [Log] ${request.method} ${request.path} by ${request.user}"
        def result = next(request)
        println "  [Log] Response: ${result.status}"
        result
    }
    .use { request, next ->
        // Rate limiting handler
        println "  [Rate] Checking rate limit..."
        if (request.requestCount > 100) {
            return [status: 429, message: 'Too many requests']
        }
        next(request)
    }
    .use { request, next ->
        // Final handler -- process the request
        println "  [Handler] Processing ${request.method} ${request.path}"
        [status: 200, message: 'OK', data: "Response for ${request.path}"]
    }

// Process valid request
println "=== Valid Request ==="
def result1 = pipeline.process([method: 'GET', path: '/api/users', token: 'abc123', requestCount: 5])
println "Result: ${result1}\n"

// Process unauthorized request
println "=== No Token ==="
def result2 = pipeline.process([method: 'GET', path: '/api/secret', requestCount: 1])
println "Result: ${result2}"

Output

=== Valid Request ===
  [Auth] Checking authentication...
  [Log] GET /api/users by alice
  [Rate] Checking rate limit...
  [Handler] Processing GET /api/users
  [Log] Response: 200
Result: [status:200, message:OK, data:Response for /api/users]

=== No Token ===
  [Auth] Checking authentication...
Result: [status:401, message:Unauthorized]

What happened here: The Chain of Responsibility pattern chains handlers together so each can decide to process or delegate. In Groovy, each handler is a closure that takes the request and a next closure. The inject() method composes them into a single pipeline. This is the same pattern used in Express.js middleware and Groovy’s own Grails interceptors.

Bonus Example 11: State Machine with Maps and Closures

What we’re doing: Implementing a state machine using maps of transitions — a clean Groovy-idiomatic pattern.

Bonus: State Machine Pattern

class OrderStateMachine {
    String currentState = 'pending'

    Map transitions = [
        pending:    [confirm: 'confirmed',  cancel: 'cancelled'],
        confirmed:  [ship: 'shipped',       cancel: 'cancelled'],
        shipped:    [deliver: 'delivered',   returnItem: 'returned'],
        delivered:  [returnItem: 'returned'],
        cancelled:  [:],
        returned:   [:]
    ]

    Map actions = [
        confirm:    { println "    -> Order confirmed. Processing payment..." },
        cancel:     { println "    -> Order cancelled. Refund initiated." },
        ship:       { println "    -> Order shipped. Tracking number generated." },
        deliver:    { println "    -> Order delivered. Customer notified." },
        returnItem: { println "    -> Return initiated. Generating label..." }
    ]

    boolean trigger(String event) {
        def available = transitions[currentState]
        if (!available.containsKey(event)) {
            println "    Cannot '${event}' from state '${currentState}'"
            return false
        }
        actions[event]?.call()
        currentState = available[event]
        println "    State: ${currentState}"
        return true
    }
}

def order = new OrderStateMachine()
println "Order state: ${order.currentState}"

println "\n1. Confirming order..."
order.trigger('confirm')

println "\n2. Trying to confirm again..."
order.trigger('confirm')

println "\n3. Shipping order..."
order.trigger('ship')

println "\n4. Delivering order..."
order.trigger('deliver')

println "\n5. Returning order..."
order.trigger('returnItem')

println "\nFinal state: ${order.currentState}"

Output

Order state: pending

1. Confirming order...
    -> Order confirmed. Processing payment...
    State: confirmed

2. Trying to confirm again...
    Cannot 'confirm' from state 'confirmed'

3. Shipping order...
    -> Order shipped. Tracking number generated.
    State: shipped

4. Delivering order...
    -> Order delivered. Customer notified.
    State: delivered

5. Returning order...
    -> Return initiated. Generating label...
    State: returned

Final state: returned

What happened here: A state machine is a common real-world pattern. In Java, you would typically use an enum with transition methods or a library like Spring State Machine. In Groovy, a map of maps defines all valid transitions, and closures handle side effects. Adding new states or transitions is just adding entries to the maps — no new classes needed.

Groovy-Idiomatic Patterns

Beyond the classic GoF patterns, Groovy has its own idiomatic patterns that use the language’s unique features:

Null Object Pattern with Safe Navigation

Groovy Null Object Pattern

// Java way: explicit null checks everywhere
// if (user != null && user.getAddress() != null && user.getAddress().getCity() != null)

// Groovy way: safe navigation operator
def user = [name: 'Alice', address: [city: 'New York', zip: '10001']]
def noAddress = [name: 'Bob', address: null]
def noUser = null

println "Alice's city: ${user?.address?.city}"
println "Bob's city: ${noAddress?.address?.city ?: 'Unknown'}"
println "Null user: ${noUser?.name ?: 'No user'}"

// Elvis operator for defaults
def config = [timeout: null, retries: 3]
def timeout = config.timeout ?: 30
def retries = config.retries ?: 5
println "\nTimeout: ${timeout} (defaulted)"
println "Retries: ${retries} (from config)"

Output

Alice's city: New York
Bob's city: Unknown
Null user: No user

Timeout: 30 (defaulted)
Retries: 3 (from config)

Pimp My Library Pattern with Extension Methods

Extending Existing Classes

// Add methods to existing classes via metaprogramming
String.metaClass.isPalindrome = {
    def cleaned = delegate.toLowerCase().replaceAll('[^a-z0-9]', '')
    cleaned == cleaned.reverse()
}

Integer.metaClass.factorial = {
    (1..delegate).inject(1) { acc, n -> acc * n }
}

List.metaClass.median = {
    def sorted = delegate.sort()
    def mid = sorted.size().intdiv(2)
    sorted.size() % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0
}

// Use the extended methods
println "'racecar'.isPalindrome(): ${'racecar'.isPalindrome()}"
println "'hello'.isPalindrome(): ${'hello'.isPalindrome()}"
println "'A man a plan a canal Panama'.isPalindrome(): ${'A man a plan a canal Panama'.isPalindrome()}"
println "5.factorial(): ${5.factorial()}"
println "7.factorial(): ${7.factorial()}"
println "[3,1,4,1,5,9,2,6].median(): ${[3,1,4,1,5,9,2,6].median()}"

Output

'racecar'.isPalindrome(): true
'hello'.isPalindrome(): false
'A man a plan a canal Panama'.isPalindrome(): true
5.factorial(): 120
7.factorial(): 5040
[3,1,4,1,5,9,2,6].median(): 3.5

These Groovy-specific patterns — safe navigation, the Elvis operator, and metaprogramming extensions — are patterns you will use daily. They are not in the GoF book, but they solve the same kinds of design problems in a more dynamic way.

Best Practices

DO:

  • Use @Singleton for thread-safe singletons instead of manual double-checked locking
  • Prefer closures over interfaces for Strategy, Command, and Observer when the interface has only one method
  • Use traits for Decorator and Mixin patterns — they compose better than wrapper classes
  • use map-based constructors for simple Builder patterns
  • Use the safe navigation operator (?.) instead of Null Object pattern in most cases

DON’T:

  • Port Java design pattern code verbatim into Groovy — refactor to use Groovy idioms
  • Overuse metaprogramming extensions — they can make code harder to debug and understand
  • Create singleton classes for things that do not need to be singletons — prefer dependency injection
  • Ignore the GoF intent — Groovy changes the implementation, not the pattern’s purpose
  • Forget that traits are resolved at compile time while withTraits() is resolved at runtime

Conclusion

We covered the most important Groovy design patterns — from @Singleton and Factory to Strategy with closures, Observer with events, Decorator with traits, Template Method, Command, Chain of Responsibility, and State Machine. In every case, Groovy’s language features — closures, traits, maps, and metaprogramming — reduce the pattern to its essential logic without the ceremony Java demands.

The key insight is this: Groovy does not eliminate design patterns. It eliminates the boilerplate that obscures them. The patterns are still there — the intent, the relationships, the responsibilities — but they are expressed in fewer, clearer lines of code.

For related topics, check out our Groovy DSL and Builder tutorial for deeper builder patterns, and our Groovy Grape tutorial to learn how to pull in libraries for your pattern implementations.

Summary

  • @Singleton replaces manual singleton boilerplate with one annotation
  • Closures replace single-method interfaces for Strategy, Command, Observer, and Template Method
  • Traits replace wrapper classes for Decorator, enabling composable behavior mixing
  • Maps of closures make Factory and State Machine patterns dynamic and extensible
  • Groovy does not eliminate patterns — it removes the ceremony around them

If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.

Up next: Groovy DSL and Builder Pattern

Frequently Asked Questions

How does Groovy’s @Singleton annotation work?

The @Singleton annotation automatically generates a private constructor, a static instance property, and thread-safe lazy initialization. You access the singleton via ClassName.instance. Options include lazy=true for deferred initialization and strict=false to allow property setting. It replaces the 20+ lines of Java boilerplate needed for a thread-safe singleton.

How do you implement the Strategy pattern in Groovy?

In Groovy, the Strategy pattern is implemented by assigning closures to a field instead of creating interface implementations. For example, a Sorter class with a Closure strategy field can swap sorting algorithms at runtime by simply assigning a new closure. This eliminates the need for a SortStrategy interface and concrete classes like AlphabeticalSort.

What is the difference between traits and abstract classes for design patterns in Groovy?

Traits support multiple inheritance (a class can implement many traits), while abstract classes support only single inheritance. This makes traits ideal for the Decorator pattern, where you want to stack multiple behaviors. Traits can also be applied at runtime using withTraits(), while abstract classes are strictly compile-time. Use traits for composition and abstract classes for strict type hierarchies.

When should I use Java-style design patterns in Groovy?

Use traditional Java patterns when you need strict type safety (interfaces for API contracts), when working with Java libraries that expect specific interfaces, or when your team includes Java developers who need to read the code. Also use Java-style patterns when IDE tooling like refactoring and code navigation is important — closures and metaprogramming can make these tools less effective.

Can Groovy traits replace the Decorator pattern entirely?

Yes, for most use cases. Groovy traits can add behavior to existing classes either at compile time (with implements) or at runtime (with withTraits()). Each trait can call super to chain behavior, just like traditional decorators. The main limitation is that traits cannot wrap instances of existing objects — they create new proxy objects, which may not work if you need to decorate a specific instance.

Previous in Series: Groovy Grape – Dependency Management for Scripts

Next in Series: Groovy DSL and Builder Pattern

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *