Skip to content

Strategy java

Example Java Strategy Design Pattern Source Code

Strategy Interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package edu.redwoods.design.strategy;

/**
 * UML: <<interface>> Strategy
 *
 * The family of interchangeable algorithms. A LoadStage holds one of these and
 * delegates its real work to it; the stage never knows which concrete strategy
 * it is currently holding.
 *
 * The UML's "execute(Object data): Object" is shown here with real types.
 * Using the narrowest type that works is what lets the compiler guarantee that
 * every strategy can be handed every batch.
 */
public interface LoadStrategy {

    /**
     * UML: +execute(Object data): Object
     *
     * @param data the batch handed over by the Context
     * @return a report describing what the algorithm did
     * @throws DuplicateKeyException if this algorithm cannot tolerate a key
     *         that already exists in the target
     */
    LoadReport execute(LoadBatch data);
}

Value Object: LoadBatch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package edu.redwoods.design.strategy;

import java.util.List;

/**
 * The payload the Context passes to the Strategy. Every strategy sees the same
 * shape, so the Context can swap algorithms without reshaping its data.
 *
 * Column 0 of each row is treated as the natural key for this demo.
 */
public record LoadBatch(String target, List<String[]> rows) {

    public LoadBatch {
        rows = List.copyOf(rows);
    }

    public int size() {
        return rows.size();
    }
}

Value Object: LoadReport

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package edu.redwoods.design.strategy;

/**
 * The value a LoadStrategy hands back to the Context. The Context treats every
 * strategy's report the same way, so it never has to ask which algorithm
 * produced it.
 */
public record LoadReport(String strategy, int inserted, int updated) {

    @Override
    public String toString() {
        return String.format("%-10s -> inserted=%d, updated=%d", strategy, inserted, updated);
    }
}

Exception Thrown by the Family

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package edu.redwoods.design.strategy;

/**
 * Unchecked, so a strategy can signal "this algorithm cannot handle this
 * batch" without every caller in the pipeline declaring it.
 */
public class DuplicateKeyException extends RuntimeException {

    public DuplicateKeyException(String message) {
        super(message);
    }
}

Concrete Strategy: Bulk Insert

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package edu.redwoods.design.strategy;

import java.util.Set;

/**
 * UML: ConcreteStrategyA
 *
 * The fast path. It assumes every key in the batch is new and writes the whole
 * batch in one statement. If the target already holds any of the keys, the
 * entire statement fails - which is exactly the trade-off that makes this
 * algorithm worth swapping out at runtime.
 */
public class BulkInsertStrategy implements LoadStrategy {

    private final Set<String> targetKeys;

    public BulkInsertStrategy(Set<String> targetKeys) {
        this.targetKeys = targetKeys;
    }

    @Override
    public LoadReport execute(LoadBatch data) {
        System.out.println("[BulkInsert] sending " + data.size()
                + " rows to " + data.target() + " in a single INSERT");

        // Pre-flight check: one duplicate fails the entire batch.
        for (String[] row : data.rows()) {
            if (targetKeys.contains(row[0])) {
                throw new DuplicateKeyException(
                        "BulkInsert aborted: key '" + row[0]
                                + "' already exists in " + data.target());
            }
        }

        // "Write" the batch.
        for (String[] row : data.rows()) {
            targetKeys.add(row[0]);
        }

        return new LoadReport("BulkInsert", data.size(), 0);
    }
}

Concrete Strategy: Upsert

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package edu.redwoods.design.strategy;

import java.util.Set;

/**
 * UML: ConcreteStrategyB
 *
 * Slower per row, but tolerant: a key that already exists is updated instead
 * of blowing up the whole batch. This is the algorithm the pipeline falls back
 * to after BulkInsert fails.
 */
public class UpsertStrategy implements LoadStrategy {

    private final Set<String> targetKeys;

    public UpsertStrategy(Set<String> targetKeys) {
        this.targetKeys = targetKeys;
    }

    @Override
    public LoadReport execute(LoadBatch data) {
        System.out.println("[Upsert] merging " + data.size()
                + " rows into " + data.target());

        int inserted = 0;
        int updated = 0;
        for (String[] row : data.rows()) {
            if (targetKeys.add(row[0])) {
                inserted++;
            } else {
                updated++;
            }
        }

        return new LoadReport("Upsert", inserted, updated);
    }
}

Context: LoadStage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package edu.redwoods.design.strategy;

/**
 * UML: Context
 *
 * A stage in the pipeline. It HAS-A LoadStrategy - composition, not
 * inheritance - and it knows nothing about how the load actually happens.
 * It knows only that it holds something with an execute() method.
 */
public class LoadStage {

    private LoadStrategy strategy;      // UML: -Strategy strategy

    public LoadStage(LoadStrategy strategy) {
        this.strategy = strategy;
    }

    // UML: +setStrategy(Strategy): void
    public void setStrategy(LoadStrategy strategy) {
        this.strategy = strategy;
    }

    // UML: +doWork(Object data): Object
    public LoadReport doWork(LoadBatch data) {
        return strategy.execute(data);
    }
}

Client of Strategy Pattern

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package edu.redwoods.design.strategy;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class StrategyPatternTest {

    public static void main(String[] args) {

        // Stands in for the target table's primary-key index.
        Set<String> warehouseKeys = new HashSet<>();
        warehouseKeys.add("C-1001");            // already loaded yesterday

        // The Context is built holding ONE algorithm.
        LoadStage loadStage = new LoadStage(new BulkInsertStrategy(warehouseKeys));

        LoadBatch cleanBatch = new LoadBatch("warehouse.orders",
                List.of(row("C-2001", "2024-01-05", "19.99"),
                        row("C-2002", "2024-01-05", "42.50")));

        LoadBatch dirtyBatch = new LoadBatch("warehouse.orders",
                List.of(row("C-3001", "2024-01-06", "7.25"),
                        row("C-1001", "2024-01-06", "5.00")));   // duplicate key

        System.out.println("=== batch 1, strategy = BulkInsert ===");
        System.out.println(loadStage.doWork(cleanBatch));

        System.out.println();
        System.out.println("=== batch 2, strategy = BulkInsert ===");
        try {
            System.out.println(loadStage.doWork(dirtyBatch));
        } catch (DuplicateKeyException e) {
            System.out.println("caught: " + e.getMessage());

            // Swap the algorithm while the program is running. The LoadStage
            // class is not edited, not subclassed, and not rebuilt.
            System.out.println("swapping strategy -> Upsert");
            loadStage.setStrategy(new UpsertStrategy(warehouseKeys));

            System.out.println(loadStage.doWork(dirtyBatch));
        }
    }

    /** Small helper so the batch literals stay readable. */
    private static String[] row(String key, String date, String amount) {
        return new String[]{key, date, amount};
    }
}