Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly implement strategy design pattern

I'm trying to implement strategy design pattern, and want to know if I do it correctly.

Lets say, I have class FormBuilder which uses strategy from list below to build the form:

  • SimpleFormStrategy
  • ExtendedFormStrategy
  • CustomFormStrategy

So the questions are:

  1. Is it correct to select strategy inside FormBuilder, and not passing strategy from outside?
  2. Doesn't this violates open closed principle? So, if I want to add one more form strategy or to remove an existing one, I have to edit the FormBuilder class.

Draft code example

class Form {
    // Form data here
}

interface IFormStrategy {
    execute(params: object): Form;
}

class SimpleFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building simple form
        return new Form();
    }
}

class ExtendedFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building extended form
        return new Form();
    }
}

class CustomFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building custom form
        return new Form();
    }
}

class FormBuilder {
    public build(params: object): Form {
        let strategy: IFormStrategy;

        // Here comes strategy selection logics based on params

        // If it should be simple form (based on params)
        strategy = new SimpleFormStrategy();
        // If it should be extended form (based on params)
        strategy = new ExtendedFormStrategy();
        // If it should be custom form (based on params)
        strategy = new CustomFormStrategy();

        return strategy.execute(params);
    }
}
like image 448
carcade Avatar asked Nov 21 '25 06:11

carcade


2 Answers

You asked 2 questions that are not directly linked to TypeScript. The code can be directly converted to C# / Java, the usual mainstream OOP languages. It's even more interesting because it's about both Design Patterns and SOLID principles, both pillars of the Object Oriented Programming.

Let's answer it specifically before being more general:

  1. Is it correct to select strategy inside FormBuilder, and not passing strategy from outside?

Yes. The opposite leads to a FormFactory wrapper without much interest. Why not calling directly strategy.execute()?

  1. Doesn't this violates open closed principle? So, if I want to add one more form strategy or to remove an existing one, I have to edit the FormBuilder class.

Builders and Factories are tightly couple to the underneath created types by design. It's a local violation of the OCP but with them the client code is decoupled from form creation implementation details.

Misc comments

  • Design patterns
    • Every design pattern must be found from the context (client code and even business domain) and not upfront. Design patterns are rarely used by the book but must be adapted to suit the context and can be mixed together.
    • The IFormStrategy is firstly an (abstract) Factory: it creates a Form. A better name should be IFormFactory { create(...): Form; } (or just FormFactory, the "I" prefix being more common in C# than in TypeScript). It's a Strategy for the FormBuilder but not intrinsically. By the way, the term Strategy is rarely used when naming classes, because it's too generic. Better use a more specific/explicit term.
    • The FormBuilder is not exactly a Builder which should create an object by parts, usually with a fluent API like formBuilder.withPartA().withPartB().build();. This class selects the appropriate Factory/Strategy based on the input params. It's a Strategy Selector, or a Factory of Factory :D that also call the factory to finally create the Form. Maybe it does too much: just selecting the factory would be enough. Maybe it's appropriate, hiding complexity from the client code.
  • OOP + Design Patterns vs Functional Programming in TypeScript
    • Strategies in a functional language are just functions. TypeScript allows to define higher order functions, with an interface/type but without a wrapping object /class that may not bring more value. The client code just have to pass another function which can be a "simple lambda" (fat arrow function).
  • Misc
    • params argument is used by both the Builder and the Factories. It would be probably better to split it, to avoid mixing distinct concerns: strategy selection and form creation.
    • If the kind of the form to create (Simple, Extended, Custom) is not dynamic but already known from the client code side, it may be better to offer directly 3 methods with specific arguments: createSimpleForm(simpleFormArgs), createExtendedForm(extendsFormArgs)... Each method will instanciate the associated factory and call it create(formArgs) method. This way, no need for a complex algorithm to select the strategy, based on ifs or switchs which increases the Cyclomatic Complexity. Calling each createXxxForm method will also be simpler, the object argument being more little.
like image 129
Romain Deneau Avatar answered Nov 22 '25 19:11

Romain Deneau


In design pattern terms for Strategy, your FormBuilder plays the role of a Context, which holds the reference to the current strategy in use (IFormStragegy). The strategy is passed from outside (using setter) so it is open to extension (OCP). So regarding your questions:

  1. Is it correct to select strategy inside FormBuilder, and not passing strategy from outside?

It is not correct implementation of strategy. You should create instances of your strategy and pass it to the context. The strategy therefore can be swapped at run-time.

  1. Doesn't this violates open closed principle? So, if I want to add one more form strategy or to remove an existing one, I have to edit the FormBuilder class.

Yes it does, you cannot make a new strategy known to the FormBuilder without changing it.

You can look here for an example.

FormBuilder context = new FormBuilder();
IFormStrategy simple = new SimpleFormStrategy();
IFormStrategy extended = new ExtendedFormStrategy();
IFormStrategy custom = new CustomFormStrategy();

context.setStrategy(simple);
context.build(/* parameters */)

context.setStrategy(custom);
context.build(/* parameters */)
like image 33
Lini Avatar answered Nov 22 '25 19:11

Lini



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!