簡體   English   中英

如何正確實施策略設計模式

[英]How to correctly implement strategy design pattern

我正在嘗試實施策略設計模式,並想知道我是否正確執行。

可以說,我有類FormBuilder ,它使用下面列表中的策略來構建表單:

  • SimpleFormStrategy
  • ExtendedFormStrategy
  • CustomFormStrategy

所以問題是:

  1. FormBuilder內部選擇策略而不是從外部傳遞策略是否正確?
  2. 這不違反開閉原則嗎? 因此,如果我想再添加一種表單策略或刪除現有的一種,我必須編輯FormBuilder類。

草稿代碼示例

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);
    }
}

在 Strategy 的設計模式術語中,您的 FormBuilder 扮演 Context 的角色,它保存對當前使用的策略 ( IFormStragegy ) 的引用。 該策略從外部傳遞(使用setter ),因此它對擴展(OCP)開放。 所以關於你的問題:

  1. FormBuilder內部選擇策略而不是從外部傳遞策略是否正確?

這不是戰略的正確實施。 您應該創建策略的實例並將其傳遞給上下文。 因此可以在運行時交換策略。

  1. 這不違反開閉原則嗎? 因此,如果我想再添加一種表單策略或刪除現有的一種,我必須編輯 FormBuilder 類。

是的,您不能在不更改它的情況下使 FormBuilder 知道新策略。

您可以在此處查看示例。

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 */)

您提出了 2 個與 TypeScript 沒有直接關聯的問題。 代碼可以直接轉換為C#/Java,通常的主流OOP語言。 它更有趣,因為它涉及設計模式SOLID原則,這兩個都是面向對象編程的支柱。

在更籠統之前,讓我們具體回答一下:

  1. FormBuilder內部選擇策略而不是從外部傳遞策略是否正確?

是的。 相反會導致一個沒有太多興趣的FormFactory包裝器。 為什么不直接調用strategy.execute()

  1. 這不違反開閉原則嗎? 因此,如果我想再添加一種表單策略或刪除現有的一種,我必須編輯FormBuilder類。

BuildersFactories通過設計與底層創建的類型緊密耦合。 這是對 OCP 的局部違反,但有了它們,客戶端代碼與表單創建實現細節分離。

雜項評論

  • 設計模式
    • 每個設計模式都必須從上下文(客戶端代碼甚至業務領域)中找到,而不是預先找到。 本書很少使用設計模式,但必須根據上下文進行調整,並且可以混合在一起。
    • IFormStrategy首先是一個(抽象)工廠:它創建一個Form 更好的名字應該是IFormFactory { create(...): Form; } IFormFactory { create(...): Form; } (或只是FormFactory ,“我”的前綴是更多的C#常見比打字稿)。 這是FormBuilder的一種策略,但不是本質上的。 順便說一下,在命名類時很少使用術語策略,因為它太通用了。 最好使用更具體/明確的術語。
    • FormBuilder 不完全是一個應該按部分創建對象的構建器,通常使用像formBuilder.withPartA().withPartB().build();這樣的流暢 API formBuilder.withPartA().withPartB().build(); . 此類根據輸入參數選擇適當的工廠/策略 它是一個Strategy SelectorFactory of Factory :D ,它也調用工廠以最終創建Form 也許它做得太多了:只需選擇工廠就足夠了。 也許這是合適的,隱藏客戶端代碼的復雜性。
  • OOP + 設計模式與 TypeScript 中的函數式編程
    • 函數式語言中的策略只是函數。 TypeScript 允許定義更高階的函數,帶有interface / type但沒有包裝對象/類,這可能不會帶來更多價值。 客戶端代碼只需要傳遞另一個可以是“簡單 lambda”(粗箭頭函數)的函數。
  • 雜項
    • params參數由BuilderFactories 使用 拆分它可能會更好,以避免混合不同的關注點:策略選擇表單創建
    • 如果要創建的表單類型(Simple、Extended、Custom)不是動態的,而是從客戶端代碼端已知的,最好直接提供 3 個帶有特定參數的方法: createSimpleForm(simpleFormArgs)createExtendedForm(extendsFormArgs) 。 .. 每個方法都會實例化關聯的工廠並調用它create(formArgs)方法。 這樣,就不需要復雜的算法來選擇策略,基於if s 或switch s 這會增加Cyclomatic Complexity 調用每個createXxxForm方法也會更簡單,對象參數createXxxForm

策略是一種行為設計模式,它將一組行為轉換為對象,並使它們在原始上下文對象中可互換。

原始對象,稱為上下文,持有對策略對象的引用,並委托它執行行為。 為了改變上下文執行其工作的方式,其他對象可以用另一個對象替換當前鏈接的策略對象。

使用示例:策略模式在 TypeScript 代碼中非常常見。 它經常用於各種框架中,為用戶提供一種無需擴展即可更改類行為的方法。

識別:策略模式可以通過讓嵌套對象完成實際工作的方法以及允許用不同的對象替換該對象的設置器來識別。

概念示例此示例說明了策略設計模式的結構。 它側重於回答以下問題: • 它由哪些類組成? • 這些類扮演什么角色? • 模式的元素以何種方式相關?

index.ts:概念示例

/**
     * The Context defines the interface of interest to clients.
     */
    class Context {
        /**
         * @type {Strategy} The Context maintains a reference to one of the Strategy
         * objects. The Context does not know the concrete class of a strategy. It
         * should work with all strategies via the Strategy interface.
         */
        private strategy: Strategy;

        /**
         * Usually, the Context accepts a strategy through the constructor, but also
         * provides a setter to change it at runtime.
         */
        constructor(strategy: Strategy) {
            this.strategy = strategy;
        }

        /**
         * Usually, the Context allows replacing a Strategy object at runtime.
         */
        public setStrategy(strategy: Strategy) {
            this.strategy = strategy;
        }

        /**
         * The Context delegates some work to the Strategy object instead of
         * implementing multiple versions of the algorithm on its own.
         */
        public doSomeBusinessLogic(): void {
            // ...

            console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
            const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
            console.log(result.join(','));

            // ...
        }
    }

    /**
     * The Strategy interface declares operations common to all supported versions
     * of some algorithm.
     *
     * The Context uses this interface to call the algorithm defined by Concrete
     * Strategies.
     */
    interface Strategy {
        doAlgorithm(data: string[]): string[];
    }

    /**
     * Concrete Strategies implement the algorithm while following the base Strategy
     * interface. The interface makes them interchangeable in the Context.
     */
    class ConcreteStrategyA implements Strategy {
        public doAlgorithm(data: string[]): string[] {
            return data.sort();
        }
    }

    class ConcreteStrategyB implements Strategy {
        public doAlgorithm(data: string[]): string[] {
            return data.reverse();
        }
    }

    /**
     * The client code picks a concrete strategy and passes it to the context. The
     * client should be aware of the differences between strategies in order to make
     * the right choice.
     */
    const context = new Context(new ConcreteStrategyA());
    console.log('Client: Strategy is set to normal sorting.');
    context.doSomeBusinessLogic();

    console.log('');

    console.log('Client: Strategy is set to reverse sorting.');
    context.setStrategy(new ConcreteStrategyB());
    context.doSomeBusinessLogic();

Output.txt:執行結果

Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM