Practical use of the Strategy template

The use of templates (or patterns) in object-oriented programming stems from the desire to make the code simpler, more reliable, and not to reinvent the wheel, as well as to effectively organize the joint work of programmers with different levels of training giving them a single basis for understanding the source code in the conceptual framework of business logic applications. This means that learning design patterns is a key step in the professional development of a programmer.

How to study design patterns? There are two approaches: boring and intelligible (Do you like my classification?). A boring approach involves the academic study of a list of patterns using abstract examples. Personally, I prefer the opposite - an intelligible approach, when setting the task at a relatively high level of formulation allows you to choose design patterns. Although you can combine both approaches.

So, let's go?

The Strategy template refers to a group of behavioral templates.

Brief Definition of the Strategy Template


The template serves to switch between a family of algorithms when an object changes its behavior based on a change in its internal state.

Practical examples of applying the Strategy template


  • Sorting: We want to sort these numbers, but we don’t know if we will use BrickSort, BubbleSort or any other sorting. For example, you have a website on which a page displays elements based on popularity. However, many things can be “Popular” (most views, most subscribers, creation date, most activity, least comment). In case the management does not yet know exactly how to place an order, and may want to experiment with different orders at a later date, you create an interface (IOrderAlgorithm or something else) with the order method and allow the Orderer object to delegate the order of the concrete implementation of the IOrderAlgorithm interface . You can create CommentOrderer, ActivityOrderer, etc., and simply turn them off when new requirements appear.
  • Processing a queue from heterogeneous objects (queue processing and saving data): An example would be a proxy system that accumulates objects from different data sources, then after extracting an object from the queue and then saving it, it is determined by the selection strategy based on the properties of this object.
  • Validation We need to check the elements in accordance with the “Some Rule”, but it is not yet clear what this rule will be, and we can think of new ones.
  • Authentication: Select an authentication strategy between Basic, Digest, OpenID, OAuth schemes.

Here is an example:

interface AuthStrategy {
    auth(): void;
}
class Auth0 implements AuthStrategy {
    auth() {
        log('Authenticating using Auth0 Strategy')
    }
}
class Basic implements AuthStrategy {
    auth() {
        log('Authenticating using Basic Strategy')
    }
}
class OpenID implements AuthStrategy {
    auth() {
        log('Authenticating using OpenID Strategy')
    }
}

class AuthProgram {
    private _strategy: AuthStrategy
    use(strategy: AuthStrategy) {
        this._strategy = strategy
        return this
    }
    authenticate() {
        if(this._strategy == null) {
            log("No Authentication Strategy set.")
        }
        this._strategy.auth()
    }
    route(path: string, strategy: AuthStrategy) {
        this._strategy = strategy
        this.authenticate()
        return this
    }
}

  • (games): — , , , , , , , . , , , , . «», «», «» Attack() . , «», «», «», « » Attack ().
  • (storing information): , , -. , PDF, , . , ; , , A, B C, . PDF , / PDF. , , , , , , B C, , A. , . PDF, , , . . Dependency Injection « / » ( , ), , , , , . , ( , ), , , , . « », «cleanUp» , , , , .
  • (outputting): X , CSV, XML, JSON .
  • (invoicing): - , , - .
  • (navigation): .
  • (logging): Log4Net Log4j , Appenders, Layouts, and Filters.
  • Encrypting: for small files, you can use the in-memory strategy when the entire file is read and stored in memory (say, for files <1 GB). For large files, you can use a different strategy where parts of the file are read in memory and partially encrypted results are stored in tmp files. These can be two different strategies for the same task.

Here is an example:

//   ""
interface  Cipher  {
     public void performAction();
}

class InMemoryCipherStrategy implements Cipher { 
         public void performAction() {
             //   byte[] ....
         }
}

class SwaptToDiskCipher implements Cipher { 
         public void performAction() {
             //     .... 
         }

}

//   
File file = getFile();
Cipher c = CipherFactory.getCipher( file.size());
c.performAction();

  • Graphic editor: for example, in the Windows Paint application there is an implementation of a strategy template in which you can independently select the shape and color in different sections. Here, shape and color are algorithms that can be changed at runtime.

Shape redCircle = new RedCircle(); //    «»
Shaped redCircle = new Shape("red","circle"); //   «»

SOLID and implementation of the Strategy template


What is the main problem that the "Strategy" template solves? In fact, this is a replacement for the flat code IF .... THAT ... ... on its object implementation.

Example of dirty flat code (wrong):

class Document {...}
class Printer {
    print(doc: Document, printStyle: Number) {
        if(printStyle == 0 /*   */) {
            // ...
        }
        if(printStyle == 1 /*  */) {
            // ...            
        }
        if(printStyle == 2 /*     */) {
            // ...
        }
        if(printStyle == 3 /*     */) {
            // ...            
        }
        if(printStyle == 4 /*     */) {
            // ...
        }
        // ...
    }
}

An example of the same code with the "Strategy" template (correctly):

class Document {...}
interface PrintingStrategy {
    printStrategy(d: Document): void;
}
class ColorPrintingStrategy implements PrintingStrategy {
    printStrategy(doc: Document) {
        log(" ")
        // ...
    }
}
class InvertedColorPrintingStrategy implements PrintingStrategy {
    printStrategy(doc: Document) {
        log("  ")
        // ...
    }
}
class Printer {
    private printingStrategy: PrintingStrategy
    print(doc: Document) {
        this.printingStrategy.printStrategy(doc)
    }
}

Here is another example of the correct implementation of the Strategy template based on SOLID.

//  /
interface LockOpenStrategy {
    open();
    lock();
}
//      
class RetinaScannerLockOpenStrategy implements LockOpenStrategy {
    open() {
        //...
    }
    lock() {
        //...
    }
}

//       
class KeypadLockOpenStrategy implements LockOpenStrategy {
    open() {
        if(password != ""){
            log("Entry Denied")
            return
        }
        //...
    }
    lock() {
        //...
    }
}
//        .
abstract class Door {
    public lockOpenStrategy: LockOpenStrategy
}
//   .
class GlassDoor extends Door {}
//    .
class MetalDoor extends Door {}
//       .
class DoorAdapter {
    openDoor(d: Door) {
        d.lockOpenStrategy.open()
    }
}

Below is the actual encoding of the logic.

var glassDoor = new GlassDoor(); //   
glassDoor.lockOpenStrategy = new RetinaScannerLockOpenStrategy(); //         
var metalDoor = new MetalDoor(); //     
metalDoor.lockOpenStrategy = new KeypadLockOpenStrategy(); //      .
var door1 = new DoorAdapter().openDoor(glassDoor); //    . 
var door2  = new DoorAdapter().openDoor(metalDoor); //    . 

As you can see above, this is a fully object-oriented code that excludes the IF ... procedural style . ELSE ....... or SWITCH ... CASE ...

By the way, why did we use the adapter class? The door itself will not open, it always opens with something on the one hand, and on the other there can be some events preceding the opening of the door or upon completion of its opening, for example BeforeOpen () and AfterOpen (), can also be linked to adapter.

Refactoring and Strategy Template


The Strategy template should be used when you begin to notice repetitive algorithms, but in different variations. Thus, it is necessary to divide the algorithms into classes and fill them as necessary in your program.

Further, if you notice duplicate conditional statements around a sibling algorithm.
When most classes have associated behavior. And then you need to select it and move them to separate classes.

I hope this selection of examples helps you to use the "Strategy" template appropriately.
I will be glad if you can give more examples of this template in the comments.

Happy coding, friends and colleagues!

Source: https://habr.com/ru/post/undefined/


All Articles