Factory Method සහ Strategy යන ඩිසයින් පැටර්න් අතර වෙනස

Submitted by Kamal Wickramanayake on සඳු, 11/16/2020 - 22:06

Gang of Four (GoF) collection of design patterns talks about these patterns where the beginners sometimes fail to see the difference between. They often look at the code example of a pattern and attempt to guess what it is but fail to read through the details and understand the real intent of it. The Factory Method and Strategy patterns are coded similarly but with minor code differences. These minor code differences make the two patterns very different in the way they are used and the problems they solve. In this article, I describe these differences.

For your very clear understanding, I use a common set of classes and an interface (that can be used to sort an array of integers) to illustrate how the same appears in both the Strategy pattern and the Factory Method pattern still producing different benefits.

To properly grasp the GoF patterns, you should very precisely analyze the intents of them. Let's look at the intents of the two patterns:

Strategy: "Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it."

Factory Method: "Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses."

Strategy Pattern

Let's say we need to sort an array of integers in an application. There are different sorting algorithms but all of them belong to the "sorting family" of algorithms. You need to write the part of the application that uses the sorting algorithm in a manner that it (the "client" as mentioned in the intent or the part that uses the sorting algorithm) is independent of the sorting algorithm used.

class SortAlgorithmClient {

    private SortAlgorithm sortAlgorithm; // Initialized somehow (setter, constructor,...)

    void someMethod() {
        ...
        sortAlgorithm.sort(someDataArray);
        ...
    }
}

interface SortAlgorithm {
    void sort(int[] dataArray);
}

class QuickSortAlgorithm implements SortAlgorithm {
    void sort(int[] dataArray) {
        ... // implementation of quick sort
    }
}

Why would you want to make the used part (QuickSortAlgorithm) and the using part (SortAlgorithmClient) independent? Obviously you are interested in changing the used part (say to BubbleSortAlgorithm). You may want the change of the algorithm to happen while the application is running, or you may want to test different algorithms and pick the best one later on, or you may have different versions of your application where each of them is configured to use a specific algorithm from the family, or you may want to keep on improving algorithms later on. You should be able to identify other reasons.

That's a case where you clearly see that the intent of the pattern matches with your needs. You need the algorithm to vary independently from the clients that use it. You want a way to define a family of algorithms. You accomplish this by encapsulating each algorithm in an interchangeable manner. For this purpose, a member of the interchangeable familly of algorithms is abstracted or represented by the SortAlgorithm interface (or you can code it as an abstract class).

If compared with the intent of the Factory Method design pattern, here you don't find an interface (method) to create an object of SortAlgorithm type. There's no subclass here that has code to create a specific type of SoftAlgorithm (so a parent class is not passing the responsibility of creating a specific type of SortAlgorithm to a sub class). That's what the Factory Method design pattern does which is used for an entirely different purpose.

Factory Method Pattern

Let's say you are writing a framework. If it is a framework, you need to allow the framework users (those developers who will use your framework) to configure or extend the framework in their own applications in ways they want. So the total application has code coming from the framework and code coming from the specific application.

As the framework writer, you don't have the application specific code while you write the framework. Nor you know who will write such code. But you know that your framework code will use the application specific code (This is different from the application specific code using your framework code - which indicates a library usage - even though most frameworks carry such libraries as well). So as the framework writer you want to have a mechanism for the framework users to introduce their code to the framework. As the framework writer, you may pick the Factory Method for this purpose.

/** First we have our framework code */

class FrameworkClassA {
    private FrameworkClassB frameworkClassB; // Initialized somehow

    // This class uses frameworkClassB a lot. Other framework classes may
    // also use frameworkClassB.
}

abstract class FrameworkClassB {

    // We are doing some framework job here. Say we need a sorting algorithm as well.
    void someMethod() {
        ...
        // We need to sort now. But we don't want to be strict and use our own means to do so.
        // We want it to be decided later on (after this class is coded fully).
        SortAlgorithm sortAlgorithm = createSortAlgorithm(); // Invoke the method that acts as a factory.
        sortAlgorithm.sort(someDataArray);
        ...
    }
   
    // Here's the method that acts as a factory. It's abstract so we force the framework user
    // to implement this. It is how this parent class (FrameworkClassB) lets the sub class to determine
    // which class (which SortAlgorithm) to instantiate.
    abstract SortAlgorithm createSoftAlgorithm();
}

interface SortAlgorithm {
    void sort(int[] dataArray);
}


/** Now we have application specific code that is written to use the above framework code */

// Here's the sorting algorithm we want the framework to use
class QuickSortAlgorithm implements SortAlgorithm {
    void sort(int[] dataArray) {
        ... // implementation of quick sort
    }
}

// This is how we inform the framework to use our preferred sorting algorithm.
// Here's the sub class that determines which class (SortAlgorithm) to instantiate.
class MyAppFrameworkClassB extends FrameworkClassB {

    SortAlgorithm createSoftAlgorithm() {
        // Below is the line of code that determines which class to instantiate
        return new QuickSortAlgorithm();
    }
}

// This is how we may start our app that uses the framwork
public class MyApp {

    public static void main(String[] args) {

         FrameworkClassA framworkClassA = new FrameworkClassA();

         // Let's customize the framework now
         MyAppFrameworkClassB myAppFrameoworkClassB = new MyAppFrameworkClassB();
         framworkClassA.setFrameworkClassB(myAppFrameworkClassB);

         ...
         // Pass myAppFrameworkClassB to other framework objects if needed
         // and do other framework initialization tasks
         ...

         frameworkClassA.doSomeWork(); // Start the framework
    }
}

You write a class (FrameworkClassB) that has some amount of framework code and when that framework code wants to execute the application specific code, you let the framework code to find such application specific code by invoking a method of it (method createSoftAlgorithm()) which would return an object that holds some application specific code.

So who decides which class to instantiate inside the factory method? It is the framework user (or the subclass as defined by the framework user). That's why the intent of the Factory method says "subclasses decide which class to instantiate".

Why all of these components are organized in this way? Because the framework writer does not know which class to instantiate to get the application specific behavior. It's a decision to be made later on (even after the framework code is completed). It's defined by the framework user (the subclass writer). That's why the intent of the Factory Method says "Factory Method lets a class defer instantiation to subclasses.". "Define an interface for creating an object" in the intent refers to the method used by the framework code to find or acquire a needed object.

Summary

So you see, both these patterns are coded similarly still with small differences that make a significant difference in the way they are used. They are solutions to different problems.

I should tell you that I explained the differences between these patterns by using an example where sorting and a framework is involved. But the patterns are meant to be used not just in sorting and not just when you want to write frameworks. Use them in other places as well when you find the intents of patterns to match with the forces of design problems you solve.

Importantly, don't just attempt to look at code examples and understand patterns. Explore the intent first. Analyze and see how the code produces or fulfills the desired intent.

Ability to understand the behaviour of a pattern or identifying different patterns used in software by looking at code are also good skills. But often you may not be able to identify the exact reasons why the code has been implemented in the way you see. That's why you need to understand the intent (or the context, problem and solution) of patterns and then understand how the solution translates into code at least when you are learning patterns.