Design Pattern Template Method Pattern (Inheritance) vs Strategy Pattern (Delegation)

Template Method Pattern and Strategy Pattern are two commonly used behavioral design patterns. They used two different implementation methods of inheritance and delegation respectively. Because the UML diagram was mentioned in the previous article, this article can bring out the UML diagrams of the two different modes by the way .

Template Method Pattern

The template method pattern (Template Method Pattern) defines the skeleton of the algorithm, and wewrite the specific implementation details in subclasses (subtypes) by inheriting inheritance . This approach allows us to change parts of the algorithm via subclassing without modifying the template method.

Behavioral Subtyping

Before talking about inheritance, we must first mention a concept Behavioral subtyping, which is an important principle in object-oriented programming, which provides a more formal way to determine when inheritance (inheritance) and extension (extension) should be used. A core idea of ​​Behavioral subtyping is that client code should not be affected when a subtype is used to replace a supertype. That is, a subtype should be able to satisfy the requirements of its supertype both syntactically and behaviorally. This idea is closely related to the Liskov Substitution Principle (LSP). Three important conditions for realizing this view are as follows:

  1. Same or stronger invariants than super class: The subtype should have the same or stronger invariants than the superclass. An invariant is a condition that a property of a class needs to satisfy throughout the lifetime of the object. This means that the subtype needs to maintain the same attribute constraints as the supertype, or add stricter constraints. This helps ensure that subtype objects are behaviorally compatible with the supertype while meeting more specific needs. This is a bit abstract, let's give an example: For example, there is a parent class called Bird, and one of its properties is called speed. BirdAn invariant of the class might be that velocity is never negative, ie speed >= 0. Now, we create a subclass Penguinto extend Birdthe class. In this case, Penguinthe class needs to satisfy Birdthe same or stronger invariants as the class. This means that Penguinthe class also needs to ensure that the velocity is never negative (same invariant), or can add stricter constraints such as an upper bound on velocity (stronger invariant), eg 0 <= speed <= 10.

  2. Same or weaker preconditions for all methods in super class: Methods of subtypes should have the same or weaker preconditions as the supertype. Preconditions are conditions that need to be met before a method is called, usually involving constraints on method input parameters. This means that methods of subtypes should have less restrictive or the same restrictions on input parameters as supertypes. In this way, when subtypes are used to replace supertypes, the original call will not fail due to input restrictions.

  3. Same or stronger postconditions for all methods in super class: Methods of subtypes should have the same or stronger postconditions as the superclass. Postconditions are conditions that must be satisfied after a method call, usually involving constraints on the method's output. This means that the output of a subtype's method should meet the supertype's requirements or have stronger guarantees. In this way, when the subclass replaces the parent class, the correctness of the program's output results dependent on the parent class will not be affected.

class Car extends Vehicle {
    int fuel;
    boolean engineOn;
    //@ invariant fuel >= 0;
    //@ requires fuel > 0 && !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < old(speed)
    void brake() { … }
}

class Hybrid extends Car {
    int charge;
    //@ invariant charge >= 0;
    //@ requires (charge > 0 || fuel > 0) 
    &&
    !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < \old(speed)
    //@ ensures charge > \old(charge)
void brake() { … }

In the above example, Hybrid is the behavioral subtyping of Car. In this example, the Car class has an invariant: //@ invariant fuel >= 0;, indicating that the car's fuel quantity cannot be negative. The Hybrid class, as a subclass of the Car class, should also satisfy this invariant. At the same time, the Hybrid class has an additional invariant: //@ invariant charge >= 0;, indicating that the electric quantity of the hybrid vehicle cannot be negative. Thus, the Hybrid class, as a subclass, satisfies the invariants of the Car class (constraints on the amount of fuel) and its own additional invariant (constraints on the amount of electricity). This is the first condition.

For the other two conditions, we can easily know that it satisfies that the overridden method start has a weaker precondition, and the overridden method brake has a stronger postcondition. These two conditions allow us to replace the parent class with a subclass anywhere in the future without causing program errors.

Generally, when the behavioral subtyping is satisfied, we can use the template method pattern. The UML diagram and example code of the template method pattern are as follows:

 Here the hollow solid arrow in UML represents the inheritance relationship. We implement the abstract class and realize the functions defined in the abstract class through the specific classes class1 and class2.

abstract class AbstractOrder {
    public abstract boolean lessThan(int i, int j);
}

class AscendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i < j;
    }
}

class DescendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i > j;
    }
}

// ...

static void sort(int[] list, AbstractOrder order) {
    // ...
    boolean mustSwap = order.lessThan(list[j], list[i]);
    // ...
}

The above is a code example of an extended sorting method implemented with the template method pattern. We use an abstract base class AbstractOrder. AscendingOrderand DescendingOrderinherit from AbstractOrderthe class respectively, and override lessThanthe method in it. Through inheritance, the AscendingOrderand DescendingOrderclass implicitly obtains AbstractOrderall the methods and properties of the class. Here, our main focus is on type hierarchies and code reuse.

Strategy Pattern

Strategy Pattern (Strategy Pattern) is a behavioral design pattern that allows switching between different algorithms or strategies at runtime as needed. The strategy mode is implemented through delegation , which separates the implementation of the algorithm from the object using the algorithm, thereby improving the flexibility and scalability of the code. Generally speaking, what can be achieved with the template method pattern, we can use the strategy pattern to achieve, and often have more benefits.

In the strategy pattern, we will abstract the strategy into an interface, and then use different strategies to implement the strategy interface. Here, the hollow arrow and the dotted line represent the implementation relationship between the classes, and here we need to inherit from the solid line above. relationship to distinguish. In the strategy pattern, which strategy we need to use, we directly put the specific object of the strategy into the Navigator for use. We use the routeStrategy class to define this strategy. Through Java's dynamic dispatch, we can freely point the pointer to different specific Strategy.

 The above figure is the interaction diagram of the strategy pattern. The system calls methods in different specific strategy objects based on different strategies. The advantage of this is that policies can be easily introduced into the system without modifying client code. The Strategy pattern allows the implementation of algorithms to be separated from the client code that uses them, improving code maintainability and flexibility.

A code example of a strategy pattern:

interface Order {
boolean lessThan(int i, int j);
}
class AscendingOrder implements Order {
public boolean lessThan(int i, int j) { return i < j; }
}
class DescendingOrder implements Order {
public boolean lessThan(int i, int j) { return i > j; }
}
…
static void sort(int[] list, Order order) {
…
boolean mustSwap =
order.lessThan(list[j], list[i]);
…
}

In fact, it looks like this code is very similar to the above, but in the way of writing the delegate, we use an interface Order. AscendingOrderand DescendingOrderimplement Orderthe interface respectively. Then we directly pass the strategy to be used ( AscendingOrder或DescendingOrder ) to the variable of the Order type in sort, so that we realize the free switching of strategies in a very flexible and low-coupling manner. It is equivalent to "delegating" the specific algorithm of sorting to this specific order object, and directly calling others to use it, regardless of their specific implementation details. In delegation, we are concerned with defining a set of behaviors (via Orderinterfaces) and implementing those behaviors individually. Delegation emphasizes behavior and composition, not type hierarchy.

Summary about these two methods:

Inheritance (Inheritance) and composition + delegation (Composition + Delegation) are key concepts in object-oriented design. Inheritance can achieve a lot of code reuse in a strong coupling relationship, but it should be used with caution. Good designs are usually more inclined to use composition and delegation, because they support the reuse and encapsulation of programming interfaces, help information hiding, and produce easier-to-test code (while using inheritance because you need to rewrite the code of the parent class, It is often necessary to know the specific implementation information of the parent class, which is not conducive to information hiding). Although inheritance is more convenient in some cases, delegation should be given priority when designing.

Guess you like

Origin blog.csdn.net/weixin_44492824/article/details/130518595