This is the first article in a series on design patterns.

  1. A word to sum up the design pattern of all paths: factory mode =? Policy mode =? Template method pattern

  2. Use a combination of design patterns — decorator patterns in beauty Camera

  3. Use the combined design pattern – Find the remote proxy pattern to use for the object

  4. Remove unnecessary state variables with design patterns – state patterns

Although different design patterns address different problems, at a higher level of abstraction, they achieve the same goals through the same means. This article takes a more abstract look at the factory pattern, the strategy pattern, the template method pattern, and the design principles that these patterns follow. Let’s start with a one-sentence summary of what they have in common:

They add a layer of abstraction to encapsulate the change, then program against the abstraction and use polymorphism to cope with the change.

Projects that apply these design patterns can use the following links:

  1. No more fighting with product managers – Android custom radio buttons
  2. Write code so you can become good friends with product managers — strategy mode in practice

The factory pattern

1. What is the change

For the factory pattern, the change is the way the object is built. For example:

public class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza ;
    
        // Construct a specific pizza object
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
    
        // Use the pizza object
        pizza.prepare();
        pizza.bake();
        returnpizza ; }}Copy the code

The opposite of abstract is concrete, which in the case of the factory pattern is building objects with new. The consequence of this is that PizzaStore not only requires the introduction of a specific Pizza class, but is coupled to the details of how the Pizza was built.

If PizzaStore only made these two pizzas for the rest of its life, the code above would be fine and would not need refactoring. But if you wanted to add a new Pizza type, you’d have to modify orderPizza() to add if-else to it.

2. How to cope with change

By encapsulating the changes in a new level of abstraction, the upper layer of code is decoupled from the changes. For the factory pattern, the new abstraction is called a factory, which separates the construction of an object from its use, so that the code that uses the object does not depend on the details of the construction.

The benefit of decoupling is that “upper-level code does not need to be changed when changes occur”, which can also be expressed as “extending functionality without modifying existing code”. This is known as the “open closed principle”.

  • “Closed for modification” means: when you need to extend functionality for a class, don’t try to modify the existing code of the class. It’s not allowed! Why not? Because the existing code is the efforts of several programmers, through the iteration of several versions, finally get the correct code. It contains extensive and profound knowledge, and you never understand the details, modify it will be a bug!
  • “Open to extension” means that the code of a class should be so abstract that it can be extended without modifying the existing code of the class.

The real problem is if the existing classes in the project are not extensible, even the ones that pull the whole thing together. When you need to add new features in a tight iteration, what do you do? Violate “off to modify” or bite the bullet and reconstruct? (Welcome to discuss ~~)

3. Three encapsulation changes

  1. Simple factory mode

Since the goal is to eliminate the details of building specific pizzas in orderPizza(), the most straightforward thing to do is to pull them out and put them in another class:

public class PizzaFactory{
    public static Pizza createPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
        returnpizza ; }}Copy the code

The code using Pizza then becomes:

public class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza = PizzaFactory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        returnpizza ; }}Copy the code

Wait, is this any different than if we took a piece of common code and put it in the Util class?

No, it doesn’t make any difference. Strictly speaking, this is not a design pattern, more like a programming habit. It’s just code moving, but the nice thing about this habit is that it hides the details of the build object, because the build object is constantly changing, so it encapsulates the change, and eventually it can be reused, such that the menu class also needs to build pizza objects and get their price.

Using static methods is a common technique for this type of encapsulation, which has the advantage that it can be invoked without creating a new factory object, but has the disadvantage that it is not extensible (static methods cannot be overridden).

  1. Factory method pattern

In the simple factory pattern, the factory’s ability to build several objects is defined before compilation, and if you want to add another new object, you must modify the existing factory class. It doesn’t comply with the open closed principle. So the simple factory pattern is not elastic enough for the new object type scenario.

Is there a way to add an object type without modifying an existing class?

The factory method pattern does this because it uses inheritance:

// Abstract pizza shop
public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        return pizza ;
    }
    // Pizza stores in different regions can offer pizza with local characteristics
    protected abstract Pizza createPizza(String type) ;
}

//A Store offers cheese and bacon pizza
public class PizzaStoreA extends PizzaStore{
    @Override
    protected Pizza createPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
        returnpizza ; }}Copy the code

While the simple factory pattern encapsulates the details of the build object in a static method (which cannot be inherited), the factory method pattern encapsulates it in an abstract method so that subclasses can add pizzas by overriding the abstract method.

Now is a good time to introduce another design principle, the “dependency inversion principle” : upper-level components must not depend on lower-level components, and they must not depend on concrete, but on abstractions.

In the example above, PizzaStore is the upper component and CheesePizza is the lower component. Building a pizepizza directly in a PizzaStore violates the dependency inversion principle. After factory-mode refactoring, PizzaStore relies on the Pizza abstraction. CheesePizza also relies on this abstraction. So violating a dependency inversion makes your code less elastic and less extensible.

RecyclerView.Adapter in Android

public abstract static class Adapter<VH extends ViewHolder> {
    // encapsulates the build details of various ViewHolder, delaying implementation of the build details into subclasses
    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
}
Copy the code
  1. Abstract Factory pattern

What if you need to build a set of objects?

The abstract factory pattern handles this by encapsulating the details of building a set of objects in an interface:

// Abstract material factory (raw material builder)
public interface IngredientFactory{
    void Flour createFlour(a)void Sause createSause(a);
}

// Raw material user
public class Pizza{
    private Flour flour;
    private Sause sause;
    // Use the combination to hold the builder
    private IngredientFactory factory;
    
    // Inject a concrete builder
    // The same pizza may have different tastes in different regions
    // That is because although the same raw materials are used (abstract), the taste will be different when the origin is different (concrete).
    public Pizza(IngredientFactory factory){
        this.factory = factory;
    }
    
    // Build raw materials using specific factories (where polymorphisms occur)
    public void prepare(a){
        flour = factory.createFlour();
        sause = factory.createSause();
    }
    
    public void bake(a){}
    public void cut(a){}}// The specific factory
public class FactoryA implements IngredientFactory{
    public Flour createFlour(a){
        return new FlourA();
    }
    
    public Sause createSause(a){
        return newSauseA(); }}// When building pizza, pass it to the specific factory
public class PizzaStoreA extends PizzaStore{
    @Override
    protected Pizza createPizza(String type){
        Pizza pizza ;
        FactoryA factory = new FactoryA();
        if(type.equals("cheese")){
            pizza = new CheesePizza(factory);
        }else if(type.equals("bacon")){
            pizza = new BaconPizza(factory);
        }
        returnpizza ; }}Copy the code

If a new Pizza store opens in region B, just create a new FactoryB and define in it how the raw materials of region B are built, and then pass in the pizza class without modifying the pizza base class.

The differences between the abstract factory pattern and the factory method pattern are:

  1. The former is suitable for building multiple objects and using combinations.
  2. The latter applies to building individual objects and uses inheritance.

The strategy pattern

1. What is the change

For strategic patterns, change is a set of behaviors. Here’s an example:

public class Robot{
    public void onStart(a){
        goWorkAt9Am();
    }
    public void onStop(a){ goHomeAt9Pm(); }}Copy the code

The robot works at 9 o ‘clock every morning. Home at 9 p.m. The company has launched two new products, one that starts work at 8am and goes home at 9am. Another works at 9 am and gets home at 10 am.

Inheritance can solve this problem, but you need to create two new Robot subclasses, overriding onStart() in one subclass and onStop() in the other. If every behavior change is addressed by inheritance, the number of subclasses will grow (bloated subclasses). More importantly, adding subclasses is adding behavior at compile time. Is there a way to dynamically change behavior at run time?

2. How to cope with change

Dynamic modification can be achieved by encapsulating the changing behavior in the interface:

// Abstract behavior
public interface Action{
    void doOnStart(a);
    void doOnStop(a);
}

public class Robot{
    // Use combinations to hold abstract behavior
    private Action action;
    // Change behavior dynamically
    public void setAction(Action action){
        this.action = action;
    }
    
    public void onStart(a){
        if(action! =null){ action.doOnStart(); }}public void onStop(a){
        if(action! =null){ action.doOnStop(); }}}// Concrete behavior 1
public class Action1 implements Action{
    public void doOnStart(a){
        goWorkAt8Am();
    }
    public void doOnStop(a){ goHomeAt9Pm(); }}// Concrete behavior 2
public class Action2 implements Action{
    public void doOnStart(a){
        goWorkAt9Am();
    }
    public void doOnStop(a){ goHomeAt10Pm(); }}// Inject specific behavior into the behavior consumer (dynamic change at runtime)
public class Company{
    public static void main(String[] args){
        Robot robot1 = new Robot();
        robot1.setAction(new Action1());
        robot1.setAction(newAction2()); }}Copy the code

The policy pattern isolates the specific behavior from the users of the behavior, with the benefit that when the behavior changes, the users of the behavior do not need to change.

Various listeners in Android use the policy mode, such as view.setonClickListener (), but the following one is more of an observer mode. The difference is that the policy mode is intended to dynamically replace the behavior. The second call to setOnClickListener(), The previous behavior is replaced, and the observer mode is to dynamically add observers:

public class RecyclerView{
    // Use combinations to hold abstract scrolling behavior
    private List<OnScrollListener> mScrollListeners;
    
    // Abstract scrolling behavior
    public abstract static class OnScrollListener {
        public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
        public void onScrolled(RecyclerView recyclerView, int dx, int dy){}}// Dynamically modify the scrolling behavior
    public void addOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners == null) {
            mScrollListeners = new ArrayList<>();
        }
        mScrollListeners.add(listener);
    }
    
    // Use the scrolling behavior
    void dispatchOnScrollStateChanged(int state) {
        if(mLayout ! =null) {
            mLayout.onScrollStateChanged(state);
        }
        onScrollStateChanged(state);

        if(mScrollListener ! =null) {
            mScrollListener.onScrollStateChanged(this, state);
        }
        if(mScrollListeners ! =null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onScrollStateChanged(this, state); }}}}Copy the code

Lists behave differently after scrolling, so use abstract classes to encapsulate them (which are really the same as interfaces).

A real-world application of the strategy mode can be found here

Template method pattern

1. What is the change

Strictly speaking, template method patterns do not fit the opening sentence: “They add a layer of abstraction to encapsulate change, then program against the abstraction and use polymorphism to cope with change.” To say so is to emphasize that the purpose is to “cope with change.” But the purpose of the template approach is more like a “reuse algorithm,” although it also has an element of coping with change.

For a template method pattern, a change is a step in the algorithm. For example:

public class View{
    public void draw(Canvas canvas) {...// skip step 2 & 5 if possible (common case)
        if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
            if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if(mOverlay ! =null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            / / we 're done...
            return; }... }protected void dispatchDraw(Canvas canvas) {}}Copy the code

Draw () defines an algorithm framework for drawing. It consists of seven steps, all of which are abstracted into a method. The difference is that each step may be different for different types of views. So in order for different views to reuse this algorithm framework, we define it in a superclass, and the subclass can override one of the steps to define different behaviors.

The template method pattern is a common refactoring method, which abstracts the common logic of subclasses into the parent class, and designs the subclass-specific child logic into abstract methods for subclasses to override.

2. Contrast

  • Template method pattern vs. factory method pattern

At the code level, template methods are implemented in much the same way as the factory method pattern, by overwriting methods of the parent class by subclasses. The only difference is that methods in the parent class of the factory method pattern must be abstract, that is, enforce the subclass implementation because the subclass cannot work without implementing the parent. Methods in the parent class of the template method pattern can be non-abstract, which means that the parent class can work without the implementation of the subclass. The method to realize the hollow in the parent class has a special name is the “hook” (hook), the existence of the hook, can let a subclass has the ability to the process control of the algorithm, such as ViewGroup. OnInterceptTouchEvent ().

  • Template method pattern vs. strategy pattern

In terms of output, the template method pattern and the strategy pattern are in the same camp because they both produce a set of behaviors (algorithms), whereas the factory pattern produces an object. But they have different control over the algorithm. The strategy pattern can easily replace the entire algorithm, while the template method pattern can only replace a certain step in the algorithm. At the code level, they are implemented differently, with the policy pattern using composition and the template method pattern using inheritance. Composition is more elastic than inheritance because it can dynamically replace behavior at run time.

subsequent

This series will continue to share understanding of other design patterns.

Head First is a book that has updated my understanding of design patterns and is highly recommended (the factory pattern example in this article is taken from it).