C1 strategy mode of HF design mode

1 Analog duck system

In the application of a simulated duck game, there are several kinds of ducks, including green headed duck, red headed duck, rubber duck, wood duck, etc. each kind of duck has its own unique behavior. How to design this system and ensure that the system has good scalability and maintainability when more ducks join in the future?

1.1 rough use inheritance completion

For each kind of duck, let's first think about their characteristics:

weight
 height
...

Flying behavior, some ducks can fly, some ducks can't...
Some ducks quack, some squeak, and some can't...
Description of appearance,(Green head, slender)mallard,(Plastic, cute)Model duck...
...

Whether it's a mallard, a red headed duck or a model duck, they are all ducks in nature. Naturally, we think of using inheritance to solve this problem:

Duck parent class:

/**
 * @author A
 * @date 2021/3/1 - 10:17
 * @function Parent of all ducks
 * The behavior method of the duck in the parent class waits to be overridden
 */
public class Duck {
    protected int weight;
    protected int height;

    public Duck(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

    public void fly() {}

    public void quack() {}

    public void show() {}

}

MallardDuck green headed ducks:

/**
 * @author A
 * @date 2021/3/1 - 10:23
 * @function mallard
 */
public class MallardDuck extends Duck {

    public MallardDuck(int weight, int height) {
        super(weight, height);
    }

    @Override
    public void fly() {
        System.out.println("Can fly, fly very low");
    }

    @Override
    public void quack() {
        System.out.println("Quack");
    }

    @Override
    public void show() {
        System.out.println("Green head, slender");
    }
}

ModelDuck model duck class:

/**
 * @author A
 * @date 2021/3/1 - 10:25
 * @function Model duck
 */
public class ModelDuck extends Duck {

    public ModelDuck(int weight, int height) {
        super(weight, height);
    }

    @Override
    public void fly() {
        System.out.println("Can't fly");
    }

    @Override
    public void quack() {
        System.out.println("Can't call");
    }

    @Override
    public void show() {
        System.out.println("Plastic, cute");
    }
}

Test:

1.2 problems after using inheritance

Although using inheritance can roughly complete the simulated duck system, with the system iteration, we need to add swimming, eating, rest and other methods to all ducks

Naturally, I want to add a new method to the de parent Duck:

However, after adding methods to the parent class, our ModelDuck can directly call the swim() method!


This is obviously unreasonable and shouldn't happen. The direct solution is to rewrite the parent method in ModelDuck. Printing won't swim, or directly change the permission of swim to private. However, with the upgrade iteration of the system, when we need to add or modify a method in the parent Duck, all subclasses rewrite the parent method thousands of times, which is meaningless

Although inheritance can retain "good things", it affects the whole body

1.3 software is constantly changing

No matter how good the original software design is, it always needs to grow and change after a period of time, otherwise the software will become a pile of meaningless fragments

There are many factors driving change:

1,Database replacement
2,Customers need new features
3,The project needs to be migrated to another platform
4,Competing with competitive products requires iterative upgrading
5,In order to face more customers, the code needs to be refactored
6,New technologies have emerged that can make the original code better
7,Limited budget, unable to rent a large number of high-quality servers, can only be optimized in the code

Before and after the completion of software development, the "after" requires more energy and time. We need to spend a lot of time on system maintenance and change, which is more than the time required for the original development. Therefore, we should devote ourselves to improving maintainability and scalability in the design

Back to the problem of simulating duck system, inheritance can not solve the problem well, because the subclasses of ducks are constantly changing

In addition to inheritance, I naturally think of the interface. Before using the interface to complete the problem, I should learn a design principle

Design principles:
Find out the possible changes in the application and separate them
 Don't put it with code that doesn't need to change

According to the design principle, the change of one part of the system will not affect other parts, which will reduce the unintended consequences caused by code change and make the system more flexible

1.4 use the interface to complete the problem

Review the design principles just now, separate the unchanged from the changed, think about the simulated duck system again, and then introduce a new design principle:

Design principles:
Programming for interfaces, not for implementations
unchanged:
1,weight
2,height

Change:
1,Flight behavior
2,Chirping behavior
3,Appearance description

For the above changed behavior, the interface is used to deal with it. For flight, a unified flight interface FlyBehavior is used, and several specific flight modes are used to realize the FlyBehavior interface

FlyBehavior:

/**
 * @author A
 * @date 2021/3/1 - 11:19
 * @function Flight behavior interface
 */
public interface FlyBehavior {

    void fly();
}

Specific flight categories:

/**
 * @author A
 * @date 2021/3/1 - 11:21
 * @function
 */
public class CantFly implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("Can't fly");
    }
}

/**
 * @author A
 * @date 2021/3/1 - 11:21
 * @function
 */
public class FlyHigh implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("Can fly very high");
    }
}

We still create a parent class, which only contains "constant" weight and height, and can "dynamically" fly or call according to the characteristics of the child class

Duck parent class:

/**
 * @author A
 * @date 2021/3/1 - 11:18
 * @function Parent of all ducks
 */
public class Duck {
    protected int weight;
    protected int height;

    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public Duck(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

    public void fly() {
        this.flyBehavior.fly();
    }

    public void quack() {
        this.quackBehavior.quack();
    }
}

MallardDuck:

/**
 * @author A
 * @date 2021/3/1 - 11:24
 * @function mallard
 */
public class MallardDuck extends Duck {

    public MallardDuck(int weight, int height) {
        super(weight, height);
        this.flyBehavior = new FlyHigh();
        this.quackBehavior = new Quack();
    }

}

ModelDuck:

/**
 * @author A
 * @date 2021/3/1 - 11:33
 * @function Model duck
 */
public class ModelDuck extends Duck {

    public ModelDuck(int weight, int height) {
        super(weight, height);
        this.flyBehavior = new CantFly();
        this.quackBehavior = new CantQuack();
    }
}

We regard the interface as a member. When producing concrete objects, we can dynamically generate ducks with different characteristics by changing the values of these special members in the constructor


The two objects in the above code call the quack() and fly() methods in the parent class, and the parent class does not execute directly, but delegates behavior class processing

For this implementation, for the FlyBehavior interface, we can create many implementation classes to simulate the different flight behavior of ducks. When generating a duck object, the behavior characteristics of the duck are determined in the constructor, so that the behavior of ducks can be simulated through the adjusted methods in the parent class

In this way, various duck behaviors can be separated from ducks. The implementation classes of FlyBehavior can also complete code reuse, avoid repeated code, and add / replace some behaviors. The change of these behaviors is particularly simple in this way, which not only makes use of the "reuse" of inheritance, but also avoids the burden of inheritance

1.5 replace behavior with set method

Although the above design makes the whole system elastic, there is a defect. The description of behavior characteristics is placed in the constructor. Once the object is generated, the object cannot be changed

However, considering the following scenarios, I want to generate two mallards. Mallard A is healthy and mallard B is incomplete. Mallard a can fly high and call, while mallard B can't fly and can only call

However, the previous code of the mallard class has been written. The default structure generates a mallard that can fly and call. It is impossible to generate a class for B and write its default structure. This is the demand for the replacement behavior of similar objects. Naturally, it comes up with the set method

Let's go back to Duck and add set methods for members of two interface types:

/**
 * @author A
 * @date 2021/3/1 - 11:18
 * @function Parent of all ducks
 */
public class Duck {
    protected int weight;
    protected int height;

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public Duck(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

    public void fly() {
        this.flyBehavior.fly();
    }

    public void quack() {
        this.quackBehavior.quack();
    }
}

With the set method, you can generate a duck object and replace its behavior:

1.6 multi use combination and less inheritance


For each class of objects, we call them "ready to implement" a family of algorithms

In the system of simulating ducks, the combination mode is adopted. Each duck has FlyBehavior and QuackBehavior. The combination of these two classes is called combination

Here again, a design principle is introduced:

Design principles:
Use more combination and less inheritance

The system based on combination has great flexibility. It can not only encapsulate the algorithm family into classes, but also change its characteristics after generating objects

1.7 strategy mode

Strategy pattern: define algorithm families and package them separately so that they can be replaced with each other. This pattern makes the change of algorithm independent of the customers using the algorithm

That is to design an interface for a function that may change, expand a series of implementation classes for the interface, and give the object unique features when generating the object, so that the implementation of the function can be separated from the object, and the feature can be replaced to reduce coupling and improve code reuse

1.8 about design mode

We use a lot of Java API s in the development process, but the library and framework can not help us organize the application into an easy to understand, easy to maintain and elastic structure. Therefore, design patterns are needed. After learning design patterns, we should adopt them in the new design, or use design patterns to reconstruct chaotic old code

Knowing abstraction, inheritance and polymorphism does not mean understanding design patterns. Design patterns are concerned with building flexible designs that can be maintained and cope with changes. Good designs must meet the following requirements: reusable, extensible and maintainable

Posted by ironman32 on Thu, 14 Apr 2022 07:08:39 +0930