Java Design Patterns (1/23): Strategy Patterns

definition

The Strategy mode defines a family of algorithms and encapsulates them separately so that they can be replaced with each other. This mode allows the changes of the algorithm to be independent of the customers who use the algorithm.

insert image description here

Case: Simulate Duck Application

at the beginning

insert image description here

New requirement: The simulation program needs a flying duck

Add a new fly() method to the parent class.
insert image description here


Disadvantage of this : not all subclasses of Duck can fly, such as rubber duck.

insert image description here

When it comes to maintenance , using inheritance for reuse purposes doesn't end well.


A remedy is to override the fly() method in the rubber duck class.

insert image description here


New Trouble : Added DecoyDuck class, which is a fake duck that can't fly or bark.

insert image description here

Leveraging inheritance to provide Duck's behavior leads to the following drawbacks:

  1. Code is repeated in multiple subclasses.
  2. The behavior at runtime cannot be easily changed.
  3. It is difficult to know the full behavior of all the ducks.
  4. Change can affect the whole body, causing changes that other ducks don't want.

It's an endless nightmare to be forced to check and possibly override fly() and quark() every time a new duck subclass comes along .


An improvement : fly() can be taken out and put into a Flyable interface . In this way, only flying ducks implement this interface. The same way, can also be used to design a Quackable interface , because not all ducks can bark.

insert image description here

Although Flyable and Quackable can solve some of the problems (there will be no more flying rubber ducks), but they make the code unreusable , which can only be regarded as jumping from one nightmare to another. Even, in a duck that can fly, there may be many variations of the flying action...

This means: whenever you need to modify a behavior, you have to track down and modify every class that defines this behavior, and if you are not careful, you may create new errors.

(MyNote: The default key of Java8 can have implementation code in the interface class.)

A beautiful vision : If there is a way to build software so that when we need to change the software, it is easy to spend less time redoing the code and let the program do more with the least impact on the existing code. Cooler thing. How good should it be...

One immutable truth of software development: change lasts forever

No matter how well designed the software is, after a while, it always needs to grow and change, or the software will die .

There are many factors driving change such as:

  • The customer or user needs something else, or wants a new feature.
  • The company decided to use another database product and also bought data from another vendor, which resulted in incompatible data formats.

Design Principle: Variation Separation

Find out where changes may be needed in your application, isolate them, and don't mix them with code that doesn't need to change.

Take out and encapsulate the parts that will change so that the other parts will not be affected. After code changes, surprises become less and the system becomes more resilient .

(MyNote: Focus on change.)

In other words, if every time a new requirement comes, it will change to a certain aspect of the code , then you can be sure that this part of the code needs to be extracted and differentiated from other unseen code.

Here's another way of thinking about this principle: take out and encapsulate the part that changes so that it can easily be extended later without affecting other parts that don't need to change .

Such a concept is simple and is the spirit behind almost every design pattern . All patterns provide a way to change one part of the system without affecting other parts .

Simulating Variation Separation in Duck Programs

The fly() and quack() in the Duck class will change from duck to duck.

To separate these two behaviors from the Duck class, we'll take them out of the Duck class and create a new set of classes representing each behavior.

insert image description here

Design duck two behaviors

We want everything to be flexible, we also want to be able to assign behaviors to instances of ducks, let's say we want to spawn mallard instances and assign specific types of flying behaviors to it.

Just let the duck's behavior change dynamically by the way . In other words, we should include methods to set the behavior in the duck class so that we can dynamically change the flight behavior of the mallard at runtime .

Design Principle: Program for the interface, not for the implementation.

We represent each behavior with an interface, say, FlyBehavior and QuackBehavior, and every implementation of the behavior must implement one of these interfaces. So this time the duck class will not be responsible for implementing this interface. Instead, other classes will specifically implement FlyBehavior and QuackBehavior. These other classes are called behavior classes. The behavior interface is implemented by the behavior class, not by the Duck class.

This approach is very different from the past, the previous approach was:

  1. The behavior is derived from the concrete implementation of the Duck superclass,

  2. Inherit an interface and implement it by subclasses.

Both of these approaches are implementation -dependent , and we are implementation-bound, with no way to change the behavior (except by writing more code).

In our new design, the duck subclasses will use the behavior represented by the interfaces (FlyBehavior and QuackBehavior), so the actual implementation is not tied to the duck subclasses. (In other words, the specific implementation code is in the specific class that implements FlyBehavior and QuakcBehavior).

insert image description here

Design Principle: Program for the interface, not for the implementation

Programming against interfaces really means programming against supertypes .

The so-called interface here has multiple meanings. The interface is

  • a concept,
  • A Java interface construct.

You can program against interfaces without involving Java interfaces. The key is polymorphism .

Using polymorphism , the program can be programmed for the supertype, and the real behavior will be executed according to the actual situation during execution, and will not be tied to the behavior of the supertype.

For supertype programming , it can be stated more clearly: the declared type of a variable should be a supertype, usually an abstract class or an interface , so as long as it is an object generated by a class that specifically implements this supertype , can be assigned to this variable; this also means that when declaring a class, don't care about the real object type when it is executed later!


Take a look at the following simple polymorphism example : Suppose there is an abstract class Animal, and there are two concrete implementations (Dog and Cat) that inherit Animal.

insert image description here

For the realization of programming, the practice is as follows:

Dog d = new Dog();
d.bark();  

Declaring the variable d as Dog type (which is the concrete implementation of Animal) will cause us to have to code for the implementation.

But for interface/supertype programming , the approach would be as follows:

Animal animal = new Dog();
animal.makeSound();

We know that the object is a dog, but we now make polymorphic calls using animal.

Even better, subtype instantiation actions no longer need to be hard-coded in the code, such as new Dog(), but instead specify the concrete implementation object at runtime.

a = getAnimal();
a.makeSound();

We don't know what the actual subtype is, we just care that it knows how to do makeSound() correctly.

(MyNote: There is nothing wrong with writing a specific implementation at the beginning of coding, and actively refactoring if there is a change in demand in the middle!)

Implement duck behavior

We have two interfaces, FlyBehavior and QuackBehavior, and their corresponding classes, responsible for implementing specific behaviors:

insert image description here

This design allows the actions of flying and croaking to be reused by other objects, because these behaviors have nothing to do with duck classes.

And we can add some behaviors, which will not affect the existing behavior classes, nor will it affect the duck classes that use the flight behavior.

In this way, there is the reuse benefit of inheritance without the burden of inheritance. (MyNote: Shun the brother's love without losing the sister-in-law's love)

Integrate duck behavior

The point is, ducks will now delegate the actions of flying and croaking to others , rather than using methods defined in their own class (or subclass).

insert image description here

Then implement performQuack() (similar to performFly())

public class Duck {
    
    
    QuackBehavior quackBehavior;
    
    // 还有更多
    public void performQuack() {
    
    
        //不亲自处理呱呱叫行为,而是委托给quackBehavior对象。
        quackBehavior.quack();
    }
}

Subclasses of Duck assign behavior variables in the constructor

public class MallardDuck extends Duck {
    
    
    public MallardDuck() {
    
    
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }
    public void display() {
    
    
    	System.out.println(I’m a real Mallard duck”);
    }
}

Grading over

duck abstract class

public abstract class Duck {
    
    
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;

	public Duck() {
    
    
	}

	public void setFlyBehavior(FlyBehavior fb) {
    
    
		flyBehavior = fb;
	}

	public void setQuackBehavior(QuackBehavior qb) {
    
    
		quackBehavior = qb;
	}

	abstract void display();

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

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

	public void swim() {
    
    
		System.out.println("All ducks float, even decoys!");
	}
}
trick duck
public class DecoyDuck extends Duck {
    
    
	public DecoyDuck() {
    
    
		setFlyBehavior(new FlyNoWay());
		setQuackBehavior(new MuteQuack());
	}
	public void display() {
    
    
		System.out.println("I'm a duck Decoy");
	}
}
mallard
public class MallardDuck extends Duck {
    
    

	public MallardDuck() {
    
    

		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();

	}

	public void display() {
    
    
		System.out.println("I'm a real Mallard duck");
	}
}
red head duck
public class RedHeadDuck extends Duck {
    
    
 
	public RedHeadDuck() {
    
    
		flyBehavior = new FlyWithWings();
		quackBehavior = new Quack();
	}
 
	public void display() {
    
    
		System.out.println("I'm a real Red Headed duck");
	}
}
rubber duck
public class RubberDuck extends Duck {
    
    
 
	public RubberDuck() {
    
    
		flyBehavior = new FlyNoWay();
		quackBehavior = new Squeak();
//		quackBehavior = () -> System.out.println("Squeak");
	}
	
	public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
    
    
		this.flyBehavior = flyBehavior;
		this.quackBehavior = quackBehavior; 
	}
 
	public void display() {
    
    
		System.out.println("I'm a rubber duckie");
	}
}

flight behavior interface

public interface FlyBehavior {
    
    
	public void fly();
}
no-flying behavior
public class FlyNoWay implements FlyBehavior {
    
    
	public void fly() {
    
    
		System.out.println("I can't fly");
	}
}
flight behavior
public class FlyWithWings implements FlyBehavior {
    
    
	public void fly() {
    
    
		System.out.println("I'm flying!!");
	}
}

call behavior interface

public interface QuackBehavior {
    
    
	public void quack();
}
behavior class
public class MuteQuack implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("<< Silence >>");
	}
}
quack behavior
public class Quack implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("Quack");
	}
}
squeaking behavior
public class Squeak implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("Squeak");
	}
}

run test class

public class MiniDuckSimulator {
    
    
 
	public static void main(String[] args) {
    
    
		Duck mallard = new MallardDuck();
		mallard.performQuack();
		mallard.performFly();
   
		System.out.println("---");
		
		Duck model = new RedHeadDuck();
		model.performFly();
        //改变飞行行为
		model.setFlyBehavior(new FlyNoWay());
		model.performFly();
	}
}

operation result:

I'm Quack
I'm flying!!
---
I'm flying!!
I can't fly

This example class and interface family portrait

insert image description here

Design principle: use more composition, less inheritance

According to this example, the use of combination to build a system has great flexibility, not only can the algorithm family be encapsulated into a class, but also can dynamically change the behavior at runtime, as long as the combined behavior object conforms to the correct interface standard.

References

  1. Head First Design Pattern

Guess you like

Origin blog.csdn.net/u011863024/article/details/119792924
Recommended