Design Patterns in Spring Framework (5)

Through the previous four articles, we saw that Spring employs a number of object creation and structural design patterns. Most of these are not behavioral design patterns. As the last article in this series about Spring design patterns, this article will talk about two behavioral design patterns.

In this article we will talk about two behavioral design patterns: the command pattern and the visitor pattern.

Spring Design Patterns - Command Patterncommand

The first mode we will cover in this article is the command mode. It allows to encapsulate a request ( request) into an object and associate it with a callback action. The request( request) is encapsulated in the command( command) object, and the result of the request is forwarded to the receiver( receiver). The command is not executed by itself but by a caller ( invoker). To better understand the gist of this line of thinking, imagine a scenario where you manage a server. The administrator ( ) initiates certain operations invokeron the command line ( ), and the results of these command lines are transmitted to the managed server ( ). The thing to thank here is the terminal, which can be thought of as a client here. Let's write this example as a JUnit test case:commandreceiver

public class CommandTest {

  // This test method is a client
  @Test
  public void test() {
    Administrator admin = new Administrator();
    Server server = new Server();

    // start Apache
    admin.setCommand(new StartApache(server));
    admin.typeEnter();

    // start Tomcat
    admin.setCommand(new StartTomcat(server));
    admin.typeEnter();

    // check executed commands
    int executed = server.getExecutedCommands().size();
    assertTrue("Two commands should be executed but only "+
      executed+ " were", executed == 2);
  }

}

// commands
abstract class ServerCommand {

  protected Server server;

  public ServerCommand(Server server) {
    this.server = server;
  }

  public abstract void execute();
}

class StartTomcat extends ServerCommand {

  public StartTomcat(Server server) {
    super(server);
  }

  @Override
  public void execute() {
    server.launchCommand("sudo service tomcat7 start");
  }
}

class StartApache extends ServerCommand {

  public StartApache(Server server) {
    super(server);
  }

  @Override
  public void execute() {
    server.launchCommand("sudo service apache2 start");
  }
}

// invoker
class Administrator {

  private ServerCommand command;

  public void setCommand(ServerCommand command) {
    this.command = command;
  }

  public void typeEnter() {
    this.command.execute();
  }

}

// receiver
class Server {

  // as in common terminals, we store executed commands in history
  private List<String> executedCommands = new ArrayList<String>();

  public void launchCommand(String command) {
    System.out.println("Executing: "+command+" on server");
    this.executedCommands.add(command);
  }

  public List<String> getExecutedCommands() {
    return this.executedCommands;
  }

}

This test case should pass and output two commands:

Executing: sudo service apache2 start on server
Executing: sudo service tomcat7 start on server

The command mode can not only encapsulate the request request(ServerCommand) and send it to the receiver receiver(Server), but also better handle the request. In the above example, a history of command executions can be considered a better handling of the request. In Spring, we extract the command design pattern into the bean factory post processor (beans factory post processor). These post-processors are executed by the application context at bean creation time to make some modifications to the bean (if you want to learn more about post-processors, see the article on bean factory post-processors ).

When we switch from the command mode logic above to Spring's bean factory post-processor, we can observe the following role-playing: the post-processor bean, which is BeanFactoryPostProcessorthe implementation class of the interface, acts as the command commandand org.springframework.context.support.PostProcessorRegistrationDelegateis the caller invoker
(he will execute the methods of all registered post-processor beans postProcessBeanFactory), and the receivers receiverare org.springframework.beans.factory.config.ConfigurableListableBeanFactoryits elements, that is, individual beans will be modified when they are constructed (eg: bean properties can be modified before the bean instance is initialized).

In addition, in our JUnit example, we also demonstrate "better handling" of requests, which is to do a command execution history. Similarly, PostProcessorRegistrationDelegateit contains an inner class BeanPostProcessorCheckerthat will log when a bean does not meet the conditions to be processed.

Spring Design Patterns - Visitor Patternvisitor

The second mode we talk about the visitor mode visitor. The idea behind this pattern is that it makes an object accessible to other types of objects. After this brief definition, you might be able to imagine that objects using this pattern would be called visitors ( visitor) or accessible objects ( object visitable), the former (visitors) accessing the latter (accessible objects). , a real-world example of this mode is a mechanic who would inspect car parts such as tires, brakes, and engine, and then conclude whether the car still works. Let's take a look at this example using JUnit:

public class VisitorTest {

  @Test
  public void test() {
    CarComponent car = new Car();
    Mechanic mechanic = new QualifiedMechanic();
    car.accept(mechanic);
    assertTrue("After qualified mechanics visit, the car should be broken",
      car.isBroken());
    Mechanic nonqualifiedMechanic = new NonQualifiedMechanic();
    car.accept(nonqualifiedMechanic);
    assertFalse("Car shouldn't be broken becase non qualified mechanic " +
      " can't see breakdowns", car.isBroken());
  }

}

// visitor
interface Mechanic {
  public void visit(CarComponent element);
  public String getName();
}

class QualifiedMechanic implements Mechanic {

  @Override
  public void visit(CarComponent element) {
    element.setBroken(true);
  }

  @Override
  public String getName() {
    return "qualified";
  }
}

class NonQualifiedMechanic implements Mechanic {

  @Override
  public void visit(CarComponent element) {
    element.setBroken(true);
  }

  @Override
  public String getName() {
    return "unqualified";
  }
}

// visitable
abstract class CarComponent {
  protected boolean broken;

  public abstract void accept(Mechanic mechanic);

  public void setBroken(boolean broken) {
    this.broken = broken;
  }

  public boolean isBroken() {
    return this.broken;
  }
}

class Car extends CarComponent {

  private boolean broken = false;
  private CarComponent[] components;

  public Car() {
    components = new CarComponent[] {
      new Wheels(), new Engine(), new Brake()
    };
  }

  @Override
  public void accept(Mechanic mechanic) {
    this.broken = false;
    if (mechanic.getName().equals("qualified")) {
      int i = 0;
      while (i < components.length && this.broken == false) {
        CarComponent component = components[i];
        mechanic.visit(component);
        this.broken = component.isBroken();
        i++;
      }
    }
    // if mechanic isn't qualified, we suppose that 
    // he isn't able to see breakdowns and so 
    // he considers the car as no broken 
    // (even if the car is broken)
  }

  @Override
  public boolean isBroken() {
          return this.broken;
  }
}

class Wheels extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

class Engine extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

class Brake extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

In this example, we see that there are two mechanic Mechanic(visitors): a qualified mechanic and an unqualified mechanic. The accessible object they see is the car Car. Use the cart acceptmethod to decide which strategy to apply to the visitor. When the visitor is qualified, Carhe is allowed to analyze all the parts. And if the visitor is unqualified, Carit will consider the visitor's intervention invalid, refuse it to analyze the various components, and finally isBroken()return one through the method false. Spring uses the visitor design pattern in bean configuration. To see this, we can look at the objects used to parse bean metadata and turn them into strings (example: scope or factory method names in XML attributes) or objects (example: parameters in constructor definitions org.springframework.beans.factory.config.BeanDefinitionVisitor) . The processed value will be set to the BeanDefinitioninstance corresponding to the analyzed bean. To see how it works, see BeanDefinitionVisitora code snippet below:

/**
 * Traverse the given BeanDefinition object and the MutablePropertyValues
 * and ConstructorArgumentValues contained in them.
 * @param beanDefinition the BeanDefinition object to traverse
 * @see #resolveStringValue(String)
 */
public void visitBeanDefinition(BeanDefinition beanDefinition) {
  visitParentName(beanDefinition);
  visitBeanClassName(beanDefinition);
  visitFactoryBeanName(beanDefinition);
  visitFactoryMethodName(beanDefinition);
  visitScope(beanDefinition);
  visitPropertyValues(beanDefinition.getPropertyValues());
  ConstructorArgumentValues cas = beanDefinition.
    getConstructorArgumentValues();
  visitIndexedArgumentValues(cas.
    getIndexedArgumentValues());
  visitGenericArgumentValues(cas.
    getGenericArgumentValues());
}

protected void visitParentName(BeanDefinition beanDefinition) {
  String parentName = beanDefinition.getParentName();
  if (parentName != null) {
    String resolvedName = resolveStringValue(parentName);
    if (!parentName.equals(resolvedName)) {
      beanDefinition.setParentName(resolvedName);
    }
  }
}

In this example, there are only access methods, and there is no access control mechanism for qualified/unqualified mechanic similar to our mechanic example above. Here the visitor's access parses the parameters of the given bean definition BeanDefinitionobject and replaces it with the resolved object.

Summarize

In this article, the last in the series on Spring design patterns, we talked about two behavioral design patterns: the command design pattern, which is used to post-process the bean factory; The parameters are converted into the parameters of the object field (string or object).

English original

Directory of articles in the series

Design Patterns in Spring Framework (5)
Design Patterns in Spring Framework (4)
Design Patterns in Spring Framework (3)
Design Patterns in Spring Framework (2) Design Patterns
in Spring Framework (1)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325641375&siteId=291194637
Recommended