This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

Actual Service Scenarios

Recently in the project need to develop a message platform, need according to the type value judgment to the corresponding business processing and the way the message is sent, which involves the repetition of the if – else problem, in order to solve such repetitive code to code redundancy problem, so consider using the strategy pattern, implement the corresponding strategy according to the specific scene, To solve the problem. This paper simply write a test Demo, the actual development needs to be based on the specific business, specific implementation of the corresponding business logic.

The specific implementation

When we encounter such logical processing, the first response is to do if… for type. else… Or the logical judgment of the switch, so as to distinguish different business logic processing.

Based on the if… else… The pseudo code

public int sendMessage(int type, Message message) {
  if (type == 1) {
    // omit business processing
    return mail.sendMessage(message); // Send an email
  } else if (type == 2) {
    // omit business processing
    return mobile.sendMessage(message); // Send SMS messages
  } else if (type == 3) {
    // omit business processing
    return app.sendSendMessage(message); // Perform app notification push
  } else if (type == 4) {// ...
  }
  / /...
}
Copy the code

Suppose the above code is used to send messages through multiple channels, choosing the sending mode according to different types. Of course, the real business scenario is not so simple to judge.

First consider the design pattern’s open and closed principle: open for extension, closed for modification.

The above code needs to be changed if a sending method is changed, or if a new sending method is added, the code also needs to be changed. Any change will inevitably affect the business logic in other ways. Completely inconsistent with the open and close principle, and with a lot of if… else… If the business is complex, the code expands rapidly.

So, let’s redesign with the policy pattern for the above example.

Pseudo-code based on policy patterns

First define an interface for sending messages, ISendMessageStrategy. In actual practice, interfaces or abstract classes can be used according to the specific situation.

public interface ISendMessageStrategy {

  /** * Send message **@paramMessage Indicates the content of the sent message */
  void sendMessage(Message message);
}
Copy the code

Provide a method in the interface that sends a message. Because it is an interface, the methods defined must be implemented by subclasses. The following is a concrete implementation of this interface. Different sending methods have different implementations.

/** ** The implementation of mail sending **/
@Slf4j
public class EmailSendMessageStrategy implemets ISendMessageStrategy {
    @Override
    public void sendMessage(Message message) {
        // omit business processingmail.sendMessage(message); }}/** ** the implementation of SMS sending **/
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
    @Override
    public void sendMessage(Message message) {
        // omit business processingphone.sendMessage(message); }}Copy the code

Let’s implement a role class, SendMessageHandler, that holds the interface ISendMessageStrategy

public class SendMessageHandler {

  /** * holds the policy abstract class */
  private ISendMessageStrategy sendMessageStrategy;

  // Inject through the constructor, or in other ways
  public SendMessageHandler(ISendMessageStrategy sendMessageStrategy) {
    this.sendMessageStrategy = sendMessageStrategy;
  }

  public void sendMessage(Message message) {
    returnsendMessageStrategy.sendMessage(message); }}Copy the code

Finally, how do we call this policy class

public class Test {
  public static void main(String[] args) {
    SendMessageHandler handler = new SendMessageHandler(new MobileSendMessageStrategy());
    handler.sendMessage(newMessage()); }}Copy the code

Use Spring to manage objects

The improvements above have avoided a lot of if… else… In this case, if the project uses Spring project, we will make further improvement. At this time, we will mainly use Spring’s @AutoWired annotation to inject the instantiated policy implementation class into a Map, and then we can easily get the service through key.

Start by instantiating the policy implementation class with the @Service annotation and specifying the name of the instantiation. Take the realization of SMS sending as an example:

@Component("mobileSendMessageStrategy")
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
    // ...
}
Copy the code

The other policy implementation classes are instantiated as above. Finally modify the environment role class to SendMessageHandler:

@Component
public class SendMessageHandler implements ApplicationContextAware {

    private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();

    public ISendMessageStrategy get(String beanName) {
        return strategyMap.get(beanName);
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws Exception { strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class); }}Copy the code

ApplicationContext. GetBeansOfType (ISendMessageStrategy. Class) will be in the container ISendMessageStrategy implementation classes (annotations @ Component) on the map. Where key is the name of the instantiated service specified in @Component and value is the corresponding object.

public class Test {
    @Resource
    private SendMessageHandler messageHandler;

    public void test(a) {
        messageHandler.get("mobileSendMessageStrategy").sendMessage(newMessage()); }}Copy the code

Further optimizations are made with enumerated classes

Let’s improve it further. Instead of calling the policy class’s methods in the policy role class, we have the policy role class as the factory’s role, returning the corresponding service. The invocation of the related service method is directly called by the client to implement the class method.

At the same time, we map the name and type of the service through enumeration. Let’s start by defining an enumerated class:

public enum TypeEnum {

  MAIL(0."mailSendMessageStrategy"."Email"),
  MOBILE(1."mobileSendMessageStrategy"."Text"),
  APP(2."appSendMessageStrategy"."app");

  TypeEnum(int type, String serviceName, String desc) {
    this.type = type;
    this.serviceName = serviceName;
    this.desc = desc;
  }

  public static TypeEnum valueOf(int type) {
    for (TypeEnum typeEnum : TypeEnum.values()) {
      if (typeEnum.getType() == type) {
        returntypeEnum; }}return null;
  }

  private int type;

  private String serviceName;

  private String desc;

  public int getType(a) {
    return type;
  }

  public String getServiceName(a) {
    return serviceName;
  }

  public String getDesc(a) {
    returndesc; }}Copy the code

Then modify the environment role class to SendMessageHandler:

@Component
public class SendMessageHandler implements ApplicationContextAware {

    private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();

    public ISendMessageStrategy get(int type) {
        TypeEnum typeEnum = TypeEnum.valueOf(type);
        return strategyMap.get(typeEnum.getServiceName());
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws Exception { strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class); }}Copy the code

Test the

public class Test {
    @Resource
    private SendMessageHandler messageHandler;

    public void test(a) {
        int type = 1;
        messageHandler.get(type).sendMessage(newMessage()); }}Copy the code

At this point, if you add a new algorithm, you can simply create the service for the algorithm and map the relationship in the enumeration class to scale without affecting the client invocation. Of course, further modifications can be made according to specific business scenarios.

conclusion

Design patterns can extend the code better, but also increase the cost of reading the code to some extent. Therefore, in the actual project, do not blindly pursue the design mode, we should combine the actual business situation to make a reasonable choice. When the judgment terms are fixed and small, if… else… It is also a more efficient and maintainable way.