This is the 21st day of my participation in the August More Text Challenge


1. N log recording modes

Students who have maintained online systems should know the importance of logs, which can help us quickly locate and solve problems when the system is abnormal. The way to record logs varies according to different environments. Here are three cases:

  1. Development environment, console output log.
  2. In an online environment, logs are persisted to disk files for troubleshooting.
  3. Logs need to be stored in a database for offline analysis.

We tried to describe this process in code, and the class diagram was designed as follows:

Write Logger interface, define log has the function:

interface Logger {

	void log(String s);
}
Copy the code

There are three ways to log an implementation class:

// Pure console output log
class ConsoleLogger implements Logger {

    @Override
    public void log(String s) { System.out.println(s); }}// Disk files record logs
class FileLogger implements Logger {
    @Override
    public void log(String s) {
        // Append logs to filesFileUtil.appendToFile(s, File); }}// The database records logs
class DbLogger implements Logger {
    @Override
    public void log(String s) {
        // Write to the database
        DBUtil.insert(...);
    }
}
Copy the code

Define the LogContext LogContext object:

class LogContext {
    Logger console = new ConsoleLogger();
    Logger file = new FileLogger();
    Logger db = new DbLogger();

    public void doSomething(int type, String arg) {
        if (type == 1) {
            console.log(arg);
        } else if (type == 2) {
            file.log(arg);
        } else if (type == 3) { db.log(arg); }}}Copy the code

OK, that’s done, now let’s go back to the code, any questions? LogContext relies heavily on various logging implementation classes and has a large number of if branches. If you want to store logs to ElasticSearch in the future, you will need to modify the LogContext code and increase the branches, which will make the code less readable and violate the “open closed principle”.

The LogContext should rely only on Logger abstraction and only log, but it doesn’t care how the log is logged.

LogContext is modified as follows:

class LogContext {
    private Logger logger;
    
    // omit the set method

    public void doSomething(String arg) { logger.log(arg); }}Copy the code

The client calls like this:

main(String[] args) {
    LogContext logContext = new LogContext();
    logContext.setLogger(new ConsoleLogger());
    logContext.doSomething("console");
}
Copy the code

The LogContext becomes very simple, eliminating the if branch, relying only on Logger abstraction, not logging implementation, and it doesn’t need to change even if a logging policy is added later. This is strategic mode!

2. Definition of policy pattern

Define a set of algorithms, encapsulate each algorithm, and make them interchangeable.

Policy pattern generic class diagram

  • Context: Encapsulates the Context role to prevent high-level modules from having direct access to algorithmic policies.
  • Strategy: Defines the functions of all policies.
  • ConcreteStrategy: ConcreteStrategy, real logic implementer.

The definition of the policy pattern is very simple and clear, defining a set of algorithms: the different implementation logic of three types of logging is a set of algorithms. Make them interchangeable: of course interchangeable, Context only depends on policy abstraction, concrete policies can be changed at any time, Context doesn’t care.

3. Advantages and disadvantages of the strategic model

advantages

  1. Policy can be switched freely, encapsulation class does not change, in line with the open and closed principle.
  2. Eliminate if branch judgments to improve code readability.
  3. It scales well and only needs to be derivedStrategySubclasses can add a policy.

disadvantages

  1. Each policy requires a new class, which tends to inflate the number of classes.
  2. All policies need to be exposed to the client so that the client can choose one of them.

If you find that one part of your system’s algorithmic logic needs to be switched frequently, or that the algorithmic logic may change later, you can consider using policy mode. For example, with the SMS function, you can define two policies:

  1. Call the third-party SDK to send SMS and deduct the fee.
  2. Only output SMS to the console, not send.

In order to save money, you can use strategy two for your development environment and strategy one for your online environment. You don’t want to have to change all the code that calls SMS every time you switch policies. Use Spring’s dependency injection feature to change the configuration of your development environment and online environment.

4. Policy enumeration

An extended implementation of the policy pattern is called “policy enumeration.” The log policy can be optimized as follows:

enum LoggerEnum implements Logger {
    CONSOLE(){
        @Override
        public void log(String s) {console output log}}...... }Copy the code

Very simple and clear, fully object-oriented operation.

5. To summarize

The policy pattern is very simple and widely used, and it doesn’t have much to offer except for Java’s “inheritance” and “polymorphism” features. The strategy mode can separate the “variable and unchanged” part of the program, and abstract out the volatile algorithm strategy. The context only depends on its abstraction, but does not care about implementation, so as to improve the stability of the system. One of the biggest disadvantages of the policy pattern is that all policies must be exposed to the client so that the client can choose a specific policy to use. When there are too many policies, the client call becomes confused and does not know which policy to use, which can be optimized using factory mode or share mode.