This is the sixth day of my participation in Gwen Challenge

Make it a habit to like it first

preface

I often see articles about design patterns, with a lot of content and some “vivid” examples.

But you might have the same problem with Head First: Read it, I know it, but I can’t seem to use it? Or hard design patterns.

A few extreme examples I’ve seen:

  1. Two fields, also need a Builder
  2. Three ifs and a strategy mode
  3. Five lines of code is very simple to initialize, also make a Factory

As to why this is a problem… Let me give you my opinion

why

Most r&d people do business function development, also known as CRUD. The COMPLEXITY of CRUD is different for different business scenarios.

For business code, however, design patterns are often not well applied or applied.

Usually see the most is the strategy mode of the article, why?

I guess because it’s easier to write, it’s easier to apply in business code; In any scenario that is slightly more complex, you can use the policy pattern to split multiple Ifs into multiple classes.

Indeed, proper use of policy patterns in business code can reduce complexity; Don’t change the if mode to policy mode in any business, or the code will explode…

I saw a project before, although it was an internal XX system, the guy who developed it may have gone crazy. More than 80 policy classes were selected, which were divided into dozens of groups, each group of seven or eight policy classes, but the code in each class was no more than a dozen or twenty lines, and there were repeated codes.

I asked him, “Are you pranking?”

This developer is a counterexample to the abuse of the design pattern by over-fitting all branches of code into the design pattern. But I suppose he did it to learn, to put it to use…

Other patterns, such as delegation-agent state, are more difficult to apply to business code because there are not as many suitable scenarios. But the strategy pattern is different. Try it wherever there is an IF…

But design patterns are designed to solve problems and reduce/transfer complexity, not increase it.

Design patterns in non-business code

Out of business code, even out of pure business code, it’s easier to apply design patterns. You don’t even have to apply them. When you encounter problems, you’ll naturally want to use design patterns.

Take a chestnut

Generally, a traceId/requestId is required to connect the entire link for log printing or centralized APM extraction.

For individual applications, the MDC of the logging framework is used to bind the traceId. In Filter or some AOP, give the MDC a traceID, and the entire invocation link can use that ID, so that the log can be printed to distinguish requests based on the traceID, like this:

2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - Request step 0 2021-06-10 18:31:44.227 [ThreadName] [000] INFO LoggerName - Step 1 2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - Step 2 2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - Request Step 0 2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - Request step 1 2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - Request Step 2...Copy the code

The 000/111 traceId identifies the request.

MDC stores data using ThreadLocal, which is, after all, tied to threads. What if thread pool processing is used in the link? When a sub-thread in the thread pool prints logs, the MDC node cannot obtain the traceId of the main thread. However, for this request, the primary and sub-threads are all the same link……

Remember that sentence?

Any problem in computer science can be solved by adding an indirect intermediate layer.”

The problem is solved by adding an intermediate layer to the delegate pattern.

Since it is a data transfer problem for the master child thread, it is only necessary to take the traceId from the main MDC thread and pass it to the new child thread, like this:

public class MDCDelegateRunnable implements Runnable{

    private Runnable target;

    private String traceId;

    public MDCDelegateRunnable(Runnable target, String traceId) {
        this.target = target;
        this.traceId = traceId;
    }

    @Override
    public void run(a) {
        MDC.put("traceId",traceId);
        target.run(a); MDC.remove("traceId"); }}Copy the code

And then a delegate thread pool, overriding the execute method. Wrap the original Runnable object in the thread pool as the MDCDelegateRunnable you just created, passing the traceId through the construct parameter

public class MDCDelegateExecutorService extends AbstractExecutorService {

    public MDCDelegateExecutorService(AbstractExecutorService target) {
        this.target = target;
    }

    private AbstractExecutorService target;

    @Override
    public void shutdown(a) {
        target.shutdown();
    }

    / /...

    @Override
    public void execute(@NotNull Runnable command) {
        target.execute(new MDCDelegateRunnable(command, MDC.get("traceId"))); }}Copy the code

Done, let’s test it out:

public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
    MDC.put("traceId"."111");
    new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).execute(new Runnable() {
        @Override
        public void run(a) {
            System.out.println("runnable: "+MDC.get("traceId")); }}); Future<String> future =new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).submit(new Callable<String>() {
        @Override
        public String call(a) throws Exception {
            return MDC.get("traceId"); }}); System.out.println("callable: "+future.get());

    System.in.read();
}

//output
runnable: 111
callable: 111
Copy the code

Perfect, the troublesome traceId delivery problem is now solved with a simple delegate pattern. You don’t change the caller code, and you don’t break the thread pool code.

Delegate mode in the JDK

Remember how to create a single-threaded thread pool with Executors#newSingleThreadExecutor. When do you need a single-threaded thread pool?

For example, if I just need an asynchronous operation and get the return operation, it is not convenient to get the return value directly from the new thread start. If I use the thread pool Callable/Runnable + Future, it is convenient:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future<String> future = executorService.submit(new Callable<String>() {
    @Override
    public String call(a) throws Exception {
        // do sth...
        returndata; }}); String data = future.get(); executorService.shutdown();Copy the code

For single-threaded asynchronous scenarios, you don’t even need to maintain a singleton thread pool, just new/shutdown every time. But I have a single thread, so I need to shutdown every time. If I forget to shutdown somewhere, I’ll be dead.

The JDK designers had this problem in mind, and they’ve already solved it. Similar to the above example, this problem can be solved perfectly with a simple delegate pattern:

public static ExecutorService newSingleThreadExecutor(a) {
    / / create FinalizableDelegatedExecutorService delegate class
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// In the delegate class, the shutdown method of the thread pool object entrusted by Finalize automatically closes the thread pool
static class FinalizableDelegatedExecutorService
    extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    protected void finalize(a) {
        super.shutdown(); }}// Common abstract delegate thread pool...
static class DelegatedExecutorService extends AbstractExecutorService {
    private final ExecutorService e;
    
    DelegatedExecutorService(ExecutorService executor) { e = executor; }
    
    public void execute(Runnable command) { e.execute(command); }
    
    public void shutdown(a) { e.shutdown(); }
    / /...
}
Copy the code

So you don’t even need to display shutdown when using newSingleThreadExecutor…

Note: Although the JDK closed it for us… However, it is recommended to manually shutdown the JDK as it is designed to be stupid. if you forget the JDK, you can shut it down automatically to avoid leakage

conclusion

Combined with the above two examples, isn’t it easy to apply design patterns once you step outside of business code? You don’t even have to stick to design patterns. When you have a problem, you’ll want to use design patterns to solve the problem, not to use design patterns to feed crap into your code.

In pure business code, the benefits of proper splitting and keeping the code clean and readable are far greater than a collection of design patterns

To repeat: Design patterns are designed to solve problems and reduce/transfer complexity, not add complexity

The above is only my personal opinion, if you have different opinions, welcome to leave a message in the comment section

Original is not easy, prohibit unauthorized reprint. Like/like/follow my post if it helps you ❤❤❤❤❤❤