Summary of learning and understanding of "The Beauty of Design Patterns"

Summary of learning and understanding of the beauty of design patterns

Overview:

The more abstract, the more top-level, and the more detached from a specific implementation design, the more flexible the code can be, and the better it can respond to future demand changes. A good code design can not only meet the current needs, but also be able to respond flexibly without destroying the original code design when the needs change in the future.

  1. Interface-based programming

1. "Programming based on interfaces rather than implementation", another way of expressing this principle is "programming based on abstraction rather than implementation". The latter way of expression actually better reflects the original design intention of this principle. When we do software development, we must have abstract awareness, packaging awareness, and interface awareness. The more abstract, the more top-level, and the more detached from a specific implementation design, the more flexibility, scalability, and maintainability of the code can be improved.


2. When we define an interface, on the one hand, the naming must be general enough not to contain words related to specific implementations; on the other hand, methods related to specific implementations should not be defined in the interface.


3. The principle of "programming based on interfaces rather than implementation" can not only guide very detailed programming development, but also guide higher-level architecture design and system design. For example, the "interface" design between the server and the client, and the "interface" design of the class library.

  1. How to achieve "open for extension, close for modification"?

We must always have the awareness of expansion, abstraction, and encapsulation. When writing code, we need to spend more time thinking about what requirements may change for this code in the future, how to design the code structure, and reserve extension points in advance so that when future requirements change, the overall structure of the code will not be changed. , In the case of minimal code changes, the new code can be flexibly inserted into the extension point.


Many design principles, design ideas, and design patterns are aimed at improving the scalability of the code. In particular, the 23 classic design patterns, most of which are summed up to solve the problem of code scalability, are all guided by the principle of opening and closing. The methods most commonly used to improve code extensibility are: polymorphism, dependency injection, programming based on interfaces rather than implementations, and most design patterns (eg, decorations, strategies, templates, chain of responsibility, state).

  1. Li-style substitution principle

The core of understanding the Li-style replacement principle is to understand the words "design by contract, design according to the agreement". The parent class defines the "contract" (or protocol) of the function, and the subclass can change the internal implementation logic of the function, but cannot change the original "contract" of the function. The conventions here include: the functions to be implemented by the function declaration; conventions on input, output, and exception; even any special instructions listed in the comments.


To understand this principle, we need to understand the difference between the Li style substitution principle and polymorphism. Although polymorphism and Li-type substitution are somewhat similar in terms of definition description and code implementation, they focus on different angles. Polymorphism is a major feature of object-oriented programming and a syntax of object-oriented programming languages. It is an idea of ​​​​code implementation. Literary replacement is a design principle, which is used to guide how to design subclasses in the inheritance relationship. The design of subclasses should ensure that when replacing the parent class, the logic of the original program will not be changed and the correctness of the original program will not be destroyed. sex.

  1. Interface Segregation Principle

If you understand "interface" as a set of interfaces, it can be an interface of a microservice or an interface of a class library. If some interfaces are only used by some callers, we need to isolate this part of the interface and use it for this part of the caller alone, without forcing other callers to also rely on this part of the interface that will not be used.


If "interface" is understood as a single API interface or function, and some callers only need part of the functions in the function, then we need to split the function into multiple functions with finer granularity, so that the caller only depends on the one it needs Fine-grained functions.


If "interface" is understood as an interface in OOP, it can also be understood as an interface syntax in an object-oriented programming language. The design of the interface should be as simple as possible, and the implementation class and caller of the interface should not be dependent on unnecessary interface functions.

  1. Dependency Inversion Principle

1. Inversion of control
In fact, inversion of control is a relatively general design idea, not a specific implementation method, and is generally used to guide the design at the framework level. The "control" mentioned here refers to the control of the program execution flow, and "reversal" refers to the fact that the programmer controls the execution of the entire program before using the framework. After using the framework, the execution flow of the entire program is controlled by the framework. Control of the process is "reversed" from the programmer to the framework.


2. Dependency injection
Dependency injection and inversion of control are just the opposite, it is a specific coding technique. We do not create objects of dependent classes inside the class through new, but after the dependent class objects are created externally, they are passed (or injected) to the class for use through constructors, function parameters, etc.


3. Dependency injection framework
We use the extension points provided by the dependency injection framework to simply configure all the required classes and their dependencies between classes, and then the framework can automatically create objects, manage object life cycles, and dependency injection Wait for things that originally required programmers to do.


4. Dependency inversion principle
Dependency inversion principle is also called dependency inversion principle. This principle is somewhat similar to inversion of control, and is mainly used to guide the design at the framework level. High-level modules do not depend on low-level modules, they all depend on the same abstraction. Abstraction does not depend on concrete implementation details, concrete implementation details depend on abstraction .

  1. How to write code that meets the KISS principle

● Don't use technology that colleagues may not understand to implement code;
● Don't reinvent the wheel, but be good at using existing tool libraries;
● Don't over-optimize.


DRY principle
DRY is the abbreviation of Don't repeat yourself, which means "don't repeat yourself".
YAGNI principle
YAGNI is the abbreviation of You aren't gonna need it, which means "you won't need it"
Rule Of Three Principle
Rule of three is called "Three Principles", which means that when a function appears for the third time, abstraction

  1. How to understand "high cohesion, loose coupling"?

The so-called high cohesion means that similar functions should be placed in the same class, and dissimilar functions should not be placed in the same class. Similar functions are often modified at the same time, put them in the same class, and the modification will be more concentrated . The so-called loose coupling means that in the code, the dependencies between classes are simple and clear. Even if two classes have a dependency relationship, a code change in one class will not or rarely cause code changes in the dependent class .

  1. How to understand "Demeter's Law"?

There should be no dependencies between classes that should not have direct dependencies; between classes that have dependencies, try to only rely on the necessary interfaces . Dimiter's law hopes to reduce the coupling between classes and make the classes as independent as possible. Each class should know little about other parts of the system. Once a change occurs, fewer classes need to know about the change.

  1. How to understand refactoring?

Refactoring :
Large-scale refactoring refers to the refactoring of the top-level code design, including: the refactoring of the system, module, code structure, and the relationship between classes, etc. The refactoring methods include: layering, modularization, Decoupling, abstracting reusable components, and more. The tools for this kind of refactoring are the design ideas, principles, and patterns we have learned. This type of refactoring involves more code changes and has a larger impact, so it is more difficult, time-consuming, and the risk of introducing bugs is relatively greater.


Small refactoring
refers to the refactoring of code details, mainly for code-level refactoring of classes, functions, variables, etc., such as standard naming, standard annotations, elimination of super large classes or functions, extraction of duplicate code, and so on . Small refactorings are more about taking advantage of coding conventions that we can talk about later. This kind of refactoring needs to be modified more concentrated, simpler, more operable, less time-consuming, and relatively less risk of introducing bugs. You only need to be proficient in various coding standards to be handy.

  1. What is unit testing?

Unit test is a test at the code level, which is written by R&D itself to test the correctness of the logic of the code written by itself. As the name suggests, unit testing is to test a "unit", which is different from integration testing. This "unit" is generally a class or function, not a module or system.


Why write unit tests?
The process of writing unit tests is itself a process of code review and refactoring, which can effectively find bugs in the code and problems in code design. In addition, unit testing is a powerful supplement to integration testing, and it can help us quickly familiarize ourselves with the code. It is an improvement solution for TDD that can be implemented on the ground.


How to write unit tests?
Writing unit tests is to design various test cases for the code to cover various inputs, exceptions, and edge cases , and translate them into code. We can use some testing frameworks to simplify the writing of unit tests. In addition, for unit testing, we need to establish the following correct cognition:
● Writing unit tests is cumbersome, but not too time-consuming;
● We can slightly lower the requirements for unit test code quality;
● Coverage as a The only standard to measure the quality of unit testing is unreasonable;
● Unit testing does not depend on the specific implementation logic of the code under test;
● The unit testing framework cannot be tested, mostly because the code is not testable.


Why is unit testing difficult to implement?
On the one hand, writing unit tests is cumbersome, and the technical challenges are not great, so many programmers are unwilling to write them; on the other hand, domestic research and development tends to be "fast, rough, and fierce", and it is easy to execute unit tests due to tight development progress. Anticlimactic. In the end, the key problem is that the team has not established a correct understanding of unit testing, and feels that it is dispensable, and it is difficult to implement it well only by supervision.

  1. A quick way to improve code quality
  1. name
  2. note
  3. Code style: How many lines are appropriate for a class/function? Functions should not exceed one screen size
  4. Divide the code into smaller equal unit blocks
  5. Avoid too many function parameters
  6. Do not use function parameters to control logic
  7. Function design should have a single responsibility
  8. Remove nesting levels that are too deep
  9. learn to use explanatory variables
  10. Unicode
  1. builder mode

Using Builder, you can avoid the existence of invalid state. If there are too many constructor parameters, we need to consider using the builder mode, first set the builder's variables, and then create the object at one time, so that the object is always in a valid state.


The difference with the factory pattern:
The factory pattern is used to create different but related types of objects (a group of subclasses that inherit the same parent class or interface), and the given parameters determine which type of object to create. Builder mode is used to create a type of complex object, by setting different optional parameters, "customized" to create different objects

  1. Proxy mode

1. The principle and implementation of the proxy mode
In the case of not changing the original class (or called the proxy class), the proxy class is introduced to add functions to the original class. In general, we let the proxy class and the original class implement the same interface . However, if the original class does not define an interface, and the original class code is not developed and maintained by us. In this case, we can implement the proxy pattern by letting the proxy class inherit the methods of the original class.


2. The principle and implementation of dynamic proxy
Static proxy needs to create a proxy class for each class, and the code in each proxy class is a bit like template-style "repeated" code, which increases maintenance costs and development costs. For the problems of static proxy, we can solve it through dynamic proxy. Instead of writing a proxy class for each original class in advance, we dynamically create the proxy class corresponding to the original class at runtime, and then replace the original class with the proxy class in the system


3. Application scenarios of proxy mode Proxy mode is often used to develop some non-functional requirements
in business systems , such as: monitoring, statistics, authentication, current limiting, transactions, idempotence, and logs. We decouple these additional functions from business functions and put them in the agent class for unified processing , so that programmers only need to focus on business development. In addition, the proxy mode can also be used in application scenarios such as RPC and caching.

  1. decorator pattern

The decorator pattern mainly solves the problem that the inheritance relationship is too complicated, and replaces inheritance by composition . Its main role is to add enhancements to the original class . This is also an important basis for judging whether to use the decorator pattern. In addition, the decorator pattern has another feature, that is, multiple decorators can be nested on the original class . In order to meet this application scenario, when designing, the decorator class needs to inherit the same abstract class or interface as the original class .
Reference case: Java IO

  1. Proxy, bridge, decorator, adapter comparison

These four patterns are more commonly used structural design patterns.
Their code structures are very similar. Generally speaking, they can all be called the Wrapper mode, that is, the original class is encapsulated twice through the Wrapper class .


Proxy mode: The proxy mode defines a proxy class for the original class without changing the interface of the original class. The main purpose is to control access, not to enhance functions. This is the biggest difference between it and the decorator mode.


Bridge mode: The purpose of the bridge mode is to separate the interface part from the implementation part, so that they can be changed relatively easily and relatively independently.


Decorator mode: The decorator mode enhances the function of the original class without changing the interface of the original class, and supports the nested use of multiple decorators.


Adapter pattern: The adapter pattern is an afterthought remedial strategy. The adapter provides a different interface from the original class, while the proxy mode and decorator mode provide the same interface as the original class.

  1. facade mode

The facade pattern provides a set of unified interfaces for the subsystem, and defines a set of high-level interfaces to make the subsystem easier to use .
Examples of application scenarios of the facade mode

  1. Solve the problem of ease of use (the facade mode can be used to encapsulate the underlying implementation of the system, hide the complexity of the system, and provide a set of easier-to-use, higher-level interfaces)
  2. Solve performance problems (we reduce network communication costs and improve the response speed of App clients by replacing multiple interface calls with one facade interface call.)
  3. Solve the problem of distributed transactions (you can learn from the idea of ​​the facade mode, and then design a new interface that wraps these two operations, so that the new interface can perform two SQL operations in one transaction.)

Principle: Try to maintain the reusability of the interface, but for special cases, it is allowed to provide redundant facade interfaces to provide easier-to-use interfaces.

  1. combination mode

Organize a group of objects (Compose) into a tree structure to represent a "part-whole" hierarchy. Composition allows the client (in many design pattern books, "client" refers to the user of the code.) to unify the processing logic of individual objects and composite objects.

  1. Flyweight mode

The so-called "flying yuan", as the name suggests, is a shared unit. The purpose of the flyweight pattern is to reuse objects and save memory, provided that the flyweight object is an immutable object. Specifically, when there are a large number of duplicate objects in a system, we can use the flyweight pattern to design objects as flyweights, and only keep one instance in memory for multiple code references, which can reduce memory The number of objects to save memory. In fact, not only the same objects can be designed as flyweights, but for similar objects, we can also extract the same parts (fields) from these objects and design them as flyweights, so that these large numbers of similar objects can refer to these flyweights.
The application of the singleton pattern is to ensure that the object is globally unique. The Flyweight mode is applied to realize object reuse and save memory. Caching is for improving access efficiency, not for multiplexing. "Reuse" in pooling technology is understood as "reuse", mainly to save time.
Integer a = new Integer(123);
Integer a = 123;
Integer a = Integer.valueOf(123);
The latter two methods should be used to save memory by using Flyweight. (between -128 and 127)
In the implementation of Java Integer, integer objects between -128 and 127 will be created in advance and cached in the IntegerCache class. When we use autoboxing or valueOf() to create an integer object of this value range, the pre-created object of the IntegerCache class will be reused. The IntegerCache class here is the flyweight factory class, and the pre-created integer objects are flyweight objects.


In the implementation of the Java String class, the JVM opens up a storage area to store string constants. This storage area is called the string constant pool, similar to the IntegerCache in Integer. However, unlike IntegerCache, it does not create objects that need to be shared in advance, but creates and caches string constants as needed during the running of the program.


  1. template method pattern

Define an algorithm skeleton in a method and defer certain steps to subclass implementation . The template method pattern allows subclasses to redefine certain steps in an algorithm without changing the overall structure of the algorithm . The "algorithm" here can be understood as "business logic" in a broad sense, and does not specifically refer to the "algorithm" in data structures and algorithms. The algorithm skeleton here is the "template", and the method containing the algorithm skeleton is the "template method", which is also the origin of the name of the template method pattern.


Template mode has two functions: reuse and extension. Among them, reuse means that all subclasses can reuse the code of the template method provided in the parent class . Extension means that the framework provides functional extension points through the template mode, allowing framework users to customize the functions of the framework based on the extension points without modifying the source code of the framework .

  1. strategy pattern

Define a family of algorithm classes, and encapsulate each algorithm separately so that they can replace each other . The Strategy pattern can make changes to algorithms independent of the clients that use them (clients here refer to the code that uses the algorithms).
The main role of the strategy pattern is to decouple the definition, creation and use of strategies, and control the complexity of the code, so that each part will not be too complicated and the amount of code will be too much. In addition, for complex code, the strategy pattern can also satisfy the open-closed principle. When adding a new strategy, it minimizes and centralizes code changes and reduces the risk of introducing bugs.

  1. Chain of Responsibility Pattern

Multiple processors process the same request sequentially. A request is first processed by A processor, and then passed to the B processor, and then passed to the C processor after the B processor is processed, and so on, forming a chain. Each processor in the chain assumes its own processing responsibilities, so it is called the responsibility chain mode.


In the definition of GoF, once a processor can handle the request, it will not continue to pass the request to subsequent processors. Of course, in actual development, there are also variants of this mode, that is, the request will not be terminated midway, but will be processed by all processors.
There are two common implementations of the Chain of Responsibility pattern. One is to use a linked list to store processors, and the other is to use an array to store processors. The latter implementation is simpler.

  1. state mode

For the state machine, today we have summarized three implementation methods.
The first implementation is called the branching logic method. Use if-else or switch-case branch logic, refer to the state transition diagram, and literally translate each state transition into code as it is. For simple state machines, this implementation is the simplest and most straightforward, and is the first choice.
The second implementation is called look-up table method. For a state machine with many states and complex state transitions, the look-up table method is more suitable. Representing the state transition diagram through a two-dimensional array can greatly improve the readability and maintainability of the code.
The third implementation is called the state pattern. For a state machine with few states and relatively simple state transition, but the business logic contained in the action triggered by the event may be more complex, we prefer this implementation method.

  1. iterator pattern

Iterator mode, also called cursor mode. It is used to traverse collection objects. The "collection object" mentioned here can also be called "container" and "aggregate object". In fact, it is an object that contains a group of objects, such as arrays, linked lists, trees, graphs, and jump lists.
There are generally three ways to traverse a collection: for loop, foreach loop, and iterator traversal. The latter two are essentially one kind, and both can be regarded as iterator traversal. Compared with for loop traversal, using iterators to traverse has the following three advantages:

  1. The iterator mode encapsulates the complex data structure inside the collection. Developers don't need to know how to traverse, just use the iterator provided by the container ;
  2. The iterator mode separates the traversal operation of the collection object from the collection class and puts it into the iterator class, making the responsibilities of the two more single ;
  3. The iterator pattern makes it easier to add new traversal algorithms, more in line with the open-closed principle. In addition, since the iterators are all implemented from the same interface, it is easier to replace iterators during development based on the interface rather than the implementation of the program .
  1. visitor pattern

The visitor pattern allows one or more operations to be applied to a set of objects. The design intention is to decouple the operations and the objects themselves, keep the class with a single responsibility, satisfy the principle of opening and closing, and deal with the complexity of the code.

  1. memo mode

On the premise of not violating the principle of encapsulation, capture the internal state of an object and save this state outside the object so that the object can be restored to its previous state later.

  1. command mode

Encapsulate the request (command) into an object, so that different requests can be used to parameterize other objects (injecting different request dependencies into other objects), and it can support queued execution, logging, revocation, etc. of requests (commands) (additional control )Function.
Its main application scenario is to add functions to the execution of commands, in other words, to control the execution of commands, such as queuing, asynchronous, delayed execution of commands, recording logs for command execution, undoing and redoing commands, and so on.

  1. What design patterns do

What the design pattern needs to do is decoupling, that is, using a better code structure to split a large amount of code into smaller classes with single responsibilities, so that they can meet the characteristics of high cohesion and low coupling . The creation mode is to decouple the creation and use codes , the structural mode is to decouple different functional codes , and the behavioral mode is to decouple different behavioral codes . The main purpose of decoupling is to deal with the complexity of the code . Design patterns are created to solve complex code problems.

  1. How to write high-quality code
    insert image description here

Guess you like

Origin blog.csdn.net/adayabetter/article/details/124943250
Recommended