[open and closed principle] of design principle

Posted by TecTao on Sun, 27 Feb 2022 04:55:26 +0100

Python wechat ordering applet course video

https://edu.csdn.net/course/detail/36074

Python actual combat quantitative transaction financial management system

https://edu.csdn.net/course/detail/35475
The design principle is a summary of some experience guiding our code design, that is, "mental method"; Object oriented is our "weapon"; Design pattern is "move".

Based on mental skill, use weapon moves to deal with complex programming problems.

My cousin forgot to clock in again at work today

Cousin: brother, I really have no memory at all

Me: what happened?

Cousin: I forgot to punch in at work today. It's another day of working for nothing. I can't do anything.

You see, the traditional clock in and clock out system takes on-time clock in and clock out as one of the assessment indicators. Although it strengthens the management of the enterprise, it limits the time freedom of employees. The situation and working status of each employee are different. The mandatory working hours are easy to cause employees to clock out in order to cope with clock out, but in fact, the work efficiency is not high.

In fact, going to and from work on time is not the goal that the boss wants to achieve. What the boss wants is that the performance of all employees meets the standard and the enterprise can finally make a profit, and the clock in and clock out system is just one of the methods to achieve this goal.

It is clear that the performance is taken as the evaluation index. Then, the performance at least meets the standard, which can not be modified. On this basis, employees' commuting time can be arranged freely, so as to improve employees' production efficiency. This is the flexible working system, which is closed to the modification of performance results and open to the expansion of the time system.

You see, this is the open closed principle in our software development.

This means that software entities (classes, modules, functions, etc.) should be extensible, but not modifiable.

This is the most difficult to understand and master, but the most useful design principle.

The reason why it is difficult to understand is that "what kind of code change is defined as extension? What kind of code change is defined as modification? How can it meet the opening and closing principle? Does modifying the code necessarily violate the opening and closing principle?" And so on.

The reason why it is difficult to master is that "how to be open to extension and closed to modification? How to flexibly apply the opening and closing principle in the project, so as to ensure the scalability without affecting the readability of the code?" And so on.

It is most useful because extensibility is one of the most important measures of code quality. Among the 23 classic design patterns, most of them exist to solve the problem of code scalability.

How to understand "open to extension, modify and close"?

For example, bookstores sell books.

Books have three attributes: title, price and author. IBook is an interface for obtaining three attributes of books, as shown below:

 1 public interface IBook { 
 2     // Name of the book 
 3     public String getName(); 
 4 ​
 5     // Price of books 
 6     public int getPrice(); 
 7 ​
 8     // Author of the book 
 9     public String getAuthor(); 
10 } 

NovelBook is a specific implementation class, as shown below:

 1 public class NovelBook implements IBook { 
 2     // Name of the book 
 3     private String name; 
 4 ​
 5     // Price of books 
 6     private int price; 
 7 ​
 8     // Author of the book 
 9     private String author; 
10 ​
11     // Pass book data through constructor 
12     public NovelBook(String \_name,int \_price,String \_author){ 
13         this.name = \_name; 
14         this.price = \_price; 
15         this.author = \_author; 
16  } 
17 ​
18     // Who is the author 
19     public String getAuthor() { 
20         return this.author; 
21  } 
22 ​
23     // Get the title 
24     public String getName() { 
25         return this.name; 
26  } 
27 ​
28     // Get the price of books 
29     public int getPrice() {
30         return this.price; 
31  } 
32 } 

Next, let's take a look at how bookstores sell books:

 1 public class BookStore { 
 2     private final static ArrayList bookList = new ArrayList(); 
 3 ​
 4 // Static module initialization is generally generated from the initialization of persistence layer in the project 
 5 static{ 
 6 bookList.add(new NovelBook("Tianlong Babu",3200,"Jin Yong")); 
 7 bookList.add(new NovelBook("Notre Dame de Paris",5600,"Hugo")); 
 8 bookList.add(new NovelBook("Miserable world",3500,"Hugo")); 
 9 bookList.add(new NovelBook("Ordinary world",4300,"Rotel ")); 
10  } 
11 ​
12 //Simulated bookstore to buy books 
13 public static void main(String[] args) { 
14 NumberFormat formatter = NumberFormat.getCurrencyInstance(); 
15 formatter.setMaximumFractionDigits(2); 
16 System.out.println("------------The records of fiction books in the bookstore are as follows:---------------------"); 
17 for(IBook book:bookList){ 
18 System.out.println("Book Name:" + book.getName()+"\t Author:" + 
19 book.getAuthor()+ "\t Book price:" + formatter.format(book.getPrice()/100.0)+"element"); 
20  } 
21  } 
22 } 

Note: a static module is declared in the BookStore to realize the initialization of data. This part should be generated from the persistence layer and managed by the persistence layer tool.

The operation results are as follows:

------------The records of fiction books in the bookstore are as follows:--------------------- 
Book Name: eight books of Tianlong Author: Jin Yong book price:¥32.00 element 
Book Name: Notre Dame de Paris book author: Hugo book price:¥56.00 element 
Book Name: les miserables book author: Hugo book price:¥35.00 element 
Book Name: Ordinary World Book Author: Lu Yao book price:¥43.00 element 

However, the sales of novel books in bookstores have declined seriously recently. Therefore, bookstores hope to stimulate consumption through Discounts: all novel books with 40 yuan or more will be sold at a 20% discount, and those with less than 40 yuan will be sold at a 10% discount.

For projects that have been put into operation, this is a change. Then, how should we deal with it?

There are three ways to solve this problem:

Modify interface

A getOffPrice() method is added to IBook for discount processing.

First of all, IBook as an interface should be stable and reliable and should not change frequently. Otherwise, the role of interface as a contract will lose its significance.

Secondly, the interface is modified, and the NovelBook implementation class also needs to be modified accordingly. In this way, in order to realize this requirement, the area of change is relatively large.

Modify implementation class

Modify the getPrice() method in the NovelBook implementation class. In this way, the changed area is relatively small and is only limited to the NovelBook implementation class. But in this case, users will not be able to get the original price of the book.

Change through expansion

Add a subclass OffNovelBook and copy the getPrice() method. The high-level module (i.e. static module area) generates new objects through the OffNovelBook class. As follows:

public class OffNovelBook extends NovelBook { 
 public OffNovelBook(String \_name,int \_price,String \_author){ 
 super(\_name,\_price,\_author); 
 } 
​
 // Overwrite sales price 
 @Override 
 public int getPrice(){ 
 // original price 
        int selfPrice = super.getPrice(); 
 int offPrice=0; 
 if(selfPrice < 4000){  // 10% off if the original price is less than 40 yuan 
            offPrice = selfPrice * 90 /100; 
 }else{ 
 offPrice = selfPrice * 80 /100; 
 } 
 return offPrice; 
 } 
}

You see, just extend a subclass and copy the getPrice() method to complete the new business. Next, let's take a look at the modification of the BookStore class:

public class BookStore { 
 private final static ArrayList bookList = new ArrayList(); 
​
 // Static module initialization is generally generated from the initialization of persistence layer in the project 
 static{ 
 // Replace it with a discounted novel
 bookList.add(new OffNovelBook("Tianlong Babu",3200,"Jin Yong")); 
 bookList.add(new OffNovelBook("Notre Dame de Paris",5600,"Hugo")); 
 bookList.add(new OffNovelBook("Miserable world",3500,"Hugo")); 
 bookList.add(new OffNovelBook("Ordinary world",4300,"Rotel ")); 
 } 
​
 // Simulated bookstore to buy books 
 public static void main(String[] args) { 
 NumberFormat formatter = NumberFormat.getCurrencyInstance(); 
 formatter.setMaximumFractionDigits(2); 
 System.out.println("------------The records of fiction books in the bookstore are as follows:---------------------"); 
 for(IBook book:bookList){ 
 System.out.println("Book Name:" + book.getName()+"\t Author:" + 
 book.getAuthor()+ "\t Book price:" + formatter.format(book.getPrice()/100.0)+"element"); 
 } 
 } 
} 

Only the static module initialization part has been modified, and other parts have not been modified. The operation results are as follows:

------------The records of fiction books in the bookstore are as follows:--------------------- 
Book Name: eight books of Tianlong Author: Jin Yong book price:¥25.60 element 
Book Name: Notre Dame de Paris book author: Hugo book price:¥50.40 element 
Book Name: les miserables book author: Hugo book price:¥28.00 element 
Book Name: Ordinary World Book Author: Lu Yao book price:¥38.70 element 

The above example realizes the new demand of discount through one extension and one modification. Some students may ask, "didn't you still modify the code?"

Does modifying the code mean violating the opening and closing principle?

The BookStore class has indeed been modified. This part belongs to a high-level module. When the business rules change, the high-level module must be partially changed to adapt to the new business. It is impossible to add a new function without "modifying" the code of any module, class or method. Class needs to be created, assembled and initialized to build a runnable program. The modification of this part of the code is inevitable.

What we need to do is to try to make the modification operations more concentrated, less and higher, and try to make the most core and complex part of the logic code meet the opening and closing principle.

How to "open to extension, modify and close"?

In fact, the opening and closing principle is about the scalability of the code. It is the "gold standard" to judge whether a piece of code is easy to expand.

Before talking about the specific methodology, let's first look at some guiding ideology that is more inclined to the top. In order to write extensible code as much as possible, we should always have the awareness of extension, abstraction and encapsulation. These "subconscious" may be more important than any development skill.

**Extension awareness: * * when writing code, we should take more time to think about what changes may be required in this code in the future and how to design the code structure. The extension points should be reserved in advance, so that when the requirements change in the future, the overall structure of the code does not need to be changed and the minimum code change can be achieved, The new code can be flexibly inserted into the extension point to "open to extension and close to modification".

**Abstract consciousness: * * provide abstract immutable interface for upper system. When the specific implementation changes, we only need to extend a new implementation based on the same abstract interface and replace the old implementation. The code of the upstream system hardly needs to be modified.

**Encapsulation awareness: * * after identifying the variable and immutable parts of the code, we should encapsulate the variable parts and isolate the changes.

Among many design principles, ideas and patterns, the most commonly used methods to improve code scalability are polymorphism, dependency injection, programming based on interface rather than implementation, and most design patterns (such as decoration, strategy, template, responsibility chain, state, etc.).

We will share the design pattern separately. Today, we will focus on how to use polymorphism, dependency injection and interface based programming instead of implementation programming to realize "opening to extension and closing to modification".

Suppose, we are now going to develop a method to send asynchronous messages through Kafka. For the development of such a function, we should learn to abstract it into a group of asynchronous message interfaces independent of the specific message queue (Kafka). All upper systems rely on this set of abstract interface programming and are called by dependency injection. When we want to replace a new message queue, such as replacing Kafka with RocketMQ, we can easily unplug the old message queue implementation and insert a new message queue implementation.

// This part embodies the abstract consciousness
public interface MessageQueue { //...}
public class KafkaMessageQueue implements MessageQueue { //...}
public class RocketMQMessageQueue implements MessageQueue { //...}
​
public interface MessageFromatter { //...}
public class JsonMessageFromatter implements MessageFromatter { //...}
public class ProtoBufMessageFromatter implements MessageFromatter { //...}
​
public class Demo {
 private MessageQueue msgQueue;        // Programming based on interfaces rather than implementation
    public Demo(MessageQueue msgQueue) {  // Dependency injection
        this.msgQueue = msgQueue;
 }
 
 // msgFormatter: polymorphism, dependency injection
    public void sendNotification(Notification notification, MessageFormatter msg) {
 //..
 }
}

Of course, the principle of opening and closing is not free. Sometimes, the scalability of code conflicts with readability. At this time, we need to make a trade-off between the two. In short, there is no universal reference standard, which is determined by the actual application scenario.

How to reserve extension points?

As mentioned earlier, the key to writing code that supports "open to extension and close to modification" is to reserve extension points. The question is, how can we identify all possible extension points?

If you develop business oriented systems, such as e-commerce system, logistics system, financial system, etc., you need to know enough about the business itself to identify as many expansion points as possible.

If you develop general and low-level frameworks, class libraries, components, etc., you need to understand how they will be used and what functions may be added in the future.

"The only constant is the change itself". Although we know enough about the business system and framework functions, we can't identify all the extension points. Even if we can identify all extension points and design reserved extension points for these places, the cost is very large, which is called * * "over design" * *.

A reasonable approach should be to reserve extension points in advance when writing code for certain cases that may be expanded in a short time, or need to be changed to have a great impact on the code structure, or to realize extension points with low cost. However, for some requirements that are uncertain whether to support in the future, or extension points with complex implementation, the extension requirements can be supported by refactoring the code.

Well, whether the design principles are properly applied should be analyzed according to specific business scenarios.

summary

Opening to expansion is to cope with change (demand);

The modification is closed to ensure the stability of the existing code;

The end result is to make the system more flexible!

reference resources

Dahua design mode

Geek time column "the beauty of design patterns"

https://blog.csdn.net/sinat_20645961/article/details/48239347

Topics: IT computer