Software design principles (02)-Open Close Principle (OCP)

1 Definition

Just change the code once when there is a need, is it natural? Anyway, it’s easy to modify, and it’s no brainer to make another CV. But each time each person changes something, and over time, when new demands arise, the amount of changes for future generations will become larger. Everyone is innocent, they just made some simple modifications. But in the end, Receiver was unable to maintain it, so he directly overthrew the old system and wrote a new system (which can also be regarded as creating job opportunities).

Since there are so many problems to be “revised”, is it okay not to do so? You have to be proficient in OCP.

Software entities like classes,modules and functions should be open for extension but closed for modifications
A software entity such as a class, module, and method should be open for extension and closed for modification. This was proposed by Bertrand Meyer in "Object-Oriented Software Construction", which puts forward extremely high requirements for software design: no code modifications and openness to extensions. Questions available:

  • Open to what?
  • How to close modifications?

1.1 Can I still write requirements without modifying the code?

Extension, that is, new requirements are implemented with new code. What OCP describes to us is the result: new functions can be completed by extension alone without modifying the code.

premise

Keeping extension points within the software requires design (the quality of a senior engineer). Each extension point is a model that needs to be designed.

1.2 Use abstraction to build the framework and implementation to extend the details

A software entity should change through extension, not through modification of existing code. It is a principle established to constrain the current development design for future events of a software entity.

2 Case-Bookstore

2.1 Source code

package com.javaedge.design.principle.openclose;

/**
 * 书籍接口
 * 
 * @author JavaEdge
 */
public interface BaseBook {
    
    

    /**
     * 获取书籍 ID
     *
     * @return 书籍 ID
     */
    Integer getId();

    /**
     * 获取书籍名
     *
     * @return 书籍名
     */
    String getName();

    /**
     * 获取书籍价
     *
     * @return 书籍价
     */
    Double getPrice();
}
package com.javaedge.design.principle.openclose;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

/**
 * Java书籍实现类
 * 
 * @author JavaEdge
 */
@AllArgsConstructor
@Getter
@Setter
public class JavaBook implements BaseBook {
    
    

    private Integer id;

    private String name;

    private Double price;
}
package com.javaedge.design.principle.openclose;

import lombok.extern.slf4j.Slf4j;

/**
 * 测试类
 *
 * @author JavaEdge
 */
@Slf4j
public class Test {
    
    
    public static void main(String[] args) {
    
    
        JavaBook baseCourse = new JavaBook(66, "Java编程思想", 98d);

        JavaDiscountBook discountBook = (JavaDiscountBook) baseCourse;
        log.info("书籍ID:" + discountBook.getId() +
                " 书籍名称:" + discountBook.getName() +
                " 书籍原价:" + discountBook.getPrice() +
                "书籍优惠价:" + discountBook.getDiscountPrice());
    }
}

2.2 Requirements

Add a new discount method: If the original interface is modified directly, each implementation class must re-add the method implementation. But the interface should be stable and should not be modified frequently!

package com.javaedge.design.principle.openclose;

/**
 * Java 书籍折扣类
 *
 * @author JavaEdge
 */
public class JavaDiscountBook extends JavaBook {
    
    

    public JavaDiscountBook(Integer id, String name, Double price) {
    
    
        super(id, name, price);
    }

    public Double getDiscountPrice() {
    
    
        return super.getPrice() * 0.8;
    }
}

Current UML:

接口应稳定且可靠,不应经常变化, otherwise the interface will lose its effectiveness as a contract.

2.3 Modify the implementation class

Discount processing is implemented in getPrice(). Low-level programmers are accustomed to completing some business changes (or bugfixes) quickly through class file replacement.

This method is excellent when the project has a clear charter (constraints within the team) or excellent architectural design. However, if the book purchaser also needs to look at the price, since this method has implemented discounted prices, the purchaser will also see the discounted price. Yes 因信息不对称而出现决策失误. Therefore, this is not the optimal solution.

2.4 Change through extension

增加子类OffNovelBook, rewrites getPrice, and the high-level module (static static module area) generates new objects through the OffNovelBook class to complete the minimized development of the system due to business changes.

good idea! There are fewer modifications and less risk.

OCP is open to extensions and closed to modifications, but this does not mean that no modifications will be made. Changes in low-level modules must be coupled with high-level modules, otherwise they will be isolated and meaningless code segments.

3 types of changes

3.1 Logic changes

Only one logic is changed, and other modules are not involved. If the original algorithm a*b+cneeds to be modified a*b*c, then directly modify the method in the original class, but the prerequisite is: all dependent or associated classes are processed according to the same logic.

3.2 Submodule changes

Changes in one module will have an impact on other modules. In particular, changes in a low-level module will inevitably cause changes in high-level modules. Therefore, when changes are completed through expansion, high-level module modifications are inevitable.

3.3 Visible view changes

Such as Swing. If it is just button and text rearrangement, it is simple. The most common thing is that business coupling changes a list of display data. According to the original requirement, there are 6 columns, but suddenly one column needs to be added, and this column needs to span N tables and process M Only logic can show that such a change is terrifying, but the change can still be completed through expansion.

3.4 Summary

Give up the idea of ​​revising history! The basic path of a project: project development, reconstruction, testing, production, operation and maintenance.

  • Refactoring, you can modify the original design and code
  • Operation and maintenance, minimize modifications to the original code, maintain the purity of historical code, and improve system stability

4 Case - Hotel Member

Develop a hotel CRS system to calculate different room rates for different users:

  • Full price for ordinary users
  • 20% off for Gold Card
  • 10% off for Silver Card

Code might:

4.1 New requirements

25% off for adding a platinum member, good CV method:

This is [Modify the Code]. Every time a new type is added, the code is modified.
But a hotel system with users of various levels must not only have different room rates, but also different services, such as whether there is breakfast? Pay in advance? Coupon intensity and continuous stay discount rules? . It is foreseeable that every time a user level is added, the code to be changed is scattered everywhere.

4.2 What’s the explanation?

Consider designing into a scalable model. Since what needs to be added every time is the user level, and various service differences are reflected at the user level, a user-level model is needed.

① User level reconstruction

The original code can be reconstructed into:

If you add a platinum user at this time, you only need to write a new class:


This can be done because there are extension points left in the code: UserLevel upgrades the original UserLevel that only supports enumerated values ​​into a [behavioral] UserLevel.

After the transformation, the getRoomPrice of HotelService is stable and does not need to be continuously adjusted according to the user level.
Once you have a stable building block, you can reuse it as a stable module later.

5 Build extension points

In fact, when we modify the code, the effect is not good, but when it comes to writing the code ourselves, we feel dizzy. Is there a problem with the system you developed? I believe most people admit it. But I ask again: Do you often take the initiative to optimize? Most people were silent.
Although it is rubbish, it runs well online. What if I fail to optimize it? The performance is only 3.25! The reality is like this. Everyone understands the macro level of the system, but the code implementation level is instinctively ignored for various reasons.

Therefore, when writing a software system, one should provide stable small modules and then combine them. A module that changes frequently is unstable, and using it to construct a larger module will lead to endless troubles.

5.1 Why can’t I write code well despite knowing a lot of principles?

What hinders us from constructing stable modules is the ability to build models. Recall how the changed UserLevel was upgraded to a behavioral UserLevel.

The key point of encapsulation is behavior, and data is just an implementation detail. However, many people are accustomed to data-oriented writing, resulting in a lack of scalability in the design.

5.2 Difficulties in building models

  1. separation of concerns
  2. find commonalities

To build an abstraction, you need to find the commonalities of things . Finding commonalities during business processing is already difficult for most people.

6 Case - Reporting Service


Many people write code in this style every day, and the code process is rigid. As long as there are new needs, this paragraph will basically be modified.

6.1 Requirements

Send statistical information to another internal system, which can display the statistical information for external partners to review.

6.2 Analysis

What is sent to another system is statistical information . In the original code:

  • The first two steps are to obtain source data and generate statistical information.
  • The last two steps generate a report and send the statistical information via email.

The last two steps and the steps to be added have something in common, they both use statistical information. Therefore, common models can be used, such as OrderStatisticsConsumer:

In this way, new requirements only need to add a new class instead of if/else:

In this case, the first step is to decompose:

  • Separate the steps
  • Then find the similarities between the steps
  • and build a new model

The actual project code is more complicated, but it does not necessarily mean that the business logic is complicated, but that the code is written in a junk and complicated manner.
Therefore, we must first split the changes caused by different demand sources into different methods according to the SRP to form small units, and then do the analysis here.

It does not happen overnight that actual projects reach OCP. Here, an OrderStatisticsConsumer is extracted only because there is a change in demand.

There may be other changes in the future, such as the logic for generating reports. At that time, maybe extract a new OrderStatisticsGenerator interface. But no matter what, every time you do this kind of model building, the core classes will develop stably.

A good design will provide enough extension points for new functions to be extended (think of the Spring life cycle).
"The Art of Unix Programming" advocates "providing mechanisms, not strategies", which embodies OCP.

Many system plug-in mechanisms, such as IDEA and VS Code, embody OCP. To understand their interfaces, you can see the various capabilities this software provides us.

6.3 Grip

OCP can also help us optimize the system. Check Git and find out the most frequently changed files. They usually do not meet OCP. This can become the starting point for your system optimization.

7 Why choose OCP?

7.1 Impact of OCP on testing

When a change is proposed, you need to consider: Can the original robust code not be modified, but can the change be implemented only through extension? Otherwise, you need to go back to the original testing process and conduct UT, functional testing, integration testing and even acceptance testing.

Looking back at the bookstore case, the BaseBook interface has been written, and the implementation class JavaBook has also been written. Write a test class:

public class JavaBookTest extends TestCase {
    
    
     private String name = "Java666";
     private int price = 6000;
     private String author = "JavaEdge";      
     private BaseBook javaBook = new JavaBook(name,price,author);
     
     // 测试getPrice方法
     public void testGetPrice() {
    
    
             //原价销售,根据输入和输出的值是否相等进行断言
             super.assertEquals(this.price, this.novelBook.getPrice());
     }
}

If you add a discount sales requirement and directly modify getPrice, you must modify the UT class. In actual projects, a class generally has only one test class, which can have many test methods. A large number of modifications are made in a bunch of already complex assertions, and it is inevitable that tests will be missed.

Therefore, business logic changes should be achieved through extension rather than modification . Changes in business requirements can be accomplished by adding a subclass OffJavaBook. What benefits does this have for testing? Regenerate a test file OffJavaBookTest, and then test getPrice. UT is an isolated test. As long as the method I provide is correct, nothing else matters:

public class OffNovelBookTest extends TestCase {
    
       
     private BaseBook below40NovelBook = new OffJavaBook("Java666",3000,"JavaEdge");
     private BaseBook above40NovelBook = new OffJavaBook("Go999",6000,"JavaEdge");
      
     // 测试低于40元的数据是否是打8折
     public void testGetPriceBelow40() {
    
    
             super.assertEquals(2400, this.below40NovelBook.getPrice());
     }
     
     // 测试大于40的书籍是否是打9折
     public void testGetPriceAbove40(){
    
    
             super.assertEquals(5400, this.above40NovelBook.getPrice());
     }
}

For newly added classes and newly added test methods, just make sure that the newly added classes are correct.

7.2 Improve reusability

In OOP, all logic is combined from atomic logic instead of independently implementing a business logic in a class. Only in this way can the code be reused. The smaller the granularity, the greater the possibility of reuse.

① Why reuse?

Reduce the amount of code, avoid the dispersion of the same logic, and prevent subsequent maintainers from searching for relevant code throughout the entire project to fix a small bug or add a new feature, and then express "extreme disappointment" to the developers.

② How to improve the reuse rate?

Reduce the logic granularity until a piece of logic can no longer be split.

7.3 Improve maintainability

After a piece of software is put into production, the receiver not only needs to maintain the data, but may also expand the program. What the receiver loves to do most is to extend a class, rather than modify a class. Regardless of whether the original code is good or bad, let the receiver Xia first understands the original code and then modifies it, which is purgatory! Don't let him swim blindly in the original code ocean before modifying it. That would be the destruction of the docking Panxia, ​​and he would drown due to lack of water.

7.4 OOP

Everything is an object. We must abstract everything into objects and then operate on the objects. But movement is certain. When there is movement, there will be changes. If there are changes, we must have strategies to deal with them. How to deal with them quickly? It is necessary to consider all possible changing factors at the beginning of the design, and then leave the interface and wait for "possibility" to become "reality".

8 Summary

If SRP mainly depends on encapsulation, OCP must be polymorphic. If you want to provide extension points, you must program for the interface.

Java SPI provides an extension mechanism, and Spring Boot and Dubbo continue to improve, each providing extension points:

  • Spring Boot allows users to customize starters
  • Dubbo customizable protocol

1. Identify the modification points, build the model, and convert the original static logic into dynamic logic.
2. The difficulty in building the model is to separate the points of concern, and secondly, find the commonalities.

Guess you like

Origin blog.csdn.net/qq_33589510/article/details/117418458
Recommended