DDD Series 2: Re-understand object-oriented development, subvert traditional cognition, and break the dilemma of CURD

DDD Series 2: Re-understand object-oriented development, subvert traditional cognition, and break the dilemma of CURD

DDD Series 1: Re-understand object-oriented development, subvert traditional cognition, and break the CURD dilemma

Bottom-up learning method: ask questions -> analyze the problem -> solve the problem -> summarize

Demand scenario

Business needs

1. Enter your mobile phone number to get the SMS verification code; 2. Enter the verification code and click to log in; 1. Determine whether the mobile phone is registered or not, register if not registered, and log in again if already registered; 2. Registration process: record login time, generate token, generate nickname through mobile phone number 3. Login process: record login time and regenerate token Copy code

User table structure

CREATE TABLE `users` ( `id` int ( 10 ) unsigned NOT NULL AUTO_INCREMENT COMMENT'Increment ID' , mobile` ` VARCHAR ( . 11 ) the NOT NULL the DEFAULT '' the COMMENT 'phone users' , `nickname` varchar ( 60 ) NOT NULL DEFAULT '' COMMENT'nickname ' , token` ` VARCHAR ( 255 ) the NOT NULL the DEFAULT '' the COMMENT 'token' , login_at` datetime ` the DEFAULT NULL the COMMENT 'landing time` `' , PRIMARY KEY (` id`), UNIQUE KEY `mobile` (` mobile`), UNIQUE KEY token` `(` token`) ) ENGINE = the InnoDB the AUTO_INCREMENT = . 1 the DEFAULT the CHARSET = utf8mb4 duplicated code

The first implementation method of parameter verification

Note: For the sake of concise code, the code is incomplete, only the required code is shown, and a lot of comments have been removed. Please refer to the source code for the complete code.

public class UserController { public Response sendCaptcha (String mobile) { //mobile phone number parameter verification isMobile(mobile); /** Send verification code logic omitted**/ return success(); } //Verify mobile phone number function private void isMobile (String mobile) { Pattern p = Pattern.compile( "^[1][3,4,5,6,7,8,9][0-9]{9}$" ); Matcher m = p.matcher(mobile); if (!m.matches()) { throw new Exception( "Mobile phone number format is incorrect" ); } } } POST request: http: //127.0.0.1/user/sendCaptcha?mobile=13000000000 -------------------------------------------------- ------------------------------ Code analysis: The parameter verification function is placed on the Controller layer; Controller layer responsibilities: obtain information from HTTP requests, extract parameters, and distribute them to different processing services; Problem 1 : Violating the responsibility of the Controller layer, this way of writing causes the Controller layer to be flooded with a large number of verification functions; Question 2 : New user delivery address requirements: the user has multiple delivery addresses, and the user can add or modify the delivery address, including mobile phone number parameter verification; If implemented in this way, AddressController needs to make a copy of the isMobile function, which leads to code duplication; Copy code

Duplicate code is a major source of software quality degradation! ! !

Duplicate code will cause the maintenance cost to increase exponentially;
if the duplicate code is modified in one place, and the modification is forgotten in another place, bugs will be generated everywhere (for example, the verification rule starting with 12 is added to the mobile phone number), and it will also make you The code size becomes bloated;

The second method of parameter verification

public class Utils { public static void isMobile (String mobile) { Pattern p = Pattern.compile( "^[1][3,4,5,6,7,8,9][0-9]{9}$" ); Matcher m = p.matcher(mobile); if (!m.matches()) { throw new Exception( "Mobile phone number format is incorrect" ); } } } -------------------------------------------------- ------------------------------ public class UserController { public Response sendCaptcha (String mobile) { //mobile phone number parameter verification Utils.isMobile(mobile); /** Send verification code logic omitted**/ return success(); } } POST request: http: //127.0.0.1/user/sendCaptcha?mobile=13000000000 -------------------------------------------------- ------------------------------ Code analysis: The verification logic is extracted, and the tool Utils implements the isMobile function Question: Extracting the verification logic to the Utils tool class is also common in real development. But there will be a lot of Utils.isMobile, Utils.isEmail in the code in the future, a lot of repetitive low-quality code Copy code

Thinking: Starting from object-oriented thinking, does isMobile belong to the actions or behaviors of Utils objects?

The third method of parameter verification

public class User { private Integer id; @NotNull(message = "SMS verification code cannot be empty") @Size(min = 4, max = 4, message = "SMS verification code can only be four digits") private String captcha; @NotBlank(message = "Mobile phone number cannot be empty") @NotNull(message = "Mobile phone number cannot be empty") @Pattern(regexp = "^[1][3,4,5,6,7,8,9 ][0-9]{9}$", message = "Mobile phone number format is incorrect") private String mobile; private String token; private String nickname; private LocalDateTime loginAt; } -------------------------------------------------- ------------------------------ public class UserController { public Response sendCaptcha (User user) { //Sending the verification code only needs to verify the mobile, captcha does not need to verify //The following two lines of code can independently verify the parameter verification annotations of the User class mobile attribute Validator<User> validator = new Validator<>(); validator.validateProperty(user, "mobile" ); /** Send verification code logic omitted**/ return success(); } } POST request: http: //127.0.0.1/user/sendCaptcha?mobile=13000000000 -------------------------------------------------- ------------------------------ Code analysis: The automatic verification of parameters realized by the way of annotation is also a popular way to realize it now. The user s delivery address requirements, create a new Address entity, and annotate attributes for verification public class Address { private Integer id; @NotNull(message = "SMS verification code cannot be empty") @Size(min = 4, max = 4, message = "SMS verification code can only be four digits") private String captcha; @NotBlank(message = "Mobile phone number cannot be empty") @NotNull(message = "Mobile phone number cannot be empty") @Pattern(regexp = "^[1][3,4,5,6,7,8,9 ][0-9]{9}$", message = "Mobile phone number format is incorrect") private String mobile; private String consignee; private String province; private String city; private String district; private String detail; } Question: Same as above, there are a lot of repeated verification codes in the comments Copy code

Thinking: Is the mobile phone number verification logic the action or behavior of the User object and Address object? Where is it right?

problem analysis

Object-oriented is an object-centric development method. The objects mentioned here are objects in the real world. You can either build several classes randomly or develop object-oriented;

A mobile phone number in the real world is not just a string of numeric characters. It has an area code (86), a length limit (11 digits), starts with 1[3,4,5,6,7,8,9], 4G, The difference between 5G and the difference between operators is that it is a real living object with its own attributes and behaviors. When assigning its attributes and behaviors to other objects, have you considered its feelings?

Similarly, the SMS verification code has its own attributes and behaviors in the real world. It may be composed of 4 digits, has an expiration time, and contains the text message content (#### is your login verification code. If you are not operating it, please ignore it. This text message.) And so on.

SMS verification code attributes: 1. Code value: value 2. Expiration time: expire 3. Mobile phone number to be verified: mobile 4. SMS text content: content SMS verification code behavior: 1. Generate code value 2. Is the verification code value format correct? 3. Generate code value 4. Generate SMS text content Copy code

Object-oriented implementation of parameter verification

public class Mobile { @NotBlank(message = "Mobile phone number cannot be empty") @NotNull(message = "Mobile phone number cannot be empty") @Pattern(regexp = "^[1][3,4,5,6,7,8,9 ][0-9]{9}$", message = "Mobile phone number format is incorrect") private String value; public void valid () { Validator<Mobile> validator = new Validator<>(); validator.validate( this ); } } -------------------------------------------------- ------------------------------ public class Captcha { @NotNull(message = "SMS verification code cannot be empty") @Size(min = 4, max = 4, message = "SMS verification code can only be four digits") private String value; @Valid private Mobile mobile; private static long expire = 60 ; //Expiration time private String content = "%s is your login verification code. If you are not operating by yourself, please ignore this text message." ; //Generate a random four-digit number public void generateValue () { value = String.valueOf(( int )(Math.random() * 9000 + 1000 )); } //Generate SMS content public void generateContent () { content = String.format(content, value); } } -------------------------------------------------- ------------------------------ public class CaptchaController { public Response send (Captcha captcha) { //Only mobile verification is required to send the verification code, captcha does not require verification captcha.getMobile().valid(); /** Send verification code logic omitted**/ return success(); } } POST request: http: //127.0.0.1/captcha/send?mobile.value=13000000000 -------------------------------------------------- ------------------------------ Code analysis: 1. Create a new Mobile entity, the value attribute is the value of the mobile phone number, annotate the value verification rules, and the Mobile entity comes with self-validation function valid 2. Create a new Captcha entity, the Captcha entity contains the Mobile entity, forming a nested object; 3. Independent CaptchaController, Use Captcha to map request parameters, Call the self-verification function valid of the Mobile attribute in the Captcha instance to verify whether the parameters are correct 4. Pay attention to the POST request parameters: mobile.value = 13000000000 5. The user's delivery address requirements, create a new Address entity, including the Mobile entity, to form a nested object; public class Address { private Integer id; @Valid private Mobile mobile; private String consignee; private String province; private String city; private String district; private String detail; } Copy code

Realization of login function parameter verification code

public class UserController { public Response login ( @Valid Captcha captcha, User user) { user = /** Send verification code logic omitted **/ ; return success(user); } } POST request: http: //127.0.0.1/user/login?value=1234&mobile.value=13000000000 -------------------------------------------------- ------------------------------ Code analysis: 1. Use the @Valid annotation to verify the SMS verification code and mobile phone number at the same time. 2. Pay attention to the request parameters: value= 1234 &mobile.value= 13000000000 value is the SMS verification code mobile.value is the mobile phone number Copy code

summary

1. The verification logic is encapsulated in their own entities, and there are entities responsible for the verification logic. The verification logic will not be scattered around the project code. When the verification logic changes, it is enough to find the corresponding entity modification. This is the high cohesion of the code.

2. Through the nested combination of different entities, a variety of verification requirements can be realized, which greatly enhances the reusability of the code, which is the low coupling of the code

Send SMS verification code code implementation

public class CaptchaController { CaptchaService captchaService; public Response send (Captcha captcha) { captchaService.send(captcha); return success(); } } -------------------------------------------------- ------------------------------ public class CaptchaService { //Object-oriented public void send (Captcha captcha) { //Generate random four-digit numbers captcha.generateValue(); //Generate SMS content captcha.generateContent(); //send messages sendSMS(captcha); //store save(captcha); } //Process-oriented or function-oriented public void send (Captcha captcha) { //Generate a random four-digit number String code = String.valueOf(( int )(Math.random() * 9000 + 1000 )); //Generate text message content content = String.format(captcha.getContent(), code); //send messages sms.send(captcha.getMobile().getValue(), content); //store repository.save(captcha); } } -------------------------------------------------- ------------------------------ public class Captcha { private String value; private Mobile mobile; private static long expire = 60 ; //Expiration time private String content = "%s is your login verification code. If you are not operating by yourself, please ignore this text message." ; private void generateValue () { value = String.valueOf(( int )(Math.random() * 9000 + 1000 )); } private void generateContent () { content = String.format(content, value); } } -------------------------------------------------- ------------------------------ Code analysis: Please experience the difference between object-oriented and process-oriented code implementation Copy code

Before the popularization of object-oriented, the mainstream development method was "function-oriented". Specifically, it was to grasp the overall function of the target system, refine it in stages, and decompose it into smaller parts. If a function-oriented development method is used to write software, when specifications change or functions are added, the scope of modification will become very wide, and the software will also be difficult to reuse.

The purpose of object-oriented technology is to make the maintenance and reuse of software easier. Its basic idea is to focus on each component, improve the independence of components, combine components, and realize the overall function of the system. By improving the independence of components, when modification occurs, the scope of influence can be minimized, and it can be reused in other systems.
--Excerpt from "How does object-oriented work"

How to understand the above paragraph, I feel that it is quite right, but it is no use to read it, let s take an example to illustrate

New requirements: The description of the validity period is added to the content of the message, "%s is your login verification code, please fill in within %s minutes, if you are not operating by yourself, please ignore this message." Copy code

Analysis of the new requirement realization process:
1. The send function for function point implementation is a whole. If you want to modify the logic, the first step is to read through the code of the entire send function, clarify the business rules of the context, and then Dare to change the code. Now it is just a few lines of code. If it is a complicated function point, one or two hundred lines of code, it will take a lot of energy to read through to clarify the rules. This is the code's poor readability and low maintainability.

2. The send function of object-oriented implementation is composed of different components. Now you only need to focus on the generateContent function of the Captcha component to find the expire and content attributes required by this function. This reduces the need to understand The context of the business logic improves the efficiency of implementing new business codes, which means that the code has good readability and high maintainability;

Modify the code as follows: public class Captcha { private static long expire = 60 ; private String content = "%s is your login verification code. Please fill in within %s minutes. If you are not operating by yourself, please ignore this message." ; private void generateContent () { long minute = expire/ 60 ; content = String.format(content, value, minute); } } Copy code

Code testability is also an important evaluation criterion for code quality

Analysis of the test process:
1. The send function, which is implemented for function points, is an indivisible whole and can only be tested for the entire function. At the same time, the function relies on third-party SMS sending services and storage services at the warehousing layer, resulting in special unit testing. Difficult to write, this is the poor testability of the code;

2. The object-oriented send function can easily realize the unit test of the generateValue and generateContent functions of Captcha, which means that the code is highly testable;

Note: SMS services, warehouse storage services belong to the hardware layer, the test unit tests focused on business logic rules copy the code

User login function code implementation

public class Nickname { private String value; public void generate (String mobile) { StringBuilder sb = new StringBuilder(mobile); value = sb.replace( 3 , 8 , "*****" ).toString(); } } -------------------------------------------------- ------------------------------ public class Token { private String value; public void generate (String val) { val += LocalDateTime.now().toString(); value = DigestUtils.md5DigestAsHex(val.getBytes()); } } -------------------------------------------------- ------------------------------ public class User { private Integer id; private Mobile mobile = new Mobile(); private Token token = new Token(); private Nickname nickname = new Nickname(); private LocalDateTime loginAt; public void reLogin () { //Update login time loginAt = LocalDateTime.now(); //Regenerate token token.generate(mobile.getValue()); } public void register () { //update login time loginAt = LocalDateTime.now(); //Generate token token.generate(mobile.getValue()); //Generate nickname nickname.generate(mobile.getValue()); } } -------------------------------------------------- ------------------------------ public class UserService { UserRepository repository; public User loginOrRegister (User user) { //Whether the phone number is registered User u = repository.findByMobile(user.getMobile()); if (u != null ) { u.reLogin(); //Re-login save(u); return u; } user.register(); //New user registration save(user); return user; } -------------------------------------------------- ------------------------------ public class UserController { UserService userService; public Response login ( @Valid Captcha captcha, User user) { user = userService.loginOrRegister(user); return success(user); } } POST request: http: //127.0.0.1/user/login?value=1234&mobile.value=13000000000 -------------------------------------------------- ------------------------------ Code analysis: . 1 , the User entity nested Mobile, Nickname, Token entity 2 , focus User Experience entity reLogin, register implementation and realization of duplicated code

summary

The property changes of objects are all caused by specific behaviors in the real world. The user's re-login leads to changes in the login time and token, and the methods of encapsulating these behaviors into specific objects can achieve reality through the recombination of these object methods. Different behavioral requirements in the world, such as the implementation process of reLogin and register; pay attention to the method to be small enough, each method only focuses on one thing, only the method is small enough, can be combined into more and more complex methods to achieve a variety of Business requirements, this is also the meaning of single responsibility, single responsibility not only applies to classes, but also class methods;

The essence of object-oriented is to divide and conquer. The premise of implementing branching ideas is that components shield internal complexity and low coupling between components. If low coupling is not based on low coupling, the complex relationships in internal errors are exposed to the upper layer, resulting in interconnected components. , Which reduces the independence of components and makes it difficult to reuse components.

Sometimes a method written using OOP has only a few lines, or even one line. Although not all methods need to be so small, in the case of OOP, the upper limit of a method should be 20 or 30 lines. --Excerpt from "How does object-oriented work"

postscript

Entity methods are pure memory operations, no return value, no state

Careful readers will find that the Captcha.generateValue, Captcha.generateContent, User.reLogin, User.register entity functions have no return value. Object methods operate on the properties of the object according to business logic. If there is a return value, you need to consider the function. The applicable scenario of is correct or incorrect;

Note: Stateful refers to the existence of various problems such as dependencies and data storage, while statelessness is the opposite

Thinking: Does each attribute need to implement an entity, such as adding a user name attribute, and nesting a Name entity in the User entity;

This is also done in real development. For example, the id attribute is also an independent Id entity, which is too troublesome and academic;
reference standards: Mobile, Token, Nickname entities

Misunderstanding: The three characteristics of object-oriented are "encapsulation, polymorphism" and "inheritance"

"How does object-oriented work" clearly define classes, polymorphism and inheritance as programming structures:
classes, polymorphism and inheritance should be clearly defined as structures that can improve the maintainability and reusability of software.
Classes are used to aggregate variables and subroutines to create highly independent components;
polymorphism and inheritance are used to eliminate duplicate codes and create highly versatile components.

Distinguish between business processes and business rules: business rules have if/else, business processes do not!

This problem has been plagued for many years. You can think about it this way. If there is a branch statement indicating that there is a logical judgment based on business rules, the logical judgment should be written in the corresponding object method;

Note: The business process is not absolutely without if/else, which will be explained below.

Service responsibilities: 1. Business process 2. Creation and destruction of entity objects (life and death) public class CaptchaService { public void send (Captcha captcha) { //Generate verification code information (the first step of the business process) captcha.generate(); //Send SMS (the second step of the business process) sendSMS(captcha); //Destruction of the entity object save(captcha); } } public class UserService extends BaseService < User > { UserRepository repository; public User loginOrRegister (User user) { //Whether the phone number is registered User u = repository.findByMobile(user.getMobile()); //Creation of entity object //How does the if statement appear in the business process here, can be understood like this, this if is the precondition of the business process, //has no effect on the logical processing of the specific business reLogin and register. if (u != null ) { //log in again u.reLogin(); //Destruction of the entity object save(u); return u; } //New User Registration user.register(); //Destruction of the entity object save(user); return user; } } The controller is responsible for service orchestration: public class UserController extends BaseController { UserService userService; CaptchaService captchaService; public Response login ( @Valid Captcha captcha, User user) { //Controller usage: get information from HTTP request, extract parameters, and distribute to different processing services //Whether the verification code is correct (call CaptchaService service) captchaService.check(captcha); //User login or registration (call UserService service) user = userService.loginOrRegister(user); return success(user); } } Copy code

The entity method is the operation of the specific logic of the entity attribute, generally speaking: add, delete and modify

Query is a different display form of data, which is not included in the entity method. It is directly implemented by calling the Dao layer in the Service layer.

4.models of blood loss, anemia, congestion and bloating

1, blood loss model
bleeding model, entities may only get set method for pure data class attribute, all business logic entirely by the Service layer to complete
2, Anemia Model
anemia model, the entity comprising atomic art does not rely on persistence Logic, and combinatorial logic is in the Service layer.
3. Congestion model In the
congestion model, most business logic should be placed in the entity, including persistence logic, while the Service layer is a very thin layer, which only encapsulates transactions and a small amount of logic, and does not interact with the DAO layer.

Note: The code in the text is an anemia model

Automatically generate model for each language version

Parameter issues: 1. Models of various language versions are automatically spliced to generate URL parameter strings, such as php's http_build_query 2. Model is directly converted to json, json is used as a parameter, and server-side json is mapped to model

Off topic