Immediate Time-The Beauty of Design Patterns in Practice II (Part 2): How to do an object-oriented analysis on the development of such a function as interface authentication?

In the previous lesson, for the development of interface authentication functions, we talked about how to perform object-oriented analysis (OOA), that is, demand analysis. In fact, after the requirements are clearly defined, this problem has been solved more than half, which is why I spent so much space on the requirements analysis. Today, let's take a look at how to implement object-oriented design (OOD) and object-oriented programming (OOP) for the needs of object-oriented analysis output.

How to carry out object-oriented design?

We know that the output of object-oriented analysis is a detailed description of requirements, and the output of object-oriented design is the class. In the object-oriented design process, we transform the requirement description into specific class design. We disassemble and refine this design link, which mainly includes the following parts:

● Divide the responsibilities to identify the categories;

● Define the class and its attributes and methods;

● Define the interactive relationship between classes;

● Assemble classes and provide entry points for execution.

To be honest, whether it is object-oriented analysis or object-oriented design, there are not many theoretical things, so we still combine the example of authentication to experience how to do object-oriented design in actual combat. 1. Divide the responsibilities and identify the types

1. Divide the responsibilities and identify the types

It is often said in books about object-oriented that a class is a modeling of things in the real world. However, not every requirement can be mapped to the real world, and not every category has a one-to-one correspondence with things in the real world. For some abstract concepts, we cannot define classes by mapping things in the real world.

Therefore, in most object-oriented books, there is another way to identify categories, which is to list the nouns in the requirement description as possible candidate categories, and then filter them. For inexperienced beginners, this method is relatively simple and clear, and can be followed directly.

However, I personally prefer another method, which is to list the function points involved one by one according to the requirement description, and then see which function points have similar responsibilities and operate the same attributes. Should they be grouped into the same class. Let's take a look at the specific example of authentication.

In the previous lesson, we have given a detailed description of the requirements. For your convenience, I reposted it below.

● When the caller makes an interface request, the URL, AppID, password, and timestamp are spliced ​​together, the token is generated through an encryption algorithm, and the token, AppID, and timestamp are spliced ​​in the URL and sent to the microserver.

● After receiving the interface request from the caller, the micro server disassembles the token, AppID, and timestamp from the request.

● The micro server first checks whether the passed timestamp and current time are within the token expiration time window. If the expiration time has elapsed, then the interface call authentication is regarded as a failure, and the interface call request is rejected.

● If the token verification has not expired, the micro-server will take out the password corresponding to the AppID from its own storage, and use the same token generation algorithm to generate another token to match the token passed by the caller. If they are consistent, the authentication is successful and the interface call is allowed; otherwise, the interface call is rejected.

First of all, what we have to do is to read the above requirement description sentence by sentence, disassemble it into small function points, and list them one by one. Note that each function point that is disassembled should be as small as possible. Each function point is only responsible for doing a small thing (professional name is "single responsibility", we will talk about it in later chapters). The following is a list of function points I got after disassembling the above description of requirements sentence by sentence:

  1. Combine URL, AppID, password, and timestamp into one string;

  2. Encrypt the string through an encryption algorithm to generate a token;

  3. Splice the token, AppID, and timestamp into the URL to form a new URL;

  4. Parse the URL to get token, AppID, timestamp and other information;

  5. Retrieve AppID and corresponding password from storage;

  6. Determine whether the token is expired or invalid according to the timestamp;

  7. Verify that the two tokens match;

From the function list above, we find that 1, 2, 6, and 7 are related to tokens, responsible for token generation and verification; 3 and 4 are processing URLs, responsible for URL splicing and parsing; 5 is operating AppID And password, responsible for reading AppID and password from storage. Therefore, we can roughly get the three core classes: AuthToken, Url, CredentialStorage. AuthToken is responsible for the four operations 1, 2, 6, and 7; Url is responsible for the two operations of 3 and 4; CredentialStorage is responsible for the operation of 5.

Of course, this is a preliminary class division. We may not be able to think about the other unimportant, corners and corners at once, but it does not matter. Object-oriented analysis, design, and programming are originally a cycle. Iterative and continuous optimization process. According to the requirements, we first give a rough version of the design plan, and then based on such a foundation, iterative optimization will be easier and the thinking will be clearer.

However, I want to emphasize one more point. The development requirement of interface call authentication is relatively simple. Therefore, the object-oriented design corresponding to the requirement is not complicated, and there are not many recognized classes. But if we are dealing with larger software development and more complex requirements development, there may be many function points involved, and there will be more corresponding classes. The method of listing function points according to the needs as before will finally Get a long list, it will be a little messy and irregular. In response to this kind of complex demand development, what we have to do first is to divide the module, first simply divide the demand into several small, independent functional modules, and then apply the method we just talked about inside the module for object-oriented design. The division and recognition of modules is similar to the division and recognition of classes.

2. Define the class and its attributes and methods

We just identified three core classes by analyzing the requirements description, they are AuthToken, Url and CredentialStorage. Now let's look at what properties and methods each class has. We still dig from the list of function points.

There are four functions related to the AuthToken class:

● Concatenate URL, AppID, password, and timestamp into one string;

● Encrypt the string through an encryption algorithm to generate a token;

● Determine whether the token is expired or invalid according to the timestamp; verify whether the two tokens match.

For method identification, many object-oriented related books generally say this, identifying verbs in demand descriptions as candidate methods, and then further filtering. By analogy with method recognition, we can use the nouns involved in the function points as candidate attributes, and then filter them as well.

We can borrow this idea and identify the attributes and methods of the AuthToken class according to the function point description, as shown below:
Insert picture description here
From the above class diagram, we can find these three small details.

● The first detail: Not all the nouns that appear are defined as attributes of the class, such as URL, AppID, password, and timestamp. We use them as method parameters.

● The second detail: We also need to dig out some attributes that do not appear in the function point description, such as createTime and expireTimeInterval, which are used in the isExpired() function to determine whether the token has expired.

● The third detail: We also added a method getToken() that is not mentioned in the function point description to the AuthToken class.

The first detail tells us that from the business model, attributes and methods that should not belong to this class should not be placed in this class. For example, information such as URL and AppID should not belong to AuthToken from the business model, so we should not put it in this class.

The second and third details tell us that when designing the attributes and methods of a class, we should not simply rely on the current needs, but also analyze what attributes and methods the class should have from the business model. This can ensure the integrity of the class definition on the one hand, and make preparations not only for the current needs but also for the future needs.

There are two functions related to the Url class:

● Splicing token, AppID, and timestamp into the URL to form a new URL;

● Parse the URL to get token, AppID, timestamp and other information.

Although in the description of requirements, we all refer to the interface request by URL, but the interface request is not necessarily expressed in the form of URL, and may be in other forms such as Dubbo, RPC. In order to make this class more universal and more appropriate, we will name it ApiRequest next. The following is the ApiRequest class I designed based on the functional point description.

Insert picture description here

There is one related function point of the CredentialStorage class:

● Retrieve AppID and corresponding password from storage.

The CredentialStorage class is very simple, and the class diagram is shown below. In order to abstract and encapsulate specific storage methods, we designed CredentialStorage as an interface, based on interfaces rather than specific implementation programming.

Insert picture description here

3. Define the interaction between the class and the class

What are the interaction relationships between classes? The relationship between six categories is defined in UML Unified Modeling Language. They are: generalization, realization, association, aggregation, combination, and dependency. There are many relationships, and some are relatively similar, such as aggregation and combination. Next, I will explain them one by one.

Generalization (Generalization) it can be understood as a simple inheritance. The specific Java code is as follows:


public class A {
    
     ... }
public class B extends A {
    
     ... }

Implement (Realization) generally refers to the relationship between the interface and implementation class. The specific Java code is as follows:


public interface A {
    
    ...}
public class B implements A {
    
     ... }

Polymerization (Aggregation) comprising a relationship, A Class B Class object contains the object, the life cycle of the object class B can not rely on the life cycle of the object class A , class A that is can be destroyed without affecting the object individual subject B, such as The relationship between the curriculum and the student. The specific Java code is as follows:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}

Composition (Composition) comprising also a relationship. Class A objects include Class B objects. The life cycle of Class B objects depends on the life cycle of Class A objects. Class B objects cannot exist alone , such as the relationship between birds and wings. The specific Java code is as follows:


public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}

Association (Association) is a very weak relationship, including aggregation and combination. Specific to the code level, if the class B object is a member variable of class A, then class B and class A are associated. The specific Java code is as follows:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}
或者
public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}

Dependency (Dependency) is a weaker relationship than the relationship, including the relationship. Regardless of whether it is a member variable of a class B object or a method of class A that uses a class B object as a parameter or return value or local variable, as long as there is any use relationship between the class B object and the class A object, we call them dependent relationship. The specific Java code is as follows:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}
或者
public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}
或者
public class A {
    
    
  public void func(B b) {
    
     ... }
}

After reading the detailed introduction of the six types of UML relationships, how do you feel? I personally think that this split is a bit too detailed, increases the cost of learning, and does not make much sense for guiding programming development. Therefore, from a programming perspective, I adjusted the relationship between classes and only retained four relationships: generalization, implementation, composition, and dependency, so that it will be easier for you to master.

Among them, the definitions of generalization, implementation, and dependency remain unchanged, and the combination relationship replaces the three concepts of combination, aggregation, and association in UML, which is equivalent to renaming the relationship as a combination relationship, and no longer distinguishes the combination and aggregation in UML. Concepts. The reason for this renaming is to unify the meaning of "combination" in the design principle of "combining more and less inheritance" that we mentioned earlier. As long as the class B object is a member variable of the class A object, then we can say that class A and class B are combined.

Now that the theory is finished, let's take a look at what are the relationships between the classes we just defined? Because there are currently only three core classes, only the implementation relationship is used, that is, the relationship between CredentialStorage and MysqlCredentialStorage. Next, when we talk about assembly classes, we will also use dependency and composition relationships, but generalization relationships are not used for the time being.

4. Assemble the class and provide execution entry

The classes are defined, and the necessary interaction relationships between the classes are also designed. Next, we will assemble all the classes together to provide an execution entry. This entry may be a main() function, or it may be a set of API interfaces for external use. Through this entry, we can trigger the entire code to run.

Interface authentication is not a stand-alone system, but a component that runs on the system. Therefore, we encapsulate all the implementation details and design a top-level ApiAuthenticator interface class to expose a group of external callers. The API interface serves as the entry point for triggering the execution of the authentication logic. The specific class design is as follows:

Insert picture description here

How to do object-oriented programming?

After the object-oriented design is completed, we have clearly defined the interaction between classes, attributes, methods, and classes, and assembled all the classes to provide a unified execution entry. Next, the job of object-oriented programming is to translate these design ideas into code implementation. With the previous class diagram, this part of the work is relatively simple. Therefore, here I only give the implementation of the more complicated ApiAuthenticator.

For the three classes of AuthToken, ApiRequest, and CredentialStorage, I will not give specific code implementations here. Leave a homework for you, you can try to implement the entire authentication framework yourself.


public interface ApiAuthenticator {
    
    
  void auth(String url);
  void auth(ApiRequest apiRequest);
}

public class DefaultApiAuthenticatorImpl implements ApiAuthenticator {
    
    
  private CredentialStorage credentialStorage;
  
  public DefaultApiAuthenticatorImpl() {
    
    
    this.credentialStorage = new MysqlCredentialStorage();
  }
  
  public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage) {
    
    
    this.credentialStorage = credentialStorage;
  }

  @Override
  public void auth(String url) {
    
    
    ApiRequest apiRequest = ApiRequest.buildFromUrl(url);
    auth(apiRequest);
  }

  @Override
  public void auth(ApiRequest apiRequest) {
    
    
    String appId = apiRequest.getAppId();
    String token = apiRequest.getToken();
    long timestamp = apiRequest.getTimestamp();
    String originalUrl = apiRequest.getOriginalUrl();

    AuthToken clientAuthToken = new AuthToken(token, timestamp);
    if (clientAuthToken.isExpired()) {
    
    
      throw new RuntimeException("Token is expired.");
    }

    String password = credentialStorage.getPasswordByAppId(appId);
    AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password, timestamp);
    if (!serverAuthToken.match(clientAuthToken)) {
    
    
      throw new RuntimeException("Token verfication failed.");
    }
  }
}

Dialectical thinking and flexible application

In the previous explanation, object-oriented analysis, design, and implementation, the boundaries of each link are relatively clear. Moreover, the design and implementation are basically translated sentence by sentence according to the description of the function points. The advantage of this is that what to do first and what to do later is very clear, clear, and rule-based. Even junior engineers without much design experience can follow this process step by step to do analysis, design and implementation.

However, in normal work, most programmers often complete object-oriented analysis and design in their heads or on scratch paper, and then start to write code, while writing and thinking while refactoring, and will not strictly follow The process is executed. Moreover, to be honest, even if we spend a lot of time doing analysis and design before writing the code, drawing perfect class diagrams and UML diagrams, it is impossible to think clearly about every detail and interaction. When implementing the code, we still have to iterate, refactor, break and rewrite.

After all, the entire software development is originally a process of iteration, patching, and problem solving, as well as a process of constant reconstruction. We can't strictly follow the steps in order. This is similar to learning a driver’s license. The driving school teaches a more formal process. Do what you do first and then do what you do. You can back up the vehicle smoothly as long as you follow the instructions, but in fact, when you drive proficiently, back up the vehicle. Libraries often rely on experience and feeling.

Guess you like

Origin blog.csdn.net/zhujiangtaotaise/article/details/110379120