preface
We studied the single responsibility principle and the open closed principle in the SOLID principle. Today, we will look again at the principle of “L” in SOLID: the interior substitution principle.
Overall, this design principle is relatively simple, easy to understand and grasp. Today, I’m going to show you a few examples of code that violates the li substitution principle. How do we modify them to satisfy li’s substitution principle? Other than that, this principle, by definition, looks a little bit like the “polymorphism” we talked about earlier.
How to understand “li substitution principle”?
Liskov Substitution Principle, abbreviated as LSP. This principle was first proposed in 1986 by Barbara Liskov, who describes it this way:
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program.
In 1996, Robert Martin redefined this principle in his SOLID Principles, in English, as follows:
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing The it.
Combining the two descriptions, we describe this principle in Chinese as follows: An object of subtype/derived class can replace an object of base/parent class anywhere in a program, In addition, the logic behavior of the original program is guaranteed to remain unchanged and the correctness is not destroyed.
It’s a little abstract, but let’s do it with an example. In the following code, the parent Transporter class uses the HttpClient class from the org.apache. HTTP library to transport network data. The subclass SecurityTransporter inherits the parent class Transporter and adds additional functions to support the transfer of appId and appToken security authentication information.
public class Transporter {
private HttpClient httpClient;
public Transporter(HttpClient httpClient) {
this.httpClient = httpClient;
}
public Response sendRequest(Request request) {
/ /... use httpClient to send request}}public class SecurityTransporter extends Transporter {
private String appId;
private String appToken;
public SecurityTransporter(HttpClient httpClient, String appId, String appToken) {
super(httpClient);
this.appId = appId;
this.appToken = appToken;
}
@Override
public Response sendRequest(Request request) {
if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) {
request.addPayload("app-id", appId);
request.addPayload("app-token", appToken);
}
return super.sendRequest(request); }}public class Demo {
public void demoFunction(Transporter transporter) {
Reuqest request = new Request();
/ /... Omit the code that sets the data values in the request...
Response response = transporter.sendRequest(request);
/ /... Omit other logic...}}// The substitution principle
Demo demo = new Demo();
demo.demofunction(new SecurityTransporter(/* omit the argument */););
Copy the code
In the code above, the subclass SecurityTransporter is designed to comply fully with the In-substitution principle and can replace the parent class anywhere it occurs without breaking the logic behavior of the original code.
But, you might wonder, isn’t this code simply designed to take advantage of object-oriented polymorphism? Are polymorphism and the li substitution principle the same thing? From the examples and definitions described above, the Li substitution principle and polymorphism do look somewhat similar, but in reality they are completely different things. Why do you say that?
So let’s go back to this example. However, we need to tweak the sendRequest () function in the SecurityTransporter class a bit. Before transformation, if appId or appToken is not set, we will not perform verification; After modification, if appId or appToken not set, the direct selling NoAuthorizationRuntimeException unauthorized anomalies. The code comparison before and after the transformation is as follows:
// Before modification:
public class SecurityTransporter extends Transporter {
/ /... Omit other code..
@Override
public Response sendRequest(Request request) {
if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) {
request.addPayload("app-id", appId);
request.addPayload("app-token", appToken);
}
return super.sendRequest(request); }}// After modification:
public class SecurityTransporter extends Transporter {
/ /... Omit other code..
@Override
public Response sendRequest(Request request) {
if (StringUtils.isBlank(appId) || StringUtils.isBlank(appToken)) {
throw newNoAuthorizationRuntimeException(...) ; } request.addPayload("app-id", appId);
request.addPayload("app-token", appToken);
return super.sendRequest(request); }}Copy the code
In the modified code, demoFunction () does not throw an exception if the Transporter object is passed to it. However, if demoFunction () is passed to a subclass SecurityTransporter object, demoFunction () may get an exception thrown. Although the code throws a Runtime Exception that we can not explicitly catch and handle in the code, the logical behavior of the entire program changes after the subclass replaces the parent class passed into the demoFunction function.
Although the modified code can still use Java’s polymorphic syntax to dynamically replace the parent class Transporter with a subclass SecurityTransporter, the program will not compile or run incorrectly. However, the design of the SecurityTransporter does not comply with the li substitution principle.
review
The Li substitution principle is a principle that guides how subclasses should be designed in inheritance relationships. A: Design by contract b: Design by contract The superclass defines the “conventions” (or protocols) of a function, and the subclass can change the internal implementation logic of the function, but not the original “conventions” of the function.
The conventions here include: function declaration to implement the function; Conventions for inputs, outputs, and exceptions; Even any special instructions listed in the notes.
More original Java reading: javawu.com