Home  >  Article  >  Java  >  Seven principles followed by Java design patterns

Seven principles followed by Java design patterns

高洛峰
高洛峰Original
2016-12-12 13:50:051517browse

In recent years, people have been actively advocating and using design patterns. The fundamental reason is to achieve code reusability and increase code maintainability. The implementation of design patterns follows some principles to achieve the purpose of code reusability and increase maintainability. Design patterns are very inspiring for understanding the three major characteristics of object-oriented. Without looking at design patterns, it is difficult to understand them in depth. to the benefits of object-oriented development. At the beginning of learning, it is difficult to integrate these modes, so this requires us to think more before coding, and then start practicing coding when we have thought enough. The following are the seven principles that design patterns should follow

1. Open Close Principle

Definition: A software entity such as a class, module, and function should be open for extension and closed for modification.

The open-closed principle means that when you design, you should always consider and try to make this class good enough. Don’t modify it once it is written. If new requirements come, we can just add some classes and that’s it. The original code doesn't move if it can. This principle has two characteristics, one is "open to extension" and the other is "closed to change". In the face of demand, changes to the program are made by adding new code rather than changing existing code. This is the spirit of the "open-closed principle"

For example, at the beginning, the requirement was just to write an addition program. Soon after it was completed in the client class, the change did not occur at this time. The requirement was to add a subtraction function. At this time, you will find Adding functionality requires modifying the original class, which violates the open-closed principle, so you should consider refactoring the program, adding an abstract operation class, and isolating specific additions and additions through some object-oriented means, such as inheritance, dynamics, etc. Subtraction is coupled with the client, so needs can still be met and changes can be handled. At this time, if you need to add multiplication and division functions, you don't need to change the client and addition and subtraction classes. Instead, just add multiplication and division subclasses.
Absolute modification closure is impossible. No matter how 'closed' the module is, there will be some changes that cannot be closed. Since it is impossible to be completely closed, the designer must decide which changes the module he designs should be closed. Out of choice. He must first guess at the kinds of changes that are most likely to occur, and then construct abstractions to isolate those changes. When we initially write code, we assume that changes won't happen, and when they do, we create abstractions to isolate the same kind of changes in the future.

What we want is to know about possible changes soon after the development work begins. The longer you wait to find out what changes may have occurred, the harder it is to create the correct abstraction. The open-closed principle is the core of object-oriented design. Following this principle can bring about the huge benefits claimed by object-oriented technology, namely maintainability, scalability, reusability, and flexibility. Developers should abstract only those parts of the program that are subject to frequent changes. However, it is also not a good idea to deliberately abstract every part of the application. Rejecting immature abstractions is as important as the abstraction itself. The open-closed principle can ensure the correctness of the previous code. Because the previous code has not been modified, it can ensure that developers focus on placing designs on the newly expanded code.

To put it simply as a classic saying: The past has become history and cannot be modified, because time cannot be turned back, but what you plan to do now or tomorrow can be decided by yourself (that is, expanded).

2. Liskov Substitution Principle

Definition 1: If for every object o1 of type T1, there is an object o2 of type T2, so that all programs P defined with T1 are in all When objects o1 are replaced with o2, the behavior of program P does not change, then type T2 is a subtype of type T1.

Definition 2: Subtypes must be able to replace their parent types.
Description: If a software entity uses a parent class, it must be applicable to its subclasses, and it cannot detect the difference between parent class objects and subclass objects. In other words, in the software, all parent classes are Replacing it with its subclass, the behavior of the program does not change
Example: In biological classification, penguin is a kind of bird, but in the programming world, penguin cannot inherit bird. In object-oriented design, the subclass has all the non-private behaviors and attributes of the parent class. Birds can fly, but penguins cannot fly, so penguins cannot inherit birds.

Only when the subclass can replace the parent class and the function of the software unit is not affected, the parent class can be truly reused, and the subclass can also add new behaviors on the basis of the parent class. It is precisely the Richter generation that The exchange principle makes inheritance and reuse possible. It is precisely because of the replaceability of subtypes that modules using parent class types can be extended without modification. Otherwise, there is no need to talk about opening for expansion and closing for modification. In layman's terms, the Liskov substitution principle is: subclass A class can extend the functionality of the parent class, but cannot change the original functionality of the parent class. It contains the following 4 levels of meaning:

1. Subclasses can implement abstract methods of the parent class, but cannot override non-abstract methods of the parent class.

2. Subclasses can add their own unique methods.

3. When a method of a subclass overrides a method of a parent class, the preconditions of the method (that is, the formal parameters of the method) are looser than the input parameters of the parent class method.

4. When a method of a subclass implements an abstract method of the parent class, the postconditions of the method (i.e., the return value of the method) are more stringent than those of the parent class.

It seems incredible, because we will find that we often violate the Liskov substitution principle in our own programming, but the program still runs well. So everyone will have this question, what will be the consequences if I insist on not following the Liskov substitution principle?

The consequence is: the chance of problems with the code you write will greatly increase.

3. Dependence Inversion Principle

Definition: High-level modules should not depend on low-level modules, both should rely on their abstractions; abstractions should not depend on details; details should depend on abstractions. That is, program for the interface, not for the implementation

Dependency inversion actually means that no one should rely on anyone. Except for the agreed interface, everyone can be flexible and free. Dependency inversion can be said to be a sign of object-oriented design. It does not matter which language is used to write the program. If when writing, you consider how to program for abstraction instead of programming for details, that is, all dependencies in the program end in abstraction. Class or interface is object-oriented design, otherwise it is procedural design. If the various components or classes of the design depend on each other, the degree of coupling will be high and it will be difficult to maintain and expand, which will not reflect the benefits of object-oriented.

The principle of dependency inversion is like a team, which has a demand group, a development group, and a testing group. The development group and the testing group all face the same needs and do their own corresponding work, instead of the testing group understanding the development group. To make test cases according to the requirements, that is to say, the development team and the test team work directly towards the requirements group. Everyone's purpose is the same, to ensure that the product is launched on time, and the requirements do not depend on development and testing.

The principle of dependency inversion is based on the fact that abstract things are much more stable than the variability of details. An architecture built on abstraction is much more stable than an architecture built on details. In Java, abstraction refers to interfaces or abstract classes, and details are specific implementation classes. The purpose of using interfaces or abstract classes is to formulate specifications and contracts without involving any specific operations, leaving the task of showing the details to Their implementation class to complete.

The central idea of ​​the dependency inversion principle is interface-oriented programming. There are three ways to transfer dependencies. The above is interface transfer. There are also two transfer methods: constructor method transfer and setter method transfer. I believe I have used the Spring framework. Yes, you will definitely be familiar with the dependency delivery method.

In actual programming, we generally need to do the following three points:

Low-level modules should have abstract classes or interfaces, or both.

The declared type of the variable should be an abstract class or interface as much as possible.

Follow the Liskov substitution principle when using inheritance.

In short, the principle of dependency inversion requires us to program for interfaces. If we understand interface-oriented programming, we will also understand dependency inversion.

4. Interface Segregation Principle

The meaning of the Interface Segregation Principle is: establish a single interface, do not build a huge and bloated interface, try to refine the interface as much as possible, and have as few methods in the interface as possible. In other words, we need to establish dedicated interfaces for each class, rather than trying to build a huge interface for all classes that rely on it to call. In programming, it is more flexible to rely on several dedicated interfaces than to rely on one comprehensive interface. Interfaces are "contracts" set externally during design. By decentrally defining multiple interfaces, we can prevent the spread of external changes and improve the flexibility and maintainability of the system.

Speaking of which, many people will think that the interface isolation principle is very similar to the single responsibility principle, but it is not. First, the single responsibility principle originally focused on responsibilities; while the interface isolation principle focused on the isolation of interface dependencies. Secondly, the single responsibility principle mainly constrains classes, followed by interfaces and methods, and it targets the implementation and details of the program; while the interface isolation principle mainly constrains interfaces, mainly for abstraction, and for the construction of the overall framework of the program.

When using the interface isolation principle to constrain interfaces, pay attention to the following points:

1. Keep the interface as small as possible, but within limits. It is a fact that refining the interface can improve programming flexibility, but if it is too small, it will cause too many interfaces and complicate the design. So it must be done in moderation.

2. Customize services for classes that rely on interfaces, exposing only the methods it needs to the calling class, and hiding the methods it doesn’t need. Only by focusing on providing customized services for a module can minimal dependencies be established.

3. Improve cohesion and reduce external interaction. Make the interface use the fewest methods to accomplish the most things.

Use the interface isolation principle and it must be moderate. It is not good to design the interface too large or too small. When designing interfaces, only by spending more time thinking and planning can you accurately implement this principle.

4. The principle of combination/aggregation reuse

means to use composition and aggregation as much as possible instead of inheritance relationships to achieve the purpose of reuse
This principle is to use some existing objects in a new object to make it Become part of new objects: New objects achieve the purpose of reusing existing functions by delegating to these objects.
In fact, the final thing here is to distinguish the difference between "has-a" and "is-a". Compared with composition and aggregation, the disadvantage of inheritance is that all methods of the parent class are exposed to the child class. If the parent class changes, the subclass must also change. When reusing aggregates, they have less dependence on other classes. .
Synthesis/aggregation reuse
① Advantages:
The only way for a new object to access component objects is through the interface of the component object;
This kind of reuse is black box reuse, because the internal details of the component objects are invisible to the new object ;

This kind of reuse supports packaging;

This kind of reuse requires fewer dependencies;
Each new class can focus on one task;
This kind of reuse can be done dynamically at runtime, new Objects can use composition/aggregation relationships to delegate new responsibilities to the appropriate objects.
② Disadvantages:
The system built through reuse in this way will have more objects to manage.

Inheritance reuse

① Advantages:
New implementation is easier, because most functions of the base class can automatically enter the derived class through the inheritance relationship;
It is easier to modify or extend the inherited implementation.
② Disadvantages:
Inheritance reuse destroys packaging, because inheritance exposes the implementation details of the base class to the derived class. This kind of reuse is also called white-box reuse;
If the implementation of the base class changes, then the implementation of the derived class It also has to change;
The implementation inherited from the base class is static, cannot be changed at runtime, and is not flexible enough.
6. Law of Demeter

The basic idea of ​​Law of Demeter is to emphasize the loose coupling between classes. The weaker the coupling between classes, the more conducive to reuse. Modification of coupled classes will not affect related classes. In other words, information hiding promotes software reuse.

Since we have been exposed to programming, we have known the general principles of software programming: low coupling and high cohesion. Whether it is process-oriented programming or object-oriented programming, only by keeping the coupling between modules as low as possible can the code reuse rate be improved. The advantages of low coupling are self-evident, but how can we achieve low coupling through programming? That's exactly what Demeter's Law is meant to accomplish.

Dimit’s Law, also known as the least known principle, was first proposed in 1987 by Ian Holland of Northeastern University in the United States. In layman's terms, the less a class knows about the classes it depends on, the better. In other words, for the dependent class, no matter how complex the logic is, the logic should be encapsulated inside the class as much as possible, and no information will be leaked to the outside except the public methods provided. Demeter's Law has a simpler definition: only communicate with direct friends. First, let’s explain what a direct friend is: each object will have a coupling relationship with other objects. As long as there is a coupling relationship between two objects, we say that the two objects are friends. There are many ways of coupling, such as dependency, association, combination, aggregation, etc. Among them, we call classes that appear in member variables, method parameters, and method return values ​​direct friends, while classes that appear in local variables are not direct friends. In other words, it is best not for unfamiliar classes to appear inside the class as local variables.

To sum it up in one sentence: an object should keep the least knowledge about other objects.

7. Single Responsibility Principle

Definition: Do not have more than one reason for class changes. In layman's terms, that is, a class is only responsible for one responsibility, and there should be only one reason for its change

When it comes to the single responsibility principle, many people will dismiss it. Because it's too simple. Even if a slightly experienced programmer has never read design patterns or heard of the single responsibility principle, they will consciously abide by this important principle when designing software because it is common sense. In software programming, no one wants to cause other functions to malfunction by modifying one function. The way to avoid this problem is to follow the single responsibility principle. Although the Single Responsibility Principle is so simple and considered common sense, even programs written by experienced programmers will have code that violates this principle. Why would such phenomenon happen? Because there is a proliferation of responsibilities. The so-called responsibility diffusion means that for some reason, responsibility P is divided into finer-grained responsibilities P1 and P2.

The advantages of following the single responsibility principle are:

1. It can reduce the complexity of the class. A class is only responsible for one responsibility, and its logic is definitely much simpler than responsible for multiple responsibilities;

2. Improve the reliability of the class Readability and improve the maintainability of the system;

3. Reduce the risk caused by changes. Changes are inevitable. If the single responsibility principle is followed well, when one function is modified, the impact on other functions can be significantly reduced.

One thing that needs to be explained is that the single responsibility principle is not unique to object-oriented programming. As long as it is modular programming, this important principle needs to be followed.


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn