Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Single responsibility

There should be only one reason for a class to change, and that is that it should have a single responsibility and not have different responsibilities. The more responsibilities a class has, the more coupling it is and the less likely it is to be reused. For example, we commonly used user management class, which contains login, logout and other activities. In login and logout, one needs to write user information to the cache, and the other needs to clear user information from the cache. So given the single responsibility, it is clear that the specific method implementation of the write and read cache is not in this class. The UserManager class only cares about login and logout, and the cache is left to another class implementation. The code looks like this:

Public class UserManager {/** * login */ void login(){httputil.login (new Callback(){@override void onSuccess(User User){ Usercache.saveuser (user); }}); } /** * logout */ void logout(){httputil.logout (new Callback(){@override void onSuccess(){// The user is removed from the cache after logout UserCache.clearUser(); }}); }}Copy the code
public class UserCache { static void saveUser(User user){ Cache.getInstance().saveUser(user); } static void clearUser(){ Cache.getInstance().clearUser(); }}Copy the code

SaveUser and clearUser are implemented in separate cache classes rather than directly in UserManager, and the single responsibility is intended to make them less coupled and easier to maintain.

The open closed principle

A module or class should be open for extension and closed for modification. This means that you should not change an existing class, but extend it to add functionality. For example, the production of a mobile phone is to have a mobile phone screen, if you want to add a mobile phone case on the line, but should not remove my screen, and in the code to use inheritance or interface to achieve.

Richter’s substitution principle

Wherever a parent class appears, a subclass should be able to replace the parent class without errors or exceptions. That is, the behavior and properties of the subclass should be consistent with the parent class, and the parent class can be extended and supplemented. That sounds like an extension of the open and closed principle. Suppose that in the UserCache, the existing UserCache caches user information to the SD card. Now if the requirement is changed to cache a copy of user information to the sp in addition to the SD card. Instead of modifying the UserCache directly, you can replace the initialization of UserCahe directly on the calling side (instead of abstracting the cache as an interface, which will be discussed later).

UserCache UserCache = new UserCache(); public class UserCache { void saveUser(User user){ Cache.getInstance().saveUser(user); } void clearUser(){ Cache.getInstance().clearUser(); }}Copy the code

Is amended as:

SpAndSdCache userCache = new SpAndSdCache(); public class SpAndSdCache extends UserCache{ @Override void saveUser(User user) { super.saveUser(user); } @override void clearUser() {super.clearuser (); // Clear the cache}}Copy the code

Dependency inversion principle

High-level modules should not depend on low-level modules, but should depend on abstractions. Abstraction should not depend on details, details should depend on abstractions. “Program to the interface, not to the implementation.” Rely on reverse principles dictate that we pass parameters in the program code or in the relationship, cited the high level abstraction layer class as far as possible, that is using an interface and an abstract class variable type declarations, parameter type declaration, the method return type declarations, as well as the data type conversion, etc., rather than a concrete class to do this. To ensure that this principle applies, a concrete class should only implement methods declared in the interface or abstract class, and should not give out redundant methods that would otherwise fail to call new methods added in the subclass. For example, in the UserManager user management class above, the login and logout methods are directly obtained by the UserManager instance. In this case, our high-level module, namely the login interface, is also a detail implementation class. The login function relies on the UserManager entity class. It relies on detail rather than abstraction. Once the UserManager changes, or we use another user management class, the login methods here will all change. To do this, we can change the dependency on UserManager to dependency abstraction:

Public class UserManager implements IUserManager{/** * implements IUserManager */ @override public void login(){httputil.login (); } @override public void logout(){httputil.logout (); UserManager = userManager.getInstance (); userManager.login();Copy the code
Implementation method of dependency inversion principle:
  • Each class tries to provide an interface, an abstract class, or both.
  • Variables should be declared as interfaces or abstract classes.
  • No class should derive from a concrete class.
  • Follow the Richter substitution principle when using inheritance.

Interface Isolation Principle

The dependency of one module on another should be based on the smallest interface, it should not depend on the interface that is not needed, different functions should be abstracted into different interfaces. When an interface becomes too large, consider whether to split the interface into different modules, which is actually a supplement to a single responsibility. For example, clients can use SP key-value caching and SD card caching, and might design interfaces like this:

Public interface ICacheManager {// SD card cache void sdCache(); Void spCache(); } public class UserManager implements ICacheManager{@override public void sdCache() {} @override public void spCache() { } }Copy the code

There is a drawback here, if a caller needs sp cache or SD card cache there is no need to implement another interface. In order to comply with the connection isolation principle, we can split the interface into two methods, which method the caller needs to implement the corresponding interface:

// SD card cache public interface ISdCacheManager {void sdCache(); } public interface ISpCacheManager {void spCache(); } / / caller public class UserManager implements ISdCacheManager, ISpCacheManager {@ Override public void sdCache () {} @Override public void spCache() { }Copy the code
Implementation of interface isolation principle
  • Keep interfaces small, but limited. An interface serves only one submodule or business logic.
  • Customize services for classes that depend on interfaces. Provide only the methods needed by the caller and mask those that are not.
  • Understand the environment and refuse to follow blindly. Each project or product has selected environmental factors, and depending on the environment, the criteria for interface separation will vary to gain insight into the business logic.
  • Improve cohesion and reduce external interactions. Make the interface do the most with the fewest methods.

Demeter’s rule

Also known as the rule of minimum knowledge, a module should be less dependent on other modules and less coupled. For example, we designed the user management class UserManager to manage login, logout and other operations, but the specific login and logout write, clearing user cache function is unknown to the caller.

conclusion

In fact, all the design principles and design patterns are to make the system can be more easy to maintain, expand. Reduce coupling, improve development efficiency, and make code more readable. When we are programming, we should always discipline ourselves with these principles and develop good coding habits.