Article catalog
preface
There are seven commonly used object-oriented design principles. These seven design principles are based on maintainability and reusability. These principles do not exist in isolation. They are interdependent and complementary. Following these design principles can effectively improve the reusability and maintainability of the system.
Tip: the following is the main content of this article. The following cases can be used for reference
1, Dependency Inversion Principle (DIP)
High level modules should not rely on low-level modules, they should all rely on abstraction; Abstraction should not depend on details, details should depend on abstraction.
The simple definition is: interface oriented (Abstract) programming, not implementation oriented programming.
What is a high-level module? In short, the encapsulation level is high, so we think it is a high-level module. Customer class is a customer class, which contains the unlock phone method. This method needs to pass a XiaoMiPhone mobile phone class to unlock the mobile phone. Then customer class is a high-level module and XiaoMiPhone class is a low-level module.
What is detail? Detail is an instance method. It is a complete, small enough logical unit and a subroutine containing code. What is abstraction? In C #, abstraction is an abstract class (to be exact, it should be an abstract method in an abstract class, because an abstract class can contain instance methods) or an interface. They cannot be instantiated directly. They can only provide instances through subclasses of abstract classes, implementation classes of interfaces or factory methods (containers can also provide instances, but they are still factories in essence). In fact, abstraction cannot rely on details at all, because c# syntax stipulates that abstract methods and interfaces cannot contain implementations, that is, they cannot contain details, which is "abstraction should not rely on details". So what is "details should depend on abstraction"? Details should depend on abstraction, which can be considered as an upgraded version of Richter's substitution principle. It requires that abstract base classes or interfaces be used as method parameters as much as possible.
2, Use steps
Example
public class XiaoMiPhone { public bool Unlock() => true; }
public class Customer { public bool UnlockPhone(XiaoMiPhone phone) => phone.Unlock(); }
var customer = new Customer(); var phone = new XiaoMiPhone(); var lockResult = customer.UnlockPhone(phone);
From the above code, we can clearly see that the Customer class of the high-level module heavily depends on the XiaoMiPhone class of the low-level module, because the UnlockPhone method requires a parameter of the XiaoMiPhone class. One consequence of this strong dependency is that no matter whether the Customer class or XiaoMiPhone class is modified, the caller can not be guaranteed to run correctly, We need to do a complete regression test on these two classes. Another problem is that one day we want to unlock IphoneX and will make large-scale modifications to the above code, which obviously violates the opening and closing principle. Here is a solution for reference:
public interface IMobilePhone { bool Unlock(); }
public class XiaoMiPhone : IMobilePhone { public bool Unlock() { Console.WriteLine("Use fingerprint to unlock your phone!"); return true; } }
public class ApplePhoneX : IMobilePhone { public bool Unlock() { Console.WriteLine("Use Face ID to unlock your phone!"); return true; } }
public class Customer { public bool UnlockPhone(IMobilePhone phone) => phone.Unlock(); }
var customer = new Customer(); IMobilePhone phone = new XiaoMiPhone(); var lockResult = customer.UnlockPhone(phone); phone = new ApplePhoneX(); lockResult = customer.UnlockPhone(phone);
First, establish a contract through imobililephone and provide the Unlock method. XiaoMiPhone and ApplePhoneX classes implement the imobililephone interface. The high-level module Customer no longer depends on a certain mobile phone class, but on the imobililephone interface, that is, the high-level module depends on abstraction. What about low-level modules? The low-level module in this example is a specific mobile phone class, which does not rely on any module. The high-level and low-level modules are relative concepts. In the actual development process, the low-level module ApplePhoneX may rely on other lower level modules to provide more functions. For this lower level module, ApplePhoneX has become its high-level module. After all, ApplePhoneX is "endless and dependent".
Through the above analysis, it is not difficult to find that the original high-level modules rely on low-level modules. After code transformation, they all rely on abstraction, that is, the dependency has shifted, which is the so-called "dependency inversion principle". The way to realize dependency inversion is called Dependency Injection. There are three common Dependency Injection methods: structure injection, setting injection and interface injection.
Note: there is another way of service locator injection, which will be introduced in detail in the related articles of Asp.Net in the future.
Structural injection:
public class Customer { private IMobilePhone _phone = null; public Customer(IMobilePhone phone) { _phone = phone; } public bool UnlockPhone() => _phone.Unlock(); }
Set point injection:
public class Customer { public IMobilePhone Phone { get; set; } public bool UnlockPhone() => Phone.Unlock(); }
Interface injection:
interface IPhoneProvider { IMobilePhone Phone { get; set; } }
public interface IMobilePhone { bool Unlock(); }
public class Custome : IPhoneProvider { public IMobilePhone Phone { get; set; } public bool UnlockPhone() => Phone.Unlock(); }
summary
To sum up, it is not difficult for us to draw a conclusion that injection is the means and dependence inversion is the purpose.