Dependency injection and Unity (I) Introduction

Source: Internet
Author: User
Before learning dependency injection and unity, you need to understand why you want to use them. To understand why you want to use them, you should understand what types of dependency injection and unity can help you solve. As an introduction, this chapter does not involve much about unity and dependency injection, but provides necessary background information to help you understand the advantages of dependency injection, and how unity works. Chapter 2 "dependency injection" shows how dependency injection solves the requirements mentioned in this chapter. Chapter 3 "unity and dependency injection" shows how unity implements dependency injection in your program. MotivationWhen designing and developing software systems, you have many requirements to consider. Some requirements are special, while others have universal purposes. These requirements can be divided into functional requirements and non-functional requirements (quality attributes ). Each system has a set of different requirements. The non-functional requirements mentioned above are a set of general requirements, especially industrial software systems with relatively long life cycles. Non-functional requirements are not important in all developed systems, but you can be sure to include these non-functional requirements in the requirements list of many systems you develop. MaintainabilityAs the system grows larger and the expected lifecycle of the system grows, maintenance of such a system becomes increasingly challenging. Generally, the original developer of the system is no longer there, or has forgotten the details of the system. The document has expired or is lost. At the same time, quick response is required in the business to meet some new and urgent needs. Maintainability is a quality indicator of the software system. It determines the degree to which the system can be updated easily and effectively. If you find a defect that must be modified, you need to update the system (that is, execute Corrective maintenance); If the operating environment changes, you need to modify the system, or add new features to the system to meet a business requirement (that is Improved maintenance). A maintainable system enhances the flexibility of the architecture and reduces costs. Therefore, you should include maintainability and other such as stability, security, and scalability in your design goals. TestabilityA testable system is such a system that allows you to test each part of the system separately. Designing and writing effective tests is just as challenging as designing and writing testable program code, especially when a system grows bigger and more complex. Methodology such as test-driven development (TDD) requires you to write unit tests before writing any code to implement a new feature, and such a design technology (TDD) the goal is to improve the quality of your program. This design technology can also help you expand the coverage of your unit test, reduce the possibility of regression, and make refactoring easier. However, as part of the testing process, other types of testing should also be included, such as acceptance testing, integration testing, performance testing, and stress testing. It also takes time and money to run the test, because the requirement needs to be tested in the real environment. For example, some types of tests are performed on cloud-based applications. You need to deploy the applications to the cloud environment and run the tests on the cloud. If you use TDD, it is impractical to run all the tests on the cloud at any time because of the time it takes to deploy applications on the cloud, or even local simulators. In this type of scenario, you may decide to use copies (simple stubs or verifiable simulation) to replace the real components for testing in the cloud environment, this allows you to run your unit test suite in an isolated environment during the standard TDD development cycle. Testability should be another design goal of your system, just like maintainability and agility: a testable system is more maintainable, and vice versa. Flexibility and scalabilityFlexibility and scalability are the quality attributes that enterprise applications often need. Because the business needs change frequently in the development stage or after running as a product, the program design should be flexible so that the program can be applied in different ways of work, and scalable, so that the program can add new features. For example, you may need to switch the program from the computer of the user or organization's business place to the cloud. Late bindingIn some applications, you may need to support late binding. If you want to replace some functions of a program without re-compiling, binding later is very useful. For example, your program may support multiple relational databases. Each database corresponds to an independent module. You can use the configuration file to tell the program to use the specified database module during running. Another very useful scenario of late binding is to allow users of the Program System to customize the program system in the form of plug-ins. Parallel DevelopmentWhen you are developing large (or even small or medium-sized) systems, it is impractical for the entire development team to develop a function or component at the same time. In fact, you will allocate different functions or components to different groups for parallel development. Although this method can shorten the project duration, it also introduces additional complexity: You need to manage multiple groups and properly integrate modules developed by different groups. Cross-focusEnterprise applications usually need to focus on a series of cross-considerations such as verification, exception handling, and logs. You may need to use these features in many places of the program and will implement them in a standard continuous manner to improve the maintainability of the system. Ideally, you want to find a mechanism that effectively and clearly adds behavior to an object at design or runtime without modifying the existing class. Generally, you need to flexibly configure these functions when the program is running, and in some cases, add functions to the existing system to solve the cross-focus problem. Loose couplingIf you can ensure that the program is loosely coupled in the design, you can solve many of the requirements mentioned above. Loose coupling, relative to tight coupling, means that it reduces the number of dependencies between components that constitute the system. This makes it easier and safer to modify a part of the system, because each part of the program is independent from other parts of the program in a large program. A simple exampleThe following is an example of tight coupling. The managmentcontroller class directly depends on the tenantstore class. These classes can be distributed across different visusal studio projects.
 1 public class TenantStore 2 { 3     ... 4     public Tenant GetTenant(string tenant) 5     { 6         ... 7     } 8     public IEnumerable<string> GetTenantNames() 9     {10         ...11     }12 }13 14 public class ManagermentController15 {16     private readonly TenantStore tenantStore;17     public ManagermentController()18     {19         tenantStore = new TenantStore(...);20     }21     public ActionResult Index()22     {23         var model = new TenantPageViewData<IEnumerable<string>>24             (this.tenantStore.GetTenantNames())25             {26                 Title = "Subscribers";27             };28             return this.View(model);29     }30     31     public ActionResult Detail(string tenant)32     {33         var contentModel = this.tenantStore.GetTenant(tenant);34         var model = TenantPageViewData<Tenant>(contentModel)35         {36             Title = string.Format("{0} details",contentModel.Name)37         };38         return this.View(model);39     }40     41     ...42 }

 

In this example, the tenantstore class implements a repository that processes Access to data storage such as relational databases. The managmentcontroller class is an MVC controller class that requests data from the repository. Note: Before calling gettenant and gettenantnames, managmentcontroller must instantiate a tenantstore object or obtain a reference to a tenantstore object. Managmentcontroller depends on specific tenantstore classes. If you review some of the general requirements of enterprise applications mentioned earlier in this chapter that are worth introducing, you will evaluate the above Code to better fit these requirements. Although this simple example only shows how to use the tenantstore class for a client class (in this example, the client class is the managemntcontroller class), The tenantstore class may be used in many client classes in the program. Assuming that each client class is responsible for initializing or locating the tenantstore object at runtime, all these client classes are bound to a constructor or Initialization Method of the tenantstore class, when the implementation of the tenantstore class changes, the classes of all clients may also change accordingly. This may make tenantstore class maintenance more complex, more error-prone, and more time-consuming. To run a unit test on the index and detail methods of the managmentcontroller class, the tenantstore object must be initialized and the underlying data storage must contain suitable test data. This complicate the test process and relies on the data storage in use, which may consume more time during the test, because the correct data in the data storage must be created and operated. This also makes the test more vulnerable. You can change the implementation of the tenantstore class to use different data storage methods, such as replacing SQL Server with Windows azure table Storage. However, the client classes that use the tenantstore class may also need to be modified if these client classes need to provide some initialization data such as the connection string. In this way, you cannot use late binding because the client class is directly compiled by using the tenantstore class. If you want to add support for cross-concerns, such as adding the log function for multiple storage classes (including tenantstore classes), you need to modify and configure each storage class separately. The following example shows a small change. The constructor of the client-class managmentcontroller receives an object that implements the itenantstore interface, and the tenantstore class implements this interface.
 1 public interface ITenantStore 2 { 3     void Initialize(); 4     Tenant GetTenant(string tenant); 5     IEnumerable<string> GetTenantNames(); 6     void SaveTenant(Tenant tenant); 7     void UploadLogo(string tenant, byte[] logo); 8 } 9 10 public class TenantStore:ITenantStore11 {12     ...13     public TenantStore()14     {15         ...16     }17     ...18 }19 20 public class ManagmentController:Controller21 {22     private readonly ITenantStore tenantSotre;23     public ManagmentController(ITenantStore tenantSotre)24     {25         this.tenantSotre = tenantSotre;26     }27     28     public ActionResult Index()29     {30         ...31     }32     33     public ActionResult Detail(string tenant)34     {35         ...36     }37     ...38 }

This change has a direct impact on meeting the non-functional requirements listed above. Now it is clear that the managmentcontroller class and any other client classes using the tenantstore class are not responsible for instantiating tenantstore objects, although this example does not show which class or component is responsible for the instantiation of the tenantstore object. From the maintenance perspective, the responsibility for instantiating a tenantstore object does not belong to multiple classes, but only to one class. Through the parameters of the constructor of the controller class, we can also understand the content that the class depends on, rather than hiding inside the Controller class method. To test some behaviors of the client class, such as the managmentcontroller class, you can create a simple implementation of the itenantstore interface and return some simple data. Instead of creating a tenantstore object that needs to obtain data from the lower-level data storage. The introduction of the itenantstore interface makes it easier to replace the tenantstore implementation without modifying the client class. If the interface and its implementation belong to different projects, the project that contains the client class only needs to reference the project that contains the interface. Classes that instantiate tenantstore objects can provide additional services for programs. This class can control the lifecycle of the itenantstore object created by it. For example, every time a tenantsotre instance is required for the managmentcontroller class, a new tenantsotre object is created, or, only one independent tenantsotre instance is maintained. When the client class needs it, the instance reference is passed to the client class. Now you can bind later, because the client class only references the itenantstore interface type. The program can create an object (probably based on a configuration file) that implements this interface during running and pass it to the client class. For example, a program may create a sqltenantstore instance or blogtenantstore instance based on a configuration in the web. config file, and then pass it to the constructor of the managmentcontroller class. If the interface definition is agreed, the two project teams can work in parallel on the Development store class and the contoller class. The class responsible for creating a store-class instance can add support for cross-focus before passing the store instance to the client class, for example, passing in an object that implements cross-focus by using the decoration mode. You do not need to modify the client class or store class to add support for cross-focus such as log or exception handling. The second code above uses a loose coupling design using interfaces. Without direct reference between the two categories, hierarchical coupling is reduced, and the maintainability, flexibility, testability, and scalability of the solution are enhanced. When will the loosely coupled design be used?Before learning dependency injection and unity, you should understand where the program needs to consider loose coupling and use interface programming to reduce the dependency between classes. In the previous section, we first mentioned the need for maintainability, which would indicate when we should reduce coupling at what point in the program. Generally, the larger the more complex the program, the more difficult it is to maintain, so these technologies are useful, regardless of the type of the program is desktop application, web application or cloud application. At first glance, it seems to be contrary to intuition. The second example above introduces the interface, but the first one does not. The second example also requires a small amount of content that we did not show above, that is, the client class is responsible for instantiating and managing objects. For a small example, these technologies seem to increase the complexity of the solution, but when a program grows bigger and more complex, this bottleneck will become increasingly unimportant. The previous example shows us another common point about where to use these technologies. Generally, the managmentcontroller class exists in the user interface layer of the program, while the tenantstore class is part of the data access layer. This example shows a general method of programming, so that when a layer needs to be replaced, it will not interfere with other layers in the future. For example, replacing or adding a new UI (for example, creating a mobile platform app outside the traditional Web UI) without changing the data layer or replacing the underlying data storage mechanism, you do not need to change the UI Layer. You can design programs in a layered manner to decouple each part. You should be aware of the areas where programs may change in the future. To minimize and localize these changes, you should decouple them from other parts of the program. Object-Oriented Design PrinciplesFinally, before learning dependency injection and unity, describe the Five Principles of object-oriented programming and design, that is, solid. The so-called solid is the acronyms of the following five principles:
1. SIngle Responsibility Principle: single Responsibility Principle 2. OPEN/Close principle: open and closed principle 3. LIskov substitution principle: Lee's replacement principle 4. INterface segregation principle: interface separation principle 5. DEpendency inversion principle: Dependency inversion principle Single Responsibility PrincipleTo change a class, there is only one reason. That is to say, a class only implements one function. In the first example above, the managmentcontroller class has two responsibilities: As the UI controller, instantiate tenantstore, and manage the lifecycle of tenantstore objects. In the second example, the role of instantiating and managing the tenantstore object lifecycle is placed in another class or component in the system. Open and closed PrincipleSoftware entities (classes, modules, functions, etc.) should be open to extensions and closed to modifications. Although you may modify this class to fix a defect, when you need to add new behaviors for this class, you should extend this class. This helps to maintain the maintainability and testability of the Code, because existing behaviors should not be modified, and new behaviors should be placed in the new class. This principle satisfies the need to add cross-focus support in the program. For example, when you add the log function for some classes in the program, you should not modify the implementation of these existing classes. Lee's replacement principleIf S is a subclass of T, T-type objects can be replaced by S-type objects. That is, child classes can replace parent classes. In the second example, if any object implementing the itenantsotre interface is passed to the managmentcontroller class, the class will also work as expected. This example uses interfaces and abstract classes. Interface separation principleA large interface should be divided into multiple small and specific interfaces, so that the client class only needs to know the method to use, the client class should not be forced to rely on methods it does not use. Dependency inversion principle> High-level modules should not depend on low-level modules. Both of them should depend on abstraction. > Abstraction should not depend on details, but on abstraction. In the first example above, the high-level managmentcontroller is directly dependent on the lower-level tenantstore. This usually limits the reuse of this High-level class in another context. In the second example, managmentcontroller depends on the abstraction of itenantstore, and tenantstore also relies on abstraction.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.