Before you learn about dependency injection and Unity, you need to understand why you should use them. And in order to understand why you should use them, you should understand what types of problems dependency injection and Unity are designed to help you address. This introductory chapter will not say much about Unity, or indeed say much about dependency injection, but it will provide some necessary background information that will help you to appreciate the benefits of dependency injection as a technique and why Unity does things the way it does.
在你学习关于依赖注入和Untity之前,你必须明白为什么你应该使用它。和为了明白你为什么应该使用它,你应该明白依赖注入和Unity可以帮你解决些什么样的问题。这个引导章节讲不会讲太多关于Unity,或者说是不会讲太多关于依赖注入,但是引导章节会提供一些必要的背景知识,这将有助于你欣赏依赖注入的技术,和为什么Unity的做事方法。
The next chapter, Chapter 2, “Dependency Injection,” will show you how dependency injection can help you meet the requirements outlined in this chapter, and the following chapter, Chapter 3, “Dependency Injection with Unity,” shows how Unity helps you to implement the dependency injection approach in your applications.
下个章节,章节2“依赖注入”将展示你怎样依赖注入可以帮助你满足本章概述的要求,和下面章节 章节3“依赖注入和Unity”,展示Unity如何帮你实现依赖注入方法在你的程序中。
Motivations
When you design and develop software systems, there are many requirements to take into account. Some will be specific to the system in question and some will be more general in purpose. You can categorize some requirements as functional requirements, and some as non-functional requirements (or quality attributes). The full set of requirements will vary for every different system. The set of requirements outlined below are common requirements, especially for line-of-business (LOB) software systems with relatively long anticipated lifetimes. They are not all necessarily going to be important for every system you develop, but you can be sure that some of them will be on the list of requirements for many of the projects you work on.
动机
当你设计和开发软件系统,考虑到有许多需求。一些特殊的系统问题和更多的普通的目标。你可以分类一些功能要求,和一些非功能要求(或质量属性)。这全套的要求将违反每一个不同的系统。一组要求描述下是普通需求,特别是业务(LOB)软件系统相对较长的预期寿命。他他们不都一定会是重要的对于每一个系统开发,但是你可以确定的是,他们一些将会在你的项目工作上的需求列表中。
This chapter introduces a lot of requirements and principles. Don’t assume that they are all relevant all of the time. However, most enterprise systems have some of the requirements, and the principles all point towards good design and coding practices
这个章节初次接触一些需求和原则。不要假设所有时候他们都是完全关联的。无论如何,大部分企业制度有一些这样的需求和那样的原则都指向好的设计和代码规范。
Maintainability
As systems become larger, and as the expected lifetimes of systems get longer, maintaining those systems becomes more and more of a challenge. Very often, the original team members who developed the system are no longer available, or no longer remember the details of the system. Documentation may be out of date or even lost. At the same time, the business may be demanding swift action to meet some pressing new business need. Maintainability is the quality of a software system that determines how easily and how efficiently you can update it. You may need to update a system if a defect is discovered that must be fixed (in other words, performing corrective maintenance), if some change in the operating environment requires you to make a change in the system, or if you need to add new features to the system to meet a business requirement (perfective maintenance). Maintainable systems enhance the agility of the organization and reduce costs.Therefore, you should include maintainability as one of your design goals, along with others such as reliability, security, and scalability.
可维护性
随着系统变大,和系统的语气寿命边长,维护那些系统就变成了越来越多的挑战。甚至时常,原团队成员谁自主开发的系统不再可用,或者不再记得系统的一些细节。文档也许过期,或者甚至丢失。在同一时间,业务可能要求快速心动去满足一些紧急的新业务需求。可维维护性是西戎软件质量,那决定你可以如何简单的和如何有效的更新他。你也许需要去更新系统,如果缺陷发现必须修理(换句话说,执行维修保养),如果某些操作环境的变化需要你再系统中做出更改,或者如果您需要再系统中添加新功能,以满足业务需求(完成的维护)。可维护的系统增强组织的灵活性和降低成本。你应该包括可维护性作为你一个设计目标,以及其他诸如可靠性、安全性和可伸缩性。
It is very hard to make existing systems more maintainable. It is much better to design for maintainability from the very start.
Unity很难使现有系统更易于维护。Unity是从一开始为可维护性大量更好的设计。
Testability
可测试性
A testable system is one that enables you to effectively test individual parts of the system. Designing and writing effective tests can be just as challenging as designing and writing testable application code, especially as systems become larger and more complex.
一个可测试系统是一个可以使你有效测试系统中的各个部分。设计和编写有效的测试可以正确的设计和编写测试应用代码,特别是系统变大和更加复杂。
Methodologies such as test-driven development (TDD) require you to write a unit test before writing any code to implement a new feature and the goal of such a design technique is to improve the quality of your application.
方法测试驱动的开发(TDD)要求您编写一个单元测试前编写任何代码来实现新功能,这样的设计技术的目标是提高应用程序的质量。
Such design techniques also help to extend the coverage of your unit tests, reduce the likelihood of regressions, and make refactoring easier.
这样的设计技巧也可以帮助你扩展你的单元测试覆盖率,减少回归的可能性,和使重构更简单。
However, as part of your testing processes you should also incorporate other types of tests such as acceptance tests, integration tests, performance tests, and stress tests.
然而,如同部分你的测试过程你应该也包含其他类型的测试比如验收测试、综合测试、性能测试、和压力测试。
Running tests can also cost money and be time consuming because of the requirement to test in a realistic environment.
运行测试也会消耗财富和时间,因为需要在显示环节中测试。
For example, for some types of testing on a cloud-based application you need to deploy the application to the cloud environment and run the tests in the cloud.
比如,一些类型是测试云端应用,你必须配置应用到云端环境,并且在云端运行测试。
If you use TDD, it may be impractical to run all the tests in the cloud all of the time because of the time it takes to deploy your application, even to a local emulator.
如果你有用测试驱动开发,它可能不能再任何时间在云端运行所有的测试,因为在哪个时候它在准备配置你的应用,甚至是本地本地模拟器。
In this type of scenario, you may decide to use test doubles (simple stubs or verifiable mocks) that replace the real components in the cloud environment with test implementations in order to enable you to run your suite of unit tests in isolation during the standard TDD development cycle.
在这个方案类型里面,你可能使用双测试(简单的存根或可核查的模拟)取代真正的组件和测试的实现在云环境中为了使您能够运行单元测试套件在隔离标准的TDD开发周期。
Testability should be another of the design goals for your system along with maintainability and agility: a testable system is typically more maintainable, and vice versa.
可测试性应该是另一个系统的设计目标以及可维护性和敏捷性:一个可测试的系统通常更易于维护,反之亦然。
Using test doubles is a great way to ensure that you can continuously run your unit tests during the development process. However, you must still fully test your application in a real environment.
使用双测试是很好的方式去保证你可以在开发过程中不断的运行你的单元测试。无论如何你必须仍然充分的在现实环境中测试你的应用。
Flexibility and Extensibility
灵活性和可扩展性
Flexibility and extensibility are also often on the list of desirable attributes of enterprise applications.
灵活性和可扩展性也常常是在企业应用理想属性列表中。
Given that business requirements often change, both during the development of an application and after it is running in production, you should try to design the application to make it flexible so that it can be adapted to work in different ways and extensible so that you can add new features.
考虑到业务需求时常改变,双方在开发应用的时候和在运行产品之前,你应该尝试去设计应用,把他做得灵活,所以他可以适应工作在不同的途经和可扩展,所以你可以添加新的特性。
For example, you may need to convert your application from running on-premises to running in the cloud.
比如,你可能需要把你的应用从本地运行变为云端运行。
For a great discussion on the use of test doubles, see the point/counterpoint debate by Steve Freeman, Nat Pryce and Joshua Kerievsky in IEEE Software (Volume: 24, Issue: 3), May/June 2007, pp.80-83.
使用双测试上的伟大探讨,亲看Steve Freeman的point/counterpoint debate(点/对位辩论),Nat Pryce和Joshua Kerievsky IEEE软件(问题:数量:24日3),2007年5月/ 6月pp.80 - 83。
Late Binding
延时绑定
In some application scenarios, you may have a requirement to support late binding.
在一些应用清洁中,你可以有延时绑定的需求。
Late binding is useful if you require the ability to replace part of your system without recompiling.
延时绑定是有用的,如果你需要在你系统无需重新编译而更换零件。
For example, your application might support multiple relational databases with a separate module for each supported database type.
比如,你的应用支持多种关系数据库,为每一个支持数据库类型用一个独立的模块。
You can use declarative configuration to tell the application to use a specific module at runtime.
你可以用申明配置去告诉应用在运行去使用特定的模块。
Another scenario where late binding can be useful is to enable users of the system to provide their own customization through a plug-in.
后期绑定的另一个场景中,可能是有用的是让用户通过一个插件系统提供自己的定制。
Again, you can instruct the system to use a specific customization by using a configuration setting or a convention where the system scans a particular location on the file system for modules to use。
此外,你可以通过使用配置或约定系统扫描文件系统上的特定位置的模块通知系统去使用特定开发。
Not all systems have a requirement for late binding. It is typically required to support a specific feature of the application such as customization using a plug-in architecture.
没有所有系统需要延时绑定需求,它通常需要支持一个特定的应用程序的功能,如定制使用插件架构。
Parallel Development
并行开发
When you are developing large scale (or even small and medium scale) systems, it is not practical to have the entire development team working simultaneously on the same feature or component.
当你在开发大规模(甚至中小规模)系统,整个团队同时的工作在相同的功能和组件上是不实际的。
In reality, you will assign different features and components to smaller groups to work on in parallel.
在现实中,你会分配不同的功能和组件到小组进行并行工作。
Although this approach enables you to reduce the overall duration of the project, it does introduce additional complexities: you need to manage multiple groups and to ensure that you can integrate the parts of the application developed by different groups to work correctly together.
虽然这种方式是你能够减少项目全部时间,它采用了额外的复杂性:你必须管理多个小组并且确保你可以整合不同小组开发的应用组件在一起并能正常工作。
It can be a significant challenge to ensure that classes and components developed independently do work together.
确保独立开发的类和组件在一起工作可能是一个巨大的挑战。
Crosscutting Concerns
横切关注
Enterprise applications typically need to address a range of crosscutting concerns such as validation, exception handling, and logging.
企业应用程序通常需要解决一系列的横切关注点,如验证、异常处理和日志记录。
You may need these features in many different areas of the application and you will want to implement them in a standard, consistent way to improve the maintainability of the system.
你可能在应用程序的许多不同领域需要这些功能并且你会希望在一个标准中实现,一致的方法来提高系统的可维护性。
Ideally, you want a mechanism that will enable you to efficiently and transparently add behaviors to your objects at either design time or run time without requiring you make changes to your existing classes.
理想情况下,你需要一个技巧,那将使你能够去有效的和透明地将行为添加到对象,在设计时或者运行时都不需要你更改现有类。
Often, you need the ability to configure these features at runtime and in some cases, add features to address a new crosscutting concern to an existing application.
通常,你需有在运行时配置配置这些特性的能力,在某些情况下,添加特性去处理一个新的横切关注点到现有应用程序中。
For a large enterprise system, it’s important to be able to manage crosscutting concerns such as logging and validation in a consistent manner. I often need to change the logging level on a specific component at run time to troubleshoot an issue without restarting the system.
为了大企业系统,重要的是能够处理横切关注点,如日志记录和验证以一致的方式。我时常需要更改记录等级在一个特定的组件在运行时解决问题而不需要重新启动系统。
A Simple Example
一个简单的例子
The following example illustrates tight coupling where the Management-Controller class depends directly on the TenantStore class. These classes might be in different Visual Studio projects.
下面的例子说明了紧密耦合的管理控制器类直接取决于TenantStore类。这些类可能在不同的Visual Studio 项目中。
The ManagementController and TenantStore classes are used in various forms throughout this guide. Although the ManagementController class is an ASP.NET MVC controller, you don’t need to know about MVC to follow along. However, these examples are intended to look like the kinds of classes you would encounter in a real-world system, especially the examples in Chapter 3.
在本指南中ManagementController和TenantStore 类被用于各种形式。尽管ManagementController类是一个ASP.NET MVC 控制器,你不需要跟着了解MVC。无论如何,这些例子是刻意看起来像你将在系统系统中遇到的类,特别是第3章的实例。
Loose coupling should be a general design goal for your enterprise applications.
松散耦合应该是你的企业应用程序的总设计目标。
In this example, the TenantStore class implements a repository that handles access to an underlying data store such as a relational database, and the ManagementController is an MVC controller class that requests data from the repository.
在这里例子中,TenantStore类实现了一个库来处理访问一个底层数据存储库,如关系数据库,ManagementController是MVC控制器类,他从存储库中请求数据。
Note that the ManagementController class must either instantiate a TenantStore object or obtain a reference to a TenantStore object from somewhere else before it can invoke the GetTenant and GetTenantNames methods.
注意,ManagementController类必须实例化一个TenantStore对象或从其他地方获取到TenantStore对象的引用才能调用GetTenant和GetTenantNames方法。
The ManagementController class depends on the specific, concrete TenantStore class.
ManagementController 类依赖细节,依赖具体的TenanStore类。
If you refer back to the list of common desirable requirements for enterprise applications at the start of this chapter, you can evaluate how well the approach outlined in the previous code sample helps you to meet them.
如果你回顾这一章开头企业应用共同的需求列表,你可以评估方法概述在前面的代码示例帮助你去满足他们。
• Although this simple example shows only a single client class of the TenantStore class, in practice there may be many client classes in your application that use the TenantStore class. If you assume that each client class is responsible for instantiating or locating a TenantStore object at runtime, then all of those classes are tied to a particular constructor or initialization method in that TenantStore class, and may all need to be changed if the implementation of the TenantStore class changes. This potentially makes maintenance of the TenantStore class more complex, more error prone, and more time consuming.
虽然这个简单的示例仅仅显示一个客户端类属于TenantStore类,在实践中可能有很多客户端类在你的应有程序中使用TenantStore类。如果你认为每个客户端类负责在运行时实例化或定位TenantStore对象,然后所有这些类都要绑定到TenantStore类的构造函数货实例化方法,如果TenantStore类更改的话你可能全部都需要修改。这可能使得TenantStore类的为维护性更加复杂、更容易出错,并且更加费时。
• In order to run unit tests on the Index and Detail methods in the ManagementController class, you need to instantiate a TenantStore object and make sure that the underlying data store contains the appropriate test data for the test. This complicates the testing process, and depending on the data store you are using, may make running the test more time consuming because you must create and populate the data store with the correct data. It also makes the tests much more brittle.
为了运行ManagementController类中的Index和Detail方法的单元测试,为了这个测试你需要实例化TenantStore对象和构造包含可靠测试数据的底层的数据存储。这是一个复杂的测试过程,并且根据你所使用的数据存储,可能运行测试更费事,因为你必须为数据存储创建并填充正确的数据。这也使得测试更加脆弱。
• It is possible to change the implementation of the TenantStore class to use a different data store, for example Windows Azure table storage instead of SQL Server. However, it might require some changes to the client classes that use TenantStore instances if it was necessary for them to provide some initialization data such as connection strings.
为了应用不同的数据存储可能需要更改TenantStore类的实现,比如Windows Azure表存储,而不是SQLServer。然而,如果它是必要的让他们提供一些初始化数据,比如连接字符串,它可能需要修改一些使用TenantStore实例的类客户端。
• You cannot use late binding with this approach because the client classes are compiled to use the TenantStore class directly.
这种方法你无法使用延时绑定,应为客户端类编译直接使用TenantStore类。
• If you need to add support for a crosscutting concern such as logging to multiple store classes, including the TenantStore class, you would need to modify and configure each of your store classes independently.
如果你需要添加支持横切关注,如登录到多个存储类,包括TenantStore类,你将需要修改和配置你每个独立的存储类。
The following code sample shows a small change, the constructor in the client ManagementController class now receives an object that implements the ITenantStore interface and the TenantStore class provides an implementation of the same interface.
下面代码示例显示了小的变化,现在在客户端ManagementController类构造函数接收一个对象实现ITenantStore接口,并且TenantStore类提供了ITenantStore接口的一个实现。
public interface ITenantStore { void Initialize(); Tenant GetTenant(string tenant); IEnumerable<string> GetTenantNames(); void SaveTenant(Tenant tenant); void UploadLogo(string tenant, byte[] logo); } public class TenantStore : ITenantStore { ... public TenantStore() { ... } ... } public class ManagementController : Controller { private readonly ITenantStore tenantStore; public ManagementController(ITenantStore tenantStore) { this.tenantStore = tenantStore; } public ActionResult Index() { ... } public ActionResult Detail(string tenant) { ... } ... }
This change has a direct impact on how easily you can meet the list of requirements.
这个更改影响到你如何容易的满足那些需求列表。
• It is now clear that the ManagementController class, and any other clients of the TenantStore class are no longer responsible for instantiating TenantStore objects, although the example code shown doesn’t show which class or component is responsible for instantiating them. From the perspective of maintenance, this responsibility could now belong to a single class rather than many.
现在可以清楚的是ManagementController类和任何其他TenantStore类客户端不再实负责例化TenantStore对象,尽管所显示的示例代码不能显示哪个类或组件是负责实例化他们。从维护的角度,这个责任显示属于一个类,而不是多个类。
• It’s now also clear what dependencies the controller has from its constructor arguments instead of being buried inside of the controller method implementations.
现在也清楚控制器的依赖性是它的构造函数参数,而不是在控制器的实现方法内部。
• To test some behaviors of a client class such as the ManagementController class, you can now provide a lightweight implementation of the ITenantStore interface that returns some sample data. This is instead of creating a TenantStore object that queries the underlying data store for sample data.
测试客户类的一些行为,比如ManagementController 类,你现在可以提供一个轻量级的ITenantStore接口实现返回一些样本数据。这是查询而不是创建TenantStore对象底层数据存储的样本数据。
• Introducing the ITenantStore interface makes it easier to replace the store implementation without requiring changes in the client classes because all they expect is an object that implements the interface.
介绍ITenantStore接口使它更简单的替换存储实现,而不需要更改客户类,因为他们需要的是一个实现了接口的对象。
If the interface is in a separate project to the implementation, then the projects that contain the client classes only need to hold a reference to the project that contains the interface definition.
如果接口在独立的项目中实现,含有客户类的项目只需要引用包含接口定义的项目。
• It is now also possible that the class responsible for instantiating the store classes could provide additional services to the application. It could control the lifetime of the ITenantStore instances that it creates, for example creating a new object every time the client ManagementController class needs an instance, or maintaining a single instance that it passes as a reference whenever a client class needs it.
现在也可能负责实例化的类存储类可以提供额外的服务应用程序。它能够管理ITenantStore实例的生命周期,它创建了,比如创建新对象每次客户ManagementController类都需要实例化,或者维护个单一实例,它传入引用,每当客户类需要它时。
• It is now possible to use late binding because the client classes only reference the ITenantStore interface type. The application can create an object that implements the interface at runtime, perhaps based on a configuration setting, and pass that object to the client classes. For example, the application might create either a SQLTenantStore instance or a BlobTenantStore instance depending on a setting in the web.config file, and pass that to the constructor in the ManagementController class.
现在可以使用延时绑定,因为客户类只引用ITenantStore接口类型。应用程序可以在运行时创建接口的实现对象,也许基于配置设置将对象传递给客户类。比如,应用程序可以根据Web.config文件中的配置创建SQLTenantStore实例或BlobTenantStore 实例,并传入到ManagementController类的构造函数中。
• If the interface definition is agreed, two teams could work in parallel on the store class and the controller class.
如果接口定义一致,两个团队可以并行工作存储类和控制器类。
• The class that is responsible for creating the store class instances could now add support for the crosscutting concerns before passing the store instance on to the clients, such as by using the decorator pattern to pass in an object that implements the crosscutting concerns. You don’t need to change either the client classes or the store class to add support for crosscutting concerns such as logging or exception handling.
那个负责创建存储实例的类现在可以在传入存储实例到客户之前添加横切关注,如通过使用装饰者模式传递一个对象实现横切关注。你不需要修改客户类或存储类去添加支持横切关注,例如日志记录或异常处理。
The approach shown in the second code sample is an example of a loosely coupled design that uses interfaces. If we can remove a direct dependency between classes, it reduces the level of coupling and helps to increase the maintainability, testability, flexibility, and extensibility of the solution.
第二个代码示例中所示的方法是一个使用接口的松散耦合设计的例子。如果我们可以删除一个类之间的直接依赖关系,它降低耦合等级,并且有助于提高可维护性、可测试性、灵活性和解决方案的可扩展性。
What the second code sample doesn’t show is how dependency injection and the Unity container fit into the picture, although you can probably guess that they will be responsible for creating instances and passing them to client classes. Chapter 2 describes the role of dependency injection as a technique to support loosely coupled designs, and Chapter 3 describes how Unity helps you to implement dependency injection in your applications.
第二个代码示例没有显示是如何依赖注入和Unity容器符合,虽然你大概猜中他们负责创建实例并传递他们到客户类。第二章描述了角色关于依赖注入技术去支持松散耦合设计,和第三张描述Unity如何帮助你在应用程序中实现依赖注入。
Loose coupling doesn’t necessarily imply dependency injection, although the two often do go together.
松散耦合并不一定意味着依赖注入,索然两者经常在一起使用。
When Should You Use a Loosely Coupled Design?
你什么时候应该使用松散耦合的设计?
Before we move on to dependency injection and Unity, you should start to understand where in your application you should consider introducing loose coupling, programming to interfaces, and reducing dependencies between classes. The first requirement we described in the previous section was maintainability, and this often gives a good indication of when and where to consider reducing the coupling in the application. Typically, the larger and more complex the application, the more difficult it becomes to maintain, and so the more likely these techniques will be helpful. This is true regardless of the type of application: it could be a desktop application, a web application, or a cloud application.
在我们继续反转注入和Unity之前,你应该开始明白在你的应用中哪里应该考虑引入松散耦合,设计接口,和降低类之间的依赖。我们在前一节中描述的首要条件是可维护性,,这往往指示何时和某个地方考虑减少应用程序的耦合性。通常,大型复制应用程序,就变得更复杂,所以更有可能这些技术是有益的。这是真的,无论应用程序的类型:它可能是一个桌面应用程序,一个web应用程序,或一个云应用程序。
At first sight, this perhaps seems counterintuitive. The second example shown above introduced an interface that wasn’t in the first example, it also requires the bits we haven’t shown yet that are responsible for instantiating and managing objects on behalf of the client classes. With a small example, these techniques appear to add to the complexity of the solution, but as the application becomes larger and more complex, this overhead becomes less and less significant.
第一映像,这或许像是违反直觉的:上面所述的第二例子引入了接口而不是第一个例子,它还需要一些我们还没显示的负责实例化和管理客户类的对象。用一个小例子,这些技术的使用添加了解决的方案的复杂性,但随着应用程序变得更大、更复杂,这种(复杂性)开销变得越来越不显着。
The previous example also illustrates another general point about where it is appropriate to use these techniques. Most likely, the ManagementController class exists in the user interface layer in the application, and the TenantStore class is part of the data access layer. It is a common approach to design an application so that in the future it is possible to replace one tier without disturbing the others. For example, replacing or adding a new UI to the application (such as creating an app for a mobile platform in addition to a traditional web UI) without changing the data tier or replacing the underlying storage mechanism and without changing the UI tier. Building the application using tiers helps to decouple parts of the application from each other. You should try to identify the parts of an application that are likely to change in the future and then decouple them from the rest of the application in order to minimize and localize the impact of those changes.
前面的例子也阐述了另一个普遍观点关于在你的应用程序的哪个地方使用这些技术。最可能,ManagementController类存在于应用程序的用户界面层,TenantStore类是数据存储层的一部分。它是设计应用程序的普通方法,以便将来可能替换一个层,而不影响到其他层。比如,更换或者添加新的UI到应用程序中(例如为传统的web UI创建移动平台的APP)不用修改数据层,或更换下面的存储机制,不用更改UI层。使用层构建应用程序有助于将应用程序的各个部分彼此分离。你应该尝试确认应用程序在将来可能会不改变的部分,然后将它们与应用程序的其余部分分离,以便最小化和本地化这些更改的影响。
The list of requirements in the previous section also includes crosscutting concerns that you might need to apply across a range of classes in your application in a consistent manner. Examples include the concerns addressed by the application blocks in Enterprise Library (http://msdn.microsoft.com/entlib) such as logging, exception handling, validation, and transient fault handling. Here you need to identify those classes where you might need to address these crosscutting concerns, so that responsibility for adding these features to these classes resides outside of the classes themselves. This helps you to manage these features consistently in the application and introduces a clear separation of concerns.
前面部分中的需求列表还包括横切关注,您可能需要以一致的方式在应用程序的一系列类中应用。示例包括企业库(http://msdn.microsoft.com/entlib)中的应用程序块所解决的问题,例如日志记录,异常处理,验证和瞬态故障处理。在这里,你需要确认那些你可能需要解决这些横切关注的类,所以负责在类本身之外添加特性到这些类。这有助于您在应用程序中一致地管理这些功能,并引入明确的关注点分离。
Small examples of loosely coupled design, programming to interfaces, and dependency injection often appear to complicate the solution. You should remember that these techniques are intended to help you simplify and manage large and complex applications with many classes and dependencies. Of course small applications can often grow into large and complex applications.
松散耦合设计的小例子,接口编程,和依赖注入经常是的解决方案更加复杂。你应该牢记这些技术的意义是在与帮助你简化和管理有许多类和依赖的大型复杂应用程序,当然小的应用程序可以发在成大的复杂应用程序。
Principles of Object-Oriented Design
面向对象设计原则
Finally, before moving on to dependency injection and Unity, we want to relate the five SOLID principles of object-oriented programming and design to the discussion so far. SOLID is an acronym that refers to the following principles:
• Single responsibility principle
• Open/close principle
• Liskov substitution principle
• Interface segregation principle
• Dependency inversion principle
The following sections describe each of these principles and their relationship to loose coupling and the requirements listed at the start of this chapter.
最后,在继续依赖注入和Unity之前,我们希望将面向对象编程和设计的五个SOLID原则与到目前为止的讨论相关联。SOLID是指以下原则的首字母缩写:
S:单一职责原则,一个类应该只有一个发生变化的原因
O:开放原则,对扩展开放,对修改关闭
L:里斯替换原则,任何基类可以出现的地方,子类一定可以出现
I:接口隔离原则,客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
D:依赖倒置原则,高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
以下部分描述了这些原理及其与松耦合的关系以及本章开头列出的要求。
Single Responsibility Principle
单一职责原则
The single responsibility principle states that a class should have one, and only one, reason to change. For more information, see the article Principles of Object Oriented Design by Robert C. Martin1.
In the first simple example shown in this chapter, the ManagementController class had two responsibilities: to act as a controller in the UI and to instantiate and manage the lifetime of TenantStore objects. In the second example, the responsibility for instantiating and managing TenantStore objects lies with another class or component in the system.
单一职责原则声明类应该有一个且只有一个理由去更改。更多信息请看Robert C. Martin1些的面向对象设计的文章。
这章中第一个简单的示例显示ManagementController 类有两个职责:UI中的行为控制和TenantStore对象的实例化和周的期管理 。在第二个示例中,实例化和管理TenantStore对象的责任在于系统中的另一个类或组件。
The Open/Closed Principle
开闭原则
The open/closed principle states that “software entities (classes, modules, functions, and so on) should be open for extension, but closed for modification” (Meyer, Bertrand (1988). Object-Oriented Software Construction.)
Although you might modify the code in a class to fix a defect, you should extend a class if you want to add any new behavior to it. This helps to keep the code maintainable and testable because existing behavior should not change, and any new behavior exists in new classes. The requirement to be able to add support for crosscutting concerns to your application can best be met by following the open/closed principle. For example, when you add logging to a set of classes in your application, you shouldn’t make changes to the implementation of your existing classes.
开闭原则申明“软件实体(类、模块、函数等等)应该对扩展开放,对修改关闭”(Meyer, Bertrand (1988). 面向对象软件构造。)
虽然你可能在类中修改代码以修复缺陷,如果你想添加任何新行为到这个类中,你应该扩展这个类。可以通过遵循开放/封闭原则来满足对您的应用程序增加对横切关注的支持的要求。例如。当你添加一组日志记录类到你的应用程序中时,你不应该去更改你现有类的实现。
The Liskov Substitution Principle
里斯替代原则
The Liskov substitution principle in object-oriented programming states that in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties, such as correctness, of that program.
里斯替代原则在面向对象编程中写明,在计算机程序,如果S是T的子类,那么T类型的对象可以被替换成S类型的对象而不会改变任何期望值,比如程序的正确性。
In the second code sample shown in this chapter, the ManagementController class should continue to work as expected if you pass any implementation of the ITenantStore interface to it. This example uses an interface type as the type to pass to the constructor of the ManagementController class, but you could equally well use an abstract type.
本章第二段代码表明,如果您将ITenantStore接口的任何实现传递给它,ManagementController类应该继续按预期工作。这个实例使用接口类型作为传递给ManagementController 类构造函数的类型,同样你可以使用抽象类型。
Interface Segregation Principle
接口隔离原则
The interface segregation principle is a software development principle intended to make software more maintainable. The interface segregation principle encourages loose coupling and therefore makes a system easier to refactor, change, and redeploy. The principle states that interfaces that are very large should be split into smaller and more specific ones so that client classes only need to know about the methods that they use: no client class should be forced to depend on methods it does not use.
接口隔离原则是一种意在使软件更易维护的软件开发原则。接口隔离原则鼓励松散耦合,并使系统更易重构、更改和重新部署。原则申明非常大的接口应该拆分成小的明确的接口,这样客户类仅仅需要知道关于他们使用的方法:没有客户类应该被迫依赖他们不适用的方法。
In the definition of the ITenantStore interface shown earlier in this chapter, if you determined that not all client classes use the UploadLogo method you should consider splitting this into a separate interface as shown in the following code sample:
在本章前面所示的ITenantStore接口的定义中,如果确定不是所有的客户端类都使用UploadLogo方法,您应该考虑将其拆分为一个单独的接口,如下面的代码示例所示:
public interface ITenantStore { void Initialize(); Tenant GetTenant(string tenant); IEnumerable<string> GetTenantNames(); void SaveTenant(Tenant tenant); } public interface ITenantStoreLogo { void UploadLogo(string tenant, byte[] logo); } public class TenantStore : ITenantStore, ITenantStoreLogo { ... public TenantStore() { ... } ... }
Dependency Inversion Principle
依赖倒置原则
The dependency inversion principle states that:
• High-level modules should not depend on low-level modules. Both should depend on abstractions.
• Abstractions should not depend upon details. Details should depend upon abstractions.
The two code samples in this chapter illustrate how to apply this principle. In the first sample, the high-level ManagementController class depends on the low-level TenantStore class. This typically limits the options for re-using the high-level class in another context.
In the second code sample, the ManagementController class now has a dependency on the ITenantStore abstraction, as does the TenantStore class.
依赖倒置原则声明:
- 高层模块不应该依赖底层模块。都应该依赖抽象。
- 抽象不应该依赖细节。细节应该依赖抽象。
本章两段代码示例说明如何应用这一原则。在第一个示例中,高层的ManagementController 类底层的TenantStore 类。这通常限制了在另一个上下文中重用高层类的选择。
在第二个示例代码中,ManagementController 类现在依赖ITenantStore 抽象,TenantStore 类也是如此。
Summary
总结
In this chapter, you have seen how you can address some of the common requirements in enterprise applications such as maintainability and testability by adopting a loosely coupled design for your application. You saw a very simple illustration of this in the code samples that show two different ways that you can implement the dependency between the ManagementController and TenantStore classes. You also saw how the SOLID principles of object-oriented programming relate to the same concerns.
However, the discussion in this chapter left open the question of how to instantiate and manage TenantStore objects if the ManagementController is no longer responsible for this task. The next chapter will show how dependency injection relates to this specific question and how adopting a dependency injection approach can help you meet the requirements and adhere to the principles outlined in this chapter.
在本章中,您已经了解了如何通过为您的应用程序采用松耦合设计来解决企业应用程序中的一些常见要求,如可维护性和可测试性。您在代码示例中看到了一个非常简单的例子,它展示了两种不同的方法,您可以实现ManagementController和TenantStore类之间的依赖关系。另外你也看到面向对象SOLID原则如何设计涉及这些关注。
但是,本章讨论遗留一个问题,如果ManagementController不再负责这个任务,那么如何实例化和管理TenantStore 对象。在下一章将会显示使用依赖注入如何与具体问题相关和采用依赖注入方法如何帮助您满足要求并遵守本章中概述的原则。
More Information
更多信息
All links in this book are accessible from the book’s online bibliography available at: http://aka.ms/unitybiblio
本书中的所有链接均可从本书的在线参考书目获得,网址为:http://aka.ms/unitybiblio