使用 Spring.net Framework 进行即插即用 (DI)






3.38/5 (4投票s)
使用 Spring.net Framework 进行即插即用,我们还将了解如何使用 Spring.Net 配置来连接我们的应用程序。
引言
在本文中,我们将探讨当我们在其他类中创建直接依赖时遇到的一些问题,以及如何使用 spring.net 解决这些问题。我们还将了解如何使用 spring.net 配置将我们所有的类/层连接在一起。本文的示例代码是用 Visual Studio 2013 编写的,但它不依赖于 Visual Studio 版本。如果您手动配置 spring.net DLL,则需要选择适当的 spring.net 版本。
我希望收到关于本文的反馈。请随时分享您的评论,您也可以发送电子邮件至 shettyashwin@outlook.com。如果您喜欢本文,请不要忘记评分。
直接依赖
为了演示直接依赖,我创建了一个示例应用程序。这个示例应用程序是一个控制台应用程序。控制台应用程序将与外部世界进行交互。控制台应用程序接收客户 ID。其余逻辑由其下方的 3 个层管理。这三个层是外观层、业务层和数据层。
internal class Program
{
public static void Main()
{
Console.WriteLine("Enter customer id & press enter");
int customerId = Convert.ToInt32(Console.ReadLine().ToString());
DoWork doWork = new DoWork();
doWork.DoProcessingWork(customerId);
Console.ReadLine();
}
}
外观层将用于协调验证过程,然后在验证成功执行后通过业务层添加客户 ID。
public class DoWork
{
public DoWork()
{}
public virtual void DoProcessingWork(int customerId)
{
CustomerProcessor _processor = new CustomerProcessor();
if (!IsCustomerValidate(customerId))
{
Console.WriteLine("Adding new customer ID");
_processor.Add(customerId);
}
else
{
Console.WriteLine("Customer Id should be greater than 10");
}
}
public virtual bool IsCustomerValidate(int customerId)
{
CustomerValidate _CustomerValidate = new CustomerValidate();
return _CustomerValidate.ValidateCustomer(customerId);
}
}
业务层将与数据层交互以验证客户 ID,然后添加它。
public class CustomerProcessor
{
public void Add(int customerId)
{
Customer _customer = new Customer();
if(customerId < 1) throw new Exception("Customer Id can't be less 1");
_customer.Add(customerId);
}
}
public class CustomerValidate
{
public virtual bool ValidateCustomer(int customerId)
{
Search _search = new Search();
return _search.IsCustomerIdRegistered(customerId);
}
}
由于我创建此应用程序仅用于演示目的,因此我不会创建数据库。我已经硬编码了客户 ID 进行验证,并且当客户 ID 通过数据层添加时,它会将一条消息写回控制台。直接写入控制台不是理想的解决方案,但现在我们可以忽略它。
public class Search
{
public bool IsCustomerIdRegistered(int customerId)
{
if (customerId < 10)
{
return true;
}
else
{
return false;
}
}
}
public class Customer
{
public void Add(int customerId)
{
Console.WriteLine("Added new Customer Id :" + customerId.ToString());
}
}
下面的顺序图将提供有关层和流程如何流动的 ধারণা。
问题区域
- 控制台应用程序、外观层和业务层直接创建它们引用的实例。
- 所有类/层都紧密绑定在一起。要将其映射到不同的类/层,我们将不得不进行代码更改。
- 编写单元测试用例会很麻烦,因为我们无法直接模拟任何层。
- 为不同客户应用定制逻辑会很困难。这将涉及大量代码中的 if 和 else,这会降低可维护性。
什么是 Spring.Net
Spring.net 是一个开源应用程序框架,用于构建企业级 .NET 应用程序。它源自 Java 版本的 Spring 框架。它允许您在使用基础类库时消除偶然的复杂性,并遵循最佳实践。您可以在 这里 找到有关 Spring.net 的更多详细信息。
为了演示,在本文中,我们将使用支持依赖注入的 Spring.Core。
使用 Spring.Net 的 DI 解决方案
为了解决上述问题,我们必须使用依赖注入。对于依赖注入,我将使用 Spring.Net 框架。通过使用 Spring 框架,我们将创建应用程序上下文。我们处理数据所需的所有类都将预先配置好,并且对象实例将使用应用程序上下文创建。我们将逐步实现这一目标。但首先,我们需要将所需的 Spring 引用放在一起。
为了获取 spring.net 引用 DLL,我使用了 NuGet。Nuget 管理器会在控制台应用程序中设置所有必需的引用。其他层将不需要 spring DLL 引用,因为我们将使用控制台应用程序中的 *app.configuration* 文件将它们全部连接在一起。
在 *app.config* 中,我们将不得不提及节、上下文和对象集合详细信息。这是我们在 *app.config* 中配置节详细信息的方式。
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
现在我们可以继续在 *app.config* 中创建一个单独的节。如上所述,我在控制台应用程序的 *app.config* 中创建了 spring 节。
<spring>
<context>
<resource uri="config://spring/objects"/>
</context>
<objects xmls="http://www.SpringFramework.net">
<object name="BusinessDoWork"
type="SpringSample.BusinessLayer.CustomerValidate,
SpringSample.BusinessLayer" singleton="false">
<property name="Search" ref="DataLayerSearch"/>
</object>
<object name="BusinessCustomerProcessor"
type="SpringSample.BusinessLayer.CustomerProcessor,
SpringSample.BusinessLayer" singleton="false">
<property name="Customer" ref="DataLayerCustomer"/>
</object>
<object name="FacadeCustomer"
type="SpringSample.FacadeLayer.DoWork, SpringSample.FacadeLayer"
singleton="false">
<constructor-arg ref="BusinessDoWork"/>
<constructor-arg ref="BusinessCustomerProcessor"/>
</object>
<object name="FacadeCustomerValidate"
type="SpringSample.FacadeLayer.DoWork, SpringSample.FacadeLayer"
singleton="false">
<constructor-arg ref="BusinessDoWork"/>
</object>
<object name="DataLayerSearch"
type="SpringSample.DataLayer.Search, SpringSample.DataLayer"
singleton="false"/>
<object name="DataLayerCustomer"
type="SpringSample.DataLayer.Customer, SpringSample.DataLayer"
singleton="false"/>
</objects>
</spring>
在 spring 节内,您可以看到两个子节(Context 和 objects)。Context 定义了需要从哪里加载/映射对象的。Spring.net 还可以从外部源(如 XML 文件)加载对象。因此,我们需要指定一个已配置的位置,从中可以加载对象及其关系。Objects 子节定义了实例及其映射。一旦我们回顾我们所做的更改,我将详细解释这个部分。
作为依赖注入的一部分,我们还需要进行的另一个重要更改是定义契约,然后在我们创建的类中实现它。在当前的示例中,我使用接口创建了契约,但您也可以使用 `abstract` 类来实现此目的,如果您有需要共享的通用逻辑。这些契约是在单独的项目中创建的,以便逻辑和契约保留在单独的程序集中。
public interface ICustomerProcessor
{
ICustomer Customer { get; set; }
void Add(int customerId);
}
public interface ICustomerValidate
{
ISearch Search { get; set; }
bool ValidateCustomer(int customerId);
}
public interface ICustomer
{
void Add(int customerId);
}
public interface ISearch
{
bool IsCustomerIdRegistered(int customerId);
}
public interface IDoWork
{
void DoProcessingWork(int customerId);
bool IsCustomerValidate(int customerId);
}
为了展示不同的注入方式,我在 `ICustomerProcessor`(`Customer`)和 `ICustomerValidate`(`Search`)中添加了其他属性。在业务层,我们将使用属性注入;在外观层,我们将通过构造函数注入对象。数据层变化不大,您看到的唯一变化是继承了 `ISearch` 和 `ICustomer` 接口。
public class Customer : ICustomer
{
public void Add(int customerId)
{
Console.WriteLine("Added new Customer Id :" + customerId.ToString());
}
}
public class Search : ISearch
{
public bool IsCustomerIdRegistered(int customerId)
{
if (customerId < 10)
{
return true;
}
else
{
return false;
}
}
}
这就是我们在 *app.config* 的 spring.net 配置中配置数据层的方式
<object name="DataLayerSearch"
type="SpringSample.DataLayer.Search,
SpringSample.DataLayer" singleton="false"/>
<object name="DataLayerCustomer" type="SpringSample.DataLayer.Customer,
SpringSample.DataLayer" singleton="false"/>
同样在业务层,我们将继承为业务层创建的接口(`ICustomerProcessor` 和 `ICustomerValidate`),并更改对象创建逻辑。此后,我们不会创建任何对象,而是将引用注入到属性中。另请注意,我们将从业务层项目中删除对数据层项目的引用。由于我们只编写针对接口签名的代码,因此我们需要添加对契约项目的引用。
public class CustomerProcessor : ICustomerProcessor
{
public ICustomer Customer { get; set; }
public void Add(int customerId)
{
if(customerId < 1) throw new Exception("Customer Id can't be less 1");
Customer.Add(customerId);
}
}
public class CustomerValidate : ICustomerValidate
{
public ISearch Search { get; set; }
public virtual bool ValidateCustomer(int customerId)
{
return this.Search.IsCustomerIdRegistered(customerId);
}
}
相同的更改将是 Spring 配置。我已经添加了 `property` 标签,它引用为数据层创建的对象存储。当创建业务层的对象时,Spring.net 将根据引用注入数据层对象。
<object name="BusinessDoWork" type="SpringSample.BusinessLayer.CustomerValidate,
SpringSample.BusinessLayer" singleton="false">
<property name="Search" ref="DataLayerSearch"/>
</object>
<object name="BusinessCustomerProcessor"
type="SpringSample.BusinessLayer.CustomerProcessor,
SpringSample.BusinessLayer" singleton="false">
<property name="Customer"
ref="DataLayerCustomer"/>
</object>
外观层也需要进行相同的更改。将继承新创建的接口,并删除对象创建逻辑。在外观层,对象将通过 `Constructor` 注入。与业务层项目类似,我们将删除对业务层和数据层的引用,并将对外观层中的契约项目的引用。
public class DoWork : IDoWork
{
protected ICustomerValidate _CustomerValidate;
protected ICustomerProcessor _processor;
public DoWork(ICustomerValidate customerValidate, ICustomerProcessor processor)
{
_CustomerValidate = customerValidate;
_processor = processor;
}
public DoWork(ICustomerValidate customerValidate)
{
_CustomerValidate = customerValidate;
}
public virtual void DoProcessingWork(int customerId)
{
if (!IsCustomerValidate(customerId))
{
Console.WriteLine("Adding new customer ID");
_processor.Add(customerId);
}
else
{
Console.WriteLine("Customer Id should be greater than 10");
}
}
public virtual bool IsCustomerValidate(int customerId)
{
return _CustomerValidate.ValidateCustomer(customerId);
}
}
这就是我们在 *app.config* 的 spring 配置中配置外观层的方式。要通过构造函数注入对象,我添加了 `constructor-arg` 标签。此标签通知 spring.net 根据 `ref` 属性中提到的键名注入依赖项对象。
<object name="FacadeCustomer" type="SpringSample.FacadeLayer.DoWork,
SpringSample.FacadeLayer" singleton="false">
<constructor-arg ref="BusinessDoWork"/>
<constructor-arg ref="BusinessCustomerProcessor"/>
</object>
在 Spring.net 中,我们可以选择构造函数或属性来创建对象。这只能根据您为对象创建配置的参数来实现。我在外观层为验证创建了一个单独的构造函数。如果逻辑要求只获取验证结果,那么可以通过单参数构造函数实现。*app.config* 中的配置如下所示
<object name="FacadeCustomerValidate" type="SpringSample.FacadeLayer.DoWork,
SpringSample.FacadeLayer" singleton="false">
<constructor-arg ref="BusinessDoWork"/>
</object>
通过构造函数/属性引用所有对象,我们消除了直接依赖。对象现在被注入了。没有任何类创建自己的对象,而是通过构造函数或属性引用它们。整个流程由配置文件连接。由于这些类/层没有直接链接,因此可以根据我们的需要进行调整。唯一剩下的部分是将外观层与 UI 连接起来。我们通过这种方式实现:
internal class Program
{
public static void Main()
{
IApplicationContext appContext = ContextRegistry.GetContext();
Console.WriteLine("Enter customer id & press enter");
int customerId = Convert.ToInt32(Console.ReadLine().ToString());
IDoWork doWork = appContext["FacadeCustomerValidate"] as IDoWork;
doWork.IsCustomerValidate(customerId);
doWork = appContext["FacadeCustomer"] as IDoWork;
doWork.DoProcessingWork(customerId);
Console.ReadLine();
}
}
通过使用 `ContextRegistry`,我们可以获取 `ApplicationContext`。我们处理用户输入所需的所有对象实例都通过 `ApplicationContext` 获取。我们需要传递我们在 *App.Config* 中配置的 `KeyName`。
有关使用 MOQ 编写测试用例,您可以参考我另一篇文章 TDD using MOQ。
关注点
在本文中,我们看到了如何消除直接依赖并使用 spring.net 配置将所有层连接在一起。希望这能让您对如何解耦类/层有所了解。当我们可以预见到大量定制化时,类/层的解耦变得非常重要。当我们需要编写 100% 可测试的代码时,这种策略也很有帮助。