拦截所产生的开销值得吗?

发布于 2024-11-08 01:09:54 字数 2080 浏览 5 评论 0原文

我正在努力将 NHibernate 引入我们的代码库。我想我必须使用某种 DI 容器,这样我就可以将依赖项注入到从数据库加载的实体中。我选择 Unity 作为容器。

我正在考虑使用 Unity 的拦截机制向我的代码添加事务方面,因此我可以执行以下操作:

class SomeService
{
    [Transaction]
    public void DoSomething(CustomerId id)
    {
        Customer c = CustomerRepository.LoadCustomer(id);
        c.DoSomething();
    }
}

[Transaction] 处理程序将负责创建会话和事务、提交 。

我担心使用这种拦截会使我在代码中几乎所有地方都使用 Unity 如果我以这种方式引入方面,那么我绝对不能调用 new SomeService(),否则我将获得没有事务的服务。虽然这在生产代码中是可以接受的,但在测试中似乎开销太大。例如,我必须将以下内容转换

void TestMethod()
{
    MockDependency dependency = new MockDependency();
    dependency.SetupForTest();
    var service = SomeService(dependency);
    service.DoSomething();
}

为:

void TestMethod()
{
    unityContainer.RegisterType<MockDependency>();
    unityContainer.RegisterType<IDependency, MockDependency>();

    MockDependency dependency = unityContainer.Resolve<MockDependency>();
    dependency.SetupForTest();
    var service = unityContainer.Resolve<SomeService>();
    service.DoSomething();
}

这会为我正在使用的每个模拟对象添加 2 行,这会导致相当多的代码(我们的测试使用大量有状态模拟,因此这并不罕见测试类有 5-8 个模拟对象,有时甚至更多。)

我认为独立注入在这里没有帮助:我必须为测试中使用的每个类设置注入,因为方面可能会测试完成后添加到班级。

现在,如果我放弃使用拦截,我最终会得到:

class SomeService
{
    public void DoSomething(CustomerId id)
    {
        Transaction.Run(
            () => {
                Customer c = CustomerRepository.LoadCustomer(id);
                c.DoSomething();
             });
    }
}

诚然,这不是那么好,但似乎也没有那么糟糕。

我什至可以设置我自己的穷人拦截:

class SomeService
{
    [Transaction]
    public void DoSomething(CustomerId id)
    {
        Interceptor.Intercept(
            MethodInfo.GetCurrentMethod(),
            () => {
                Customer c = CustomerRepository.LoadCustomer(id);
                c.DoSomething();
             });
    }
}

然后我的拦截器可以处理该类的属性,但我仍然可以使用 new 实例化该类,而不用担心失去功能。

有没有更好的方法来使用 Unity 拦截,而不会强迫我始终使用它来实例化我的对象?

I'm in the middle of a significant effort to introduce NHibernate into our code base. I figured I would have to use some kind of a DI container, so I can inject dependencies into the entities I load from the database. I chose Unity as that container.

I'm considering using Unity's interception mechanism to add a transaction aspect to my code, so I can do e.g. the following:

class SomeService
{
    [Transaction]
    public void DoSomething(CustomerId id)
    {
        Customer c = CustomerRepository.LoadCustomer(id);
        c.DoSomething();
    }
}

and the [Transaction] handler will take care of creating a session and a transaction, committing the transaction (or rolling back on exception), etc.

I'm concerned that using this kind of interception will bind me to using Unity pretty much everywhere in the code. If I introduce aspects in this manner, then I must never, ever call new SomeService(), or I will get a service that doesn't have transactions. While this is acceptable in production code, it seems too much overhead in tests. For example, I would have to convert this:

void TestMethod()
{
    MockDependency dependency = new MockDependency();
    dependency.SetupForTest();
    var service = SomeService(dependency);
    service.DoSomething();
}

into this:

void TestMethod()
{
    unityContainer.RegisterType<MockDependency>();
    unityContainer.RegisterType<IDependency, MockDependency>();

    MockDependency dependency = unityContainer.Resolve<MockDependency>();
    dependency.SetupForTest();
    var service = unityContainer.Resolve<SomeService>();
    service.DoSomething();
}

This adds 2 lines for each mock object that I'm using, which leads to quite a bit of code (our tests use a lot of stateful mocks, so it is not uncommon for a test class to have 5-8 mock objects, and sometimes more.)

I don't think standalone injection would help here: I have to set up injection for every class that I use in the tests, because it's possible for aspects to be added to a class after the test is written.

Now, if I drop the use of interception I'll end up with:

class SomeService
{
    public void DoSomething(CustomerId id)
    {
        Transaction.Run(
            () => {
                Customer c = CustomerRepository.LoadCustomer(id);
                c.DoSomething();
             });
    }
}

which is admittedly not as nice, but doesn't seem that bad either.

I can even set up my own poor man's interception:

class SomeService
{
    [Transaction]
    public void DoSomething(CustomerId id)
    {
        Interceptor.Intercept(
            MethodInfo.GetCurrentMethod(),
            () => {
                Customer c = CustomerRepository.LoadCustomer(id);
                c.DoSomething();
             });
    }
}

and then my interceptor can process the attributes for the class, but I can still instantiate the class using new and not worry about losing functionality.

Is there a better way of using Unity interception, that doesn't force me to always use it for instantiating my objects?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

臻嫒无言 2024-11-15 01:09:54

如果您想使用 AOP 但又担心 Unity,那么我建议您查看 PostSharp。它实现了 AOP 作为编译后检查,但对运行时使用代码的方式没有任何改变。

http://www.sharpcrafters.com/

他们有一个免费的社区版本,具有良好的功能集,如以及具有显着增强功能集的专业版和企业版。

If you want to use AOP but are concerned abut Unity then I would recommend you check out PostSharp. That implements AOP as a post-compile check but has no changes on how you use the code at runtime.

http://www.sharpcrafters.com/

They have a free community edition that has a good feature set, as well as professional and enterprise versions that have significantly enhanced feature sets.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文