这是使用和测试类,使得使用工厂模式的正确方法? [英] Is this the correct way to use and test a class that makes use of the factory pattern?

查看:251
本文介绍了这是使用和测试类,使得使用工厂模式的正确方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我没有很多的工厂模式的经验和我遇到这样一个场景,我相信这是必要的,但我不知道我的已经正确实施的模式,我很担心它的影响是对我的单元测试的可读性。

I don't have a lot of experience with the factory pattern and I've come across a scenario where I believe it is necessary but I'm not sure the I've implemented the pattern correctly and I'm concerned about the impact it's had on the readability of my unit tests.

我创建了一个代码段接近(从内存中),我在工作的场景的精髓工作。我会很感激,如果有人可以看看它,看看我做了什么似乎是合理的。

I've created a code snippet that approximates (from memory) the essence of the scenario I am working on at work. I'd really appreciate it if someone could take a look at it and see if what I've done seems reasonable.

这是我需要测试的类:

public class SomeCalculator : ICalculateSomething
{
	private readonly IReducerFactory reducerFactory;
	private IReducer reducer;

	public SomeCalculator(IReducerFactory reducerFactory)
	{
		this.reducerFactory = reducerFactory;
	}

	public SomeCalculator() : this(new ReducerFactory()){}

	public decimal Calculate(SomeObject so)
	{	
		reducer = reducerFactory.Create(so.CalculationMethod);

		decimal calculatedAmount = so.Amount * so.Amount;

		return reducer.Reduce(so, calculatedAmount);
	}
}

下面是一些基本的接口定义...

Here are some of the basic interface definitions...

public interface ICalculateSomething
{
	decimal Calculate(SomeObject so);
}

public interface IReducerFactory
{
	IReducer Create(CalculationMethod cm);
}

public interface IReducer
{
	decimal Reduce(SomeObject so, decimal amount);
}

这是我创建的工厂。我现在的要求已经让我添加了特定的减速MethodAReducer在特定的情况下这就是为什么我想介绍一个工厂中使用。

This is the factory I've created. My current requirements have had me add a specific Reducer MethodAReducer to be used in a particular scenario which is why I'm trying to introduce a factory.

public class ReducerFactory : IReducerFactory
{
	public IReducer Create(CalculationMethod cm)
	{
		switch(cm.Method)
		{
			case CalculationMethod.MethodA:
				return new MethodAReducer();
				break;
			default:
				return DefaultMethodReducer();
				break;
		}
	}
}



这是两种的近似值实现...实施的精髓在于它不仅降低的数额,如果对象是在一个特定的状态。

These are approximations of the two implementations... The essence of the implementation is that it only reduces the amount if the object is in a particular state.

public class MethodAReducer : IReducer
{
	public decimal Reduce(SomeObject so, decimal amount)
	{	
		if(so.isReductionApplicable())
		{
			return so.Amount-5;
		}
		return amount;
	}
}

public class DefaultMethodReducer : IReducer
{
	public decimal Reduce(SomeObject so, decimal amount)
	{
		if(so.isReductionApplicable())
		{
			return so.Amount--;
		}
		return amount;
	}
}

这是我使用的测试夹具。有什么关心我是多少空间,在测试工厂模式已经采取和它如何出现以减少测试的可读性。请记住,在我的现实世界一流的,我有几个依存关系,我需要模拟出,这意味着这里的测试是几行比需要为我的真实世界测试的那些短。

This is the test fixture I am using. What has concerned me is how much space in the tests the factory pattern has taken up and how it appears to reduce the readability of the test. Please keep in mind that in my real world class I have several dependencies that I need to mock out which means that the tests here are several lines shorter than the ones needed for my real world test.

[TestFixture]
public class SomeCalculatorTests
{
	private Mock<IReducerFactory> reducerFactory;
	private SomeCalculator someCalculator;

	[Setup]
	public void Setup()
	{
		reducerFactory = new Mock<IReducerFactory>();
		someCalculator = new SomeCalculator(reducerFactory.Object);		
	}

	[Teardown]
	public void Teardown(){}

第一次测试

	//verify that we can calculate an amount
	[Test]
	public void Calculate_CalculateTheAmount_ReturnsTheAmount()
	{
		decimal amount = 10;
		decimal expectedAmount = 100;
		SomeObject so = new SomeObjectBuilder()
		 .WithCalculationMethod(new CalculationMethodBuilder())								                             
                     .WithAmount(amount);

		Mock<IReducer> reducer = new Mock<IReducer>();

		reducer
			.Setup(p => p.Reduce(so, expectedAmount))
			.Returns(expectedAmount);

		reducerFactory
			.Setup(p => p.Create(It.IsAny<CalculationMethod>))
			.Returns(reducer);

		decimal actualAmount = someCalculator.Calculate(so);

		Assert.That(actualAmount, Is.EqualTo(expectedAmount));
	}



二测

Second test

	//Verify that we make the call to reduce the calculated amount
	[Test]
	public void Calculate_CalculateTheAmount_ReducesTheAmount()
	{
		decimal amount = 10;
		decimal expectedAmount = 100;
		SomeObject so = new SomeObjectBuilder()
		 .WithCalculationMethod(new CalculationMethodBuilder())								                             
                     .WithAmount(amount);

		Mock<IReducer> reducer = new Mock<IReducer>();

		reducer
			.Setup(p => p.Reduce(so, expectedAmount))
			.Returns(expectedAmount);

		reducerFactory
			.Setup(p => p.Create(It.IsAny<CalculationMethod>))
			.Returns(reducer);

		decimal actualAmount = someCalculator.Calculate(so);

		reducer.Verify(p => p.Reduce(so, expectedAmount), Times.Once());			
	}
}



因此,没有这一切是否正确?或者是有使用工厂模式更好的办法?

So does all of that look right? Or is there a better way to use the factory pattern?

推荐答案

这是你在问一个相当长的问题,但这里有一些流浪的想法:

It's a pretty long question you are asking, but here are some stray thoughts:


  • AFAIK,有没有工厂模式。有一个名为抽象工厂和另一个名为工厂方法格局。现在,你似乎使用抽象工厂是。

  • 有没有理由认为SomeCalculator既有 reducerFactory 减速字段。摆脱他们的一个 - 在当前的实现,你不需要减速字段

  • 请注入的依赖(。 reducerFactory )是只读的。

  • 摆脱默认的构造函数。

  • switch语句中ReducerFactory可以是代码的气味。也许你可以移动创建方法的CalculationMethod类。这将从根本上改变抽象工厂一个工厂方法。

  • AFAIK, there's no 'Factory' pattern. There's a pattern called Abstract Factory and another one called Factory Method. Right now you seem to be using Abstract Factory.
  • There's no reason that SomeCalculator have both a reducerFactory and a reducer field. Get rid of one of them - in your current implementation, you don't need the reducer field.
  • Make the injected dependency (reducerFactory) readonly.
  • Get rid of the default constructor.
  • The switch statement in ReducerFactory may be a code smell. Perhaps you could move the creation method to the CalculationMethod class. That would essentially change the Abstract Factory to a Factory Method.

在任何情况下,总是有与引入松耦合相关的开销,但不要以为你是可测性只能这样做。 可测性真的只有打开/关闭原则,所以你让你的代码在很多不仅仅是使测试更加的方式更加灵活。

In any case, there's always an overhead associated with introducing loose coupling, but don't think that you are doing this for testability only. Testability is really only the Open/Closed Principle, so you are making your code more flexible in many more way than just to enable testing.

是的,有一个小的价格要支付的,但它是值得的。

Yes, there's a small price to be paid for that, but it's well worth it.

在大多数情况下,所注入的依赖应只读的。虽然技术上没有必要,这是良好的安全额外级别来标记与C#只读关键字字段。

In most cases, the injected dependency should be read-only. While not technically necessary, it is a good extra level of safety to mark the field with the C# readonly keyword.

当您决定使用DI,你必须一直使用它。这意味着,重载的构造函数是另一个反模式。这使得构造模糊,也可能导致的紧耦合漏抽象

When you decide to use DI, you must use it consistently. This means that overloaded constructors are yet another anti-pattern. This makes the constructor ambiguous and may also leads to Tight Coupling and Leaky Abstractions.

这瀑布和似乎像的缺点,但实际上是一个优势。当你需要在其它的类来创建SomeCalculator的新实例,则必须重新注入要么它或注入一个抽象工厂,可以创建它。该优势是,当你再提取SomeCalculator(比如ISomeCalculator)的接口,并注入了吧。现在,您已经有效地脱钩IReducer和IReducerFactory SomeCalculator的客户端

This cascades and may seem like a drawback, but is actually an advantage. When you need to create a new instance of SomeCalculator in some other class, you must again either inject it or inject an Abstract Factory that can create it. The advantage comes when you then extract an interface from SomeCalculator (say, ISomeCalculator) and inject that instead. You have now effectively decoupled the client of SomeCalculator from IReducer and IReducerFactory.

您不需要DI容器做到这一切 - 你可以线了实例改为手动。这就是所谓的纯DI

You don't need a DI Container to do all this - you can wire up instances manually instead. This is called Pure DI.

当谈到在ReducerFactory移动逻辑CalculationMethod,我在想一个虚方法。像这样的东西:

When it comes to moving the logic in ReducerFactory to CalculationMethod, I was thinking about a virtual method. Something like this:

public virtual IReducer CreateReducer()
{
    return new DefaultMethodReducer();
}

有关特殊CalculationMethods,然后你可以重写CreateReducer方法,并返回一个不同的减速

For special CalculationMethods, you can then override the CreateReducer method and return a different reducer:

public override IReducer CreateReducer()
{
    return new MethodAReducer();
}



这是否最后的建议是有道理取决于很多的,我不要信息吨有,所以我只是说你应该的认为的它 - 它可能不会让你的特殊情况意识

Whether this last advice makes sense depends on a lot of information that I don't have, so I'm just saying that you should consider it - it may not make sense in your specific case.

这篇关于这是使用和测试类,使得使用工厂模式的正确方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆