具有不同构造函数的对象的工厂
本文关键字:对象 工厂 构造函数 | 更新日期: 2023-09-27 18:16:40
有一个接口规则,它包含一个Validate()方法和几个实现该方法的派生类。类具有不同的变量(类型和参数数量)。此外,还有一个名为IPaymentProcessor的核心接口,它必须验证所有现有的规则。我目前的任务是实现像工厂或容器这样的高级抽象,理想情况下,它使用不同的构造函数创建所有规则,然后将它们作为IEnumerable返回以迭代并应用每个规则进行卡片验证。
是否有可能在。net中使用Ninject或任何其他基于反射的库来完成任务?(AutoFixture, Moq等)
这是一个当前的解决方案,我想改进。
public interface IRule
{
bool Validate();
}
class Rule1 : IRule
{
public Rule1(string name) { ... }
bool Validate() { ... }
}
class Rule2 : IRule
{
public Rule1(int month, int year) { ... }
bool Validate() { ... }
}
interface IPaymentProcessor
{
bool MakePayment(CreditCard card);
}
class MyPaymentProcess : IPaymentProcessor
{
public bool MakePayment(CreditCard card)
{
// Here is pitfall. If we need to add/remove another rule or one of
// ctors changed, then we have to edit this place, which isn't flexible
var rules = new List<IBusinessRule>() { new Rule1(card.Name), new Rule2(card.Month, card.Year) };
foreach(var r in rules) if(!r.Validate()) { return false; }
return true;
}
}
最终看起来您想要验证信用卡,所以我真的不明白为什么您要创建不同的规则来验证信用卡的特定属性,而不是实现一个通用的规则来验证卡作为一个整体。
如果,由于我不知道的原因,您需要创建规则独立验证名称,过期日期,号码等,你仍然有一个非常简单的方法来做到这一点;只需在构造函数中传递de card,并让每个规则验证它应该验证的信息;
public NameRule(CreditCard card) { ... }
public bool Validate() => !string.IsNullOrEmpty(card.Name);
如果在创建规则时不知道要验证的卡片,则需要使用一个更简洁的解决方案,即将Predicate<CreditCard>
传递给构造函数。在这种情况下,您甚至不需要多个规则类型:
var nameRule = new Rule<CreditCard>(c => !string.IsNullOrEmpty(c.Name));
var dateRule = new Rule<CreditCard>(c => c.Date > DateTime.
作为Rule
的实现:
public class Rule<T>
{
private readonly Predicate<T> myPredicate;
public Rule(Predicate<T> predicate)
{
myPredicate = predicate;
}
public bool Validate(CreditCard card) => myPredicate(card);
}
麻烦的根源在于您试图使用运行时数据(仅在运行时知道的CreditCard
实例的属性)构建应用程序组件(业务规则实现),而向应用程序组件注入运行时数据是一种反模式。
相反,您的组件应该是无状态的,并且通过IRule
抽象的公共API传递运行时数据,您可以避免必须在工厂内创建这样的组件(因为工厂是一种代码气味),并且您可以防止这些维护问题,正如您在问题中描述的那样。
@InBetween对使IRule
抽象泛型做了一个很好的评论,因为这允许创建一个类型安全的业务规则实现,它准确地定义了它验证的内容:
public interface IBusinessRule<TEntity>
{
IEnumerable<string> Validate(TEntity entity);
}
还请注意,我更改了Validate
,使其不返回布尔值,而是返回(零个或多个)验证错误的集合。这允许更清楚地沟通为什么系统停止处理您的请求。
实现可能如下所示:
类creditcardnamentempty: IBusinessRule{公共IEnumerable Validate(信用卡实体){如果(string.IsNullOrWhiteSpace (entity.Name)yield return "信用卡名不能为空";}}
通过将运行时数据移出构造函数,现在可以让我们更容易地构造包含它们自己的依赖项的应用程序组件。例如:
类CreditCardDateIsValid: IBusinessRule{私有只读日志记录器;公共CreditCardDateIsValid(ILogger logger) {this.logger;}
public IEnumerable<string> Validate(CreditCard entity) {
// etc
}
}
虽然我们可以将IEnumerable<IBusinessRule<T>>
注入到需要业务规则验证的组件中,但这样做并不好,因为这会迫使使用者迭代返回的集合,这将导致大量代码重复。因此,我们希望对消费者隐藏IBusinessRule<T>
抽象,并向他们展示一个更关注他们需求的抽象。例如:
public interface IValidator<T>
{
// Throws a ValidationException in case of a validation error.
void Validate(T instance);
}
我们可以很容易地实现如下:
public class Validator<T> : IValidator<T>
{
private readonly IEnumerable<IBusinessRule<T>> rules;
public Validator(IEnumerable<IBusinessRule<T>> rules) {
if (rules == null) throw new ArgumentNullException(nameof(rules));
this.rules = rules;
}
public void Validate(T instance) {
if (instance == null) throw new ArgumentNullException(nameof(instance));
var errorMessages = rules.Select(rule => rule.Validate(instance)).ToArray();
if (errorMessages.Any()) throw new ValidationException(errorMessages);
}
}
这允许我们将支付处理程序简化为以下内容:
class MyPaymentProcess : IPaymentProcessor
{
private readonly IValidator<CreditCard> creditCardValidator;
public MyPaymentProcess(IValidator<CreditCard> creditCardValidator) {
this.creditCardValidator = creditCardValidator;
}
public void MakePayment(CreditCard card)
{
this.creditCardValidator.Validate(card);
// continue the payment
}
}
注意,MakePayment
方法现在不再返回bool
。这是因为如果一个操作不能完成它所承诺的(在这个例子中是支付),它应该抛出一个异常。通过返回一个布尔值,您将返回一个错误代码,这是我们很久以前留下的做法。