【发布时间】:2020-08-21 13:46:36
【问题描述】:
我有一个名为CommercialRiskProcessor 的课程。它只有 221 行长,包括 cmets 和using。它只有 3 个注入参数,它只做一件事 - 评估一些风险。其实它的接口只有一个方法:
public interface ICommercialRiskProcessor
{
Task<CommercialQuoteWithStatus> ApplyRules(CommercialRiskProcessorRequestInfo info, CommercialQuoteContext qtx, EFCommercialQuote quote);
}
所以,无论如何,它都不是“神”类。开头如下:
public class CommercialRiskProcessor : ServiceBase, ICommercialRiskProcessor
{
public const string UiHardDeclineMessage = WorkflowFailure.UiDefaultWorkflowFailureMessage;
protected ICommercialRiskRuleCollectionFactory RiskRuleCollectionFactory { get; }
protected ICommercialRiskRuleFactory RiskRuleFactory { get; }
protected ICommercialOverrideService OverrideService { get; }
public CommercialRiskProcessor(
ICommercialRiskRuleCollectionFactory riskRuleCollectionFactory,
ICommercialRiskRuleFactory riskRuleFactory,
ICommercialOverrideService overrideService)
{
RiskRuleCollectionFactory = riskRuleCollectionFactory;
RiskRuleFactory = riskRuleFactory;
OverrideService = overrideService;
}
public async Task<CommercialQuoteWithStatus> ApplyRules(CommercialRiskProcessorRequestInfo info, CommercialQuoteContext qtx, EFCommercialQuote quote)
{
var ruleCollection = await RiskRuleCollectionFactory.TryResolveService(info.RuleCollectionName, qtx, quote);
if (ruleCollection == null)
{
return new CommercialWorkflowFailure(UiNotSupportedStateMessage);
}
var results = await ApplyRules(ruleCollection, qtx, quote);
qtx.RuleEvaluationResults.AddRange(results);
return await ProcessResults(info, results, qtx, quote);
}
还有一些私有方法可以做一些额外的处理。这是无关紧要的。问题是我需要另一个风险处理器,称之为HomeownerRiskProcessor。如果我复制/粘贴整个文件,然后将“Commercial”重命名为“Homeowner”,那么新类将开箱即用,因为所有支持类和属性在这两组类中的调用方式相同。然而,房主和商业类别非常不同,因此所有底层对象都不同。
所以,复制/粘贴/单个替换似乎不是一种好的编码风格,不是吗?然而,利用代码重用的唯一可能方法似乎是引入泛型。这就是该死的地方:风险处理器类中使用的每个方法/属性都必须是generic-ized,同时引入一些通用的通用接口。随后,使整个类成为泛型(包括私有成员)将需要超过 10 个具有多个泛型约束的泛型参数!这显然超出了可读性,可能会比复制/粘贴/修改更糟糕。
我想知道是否有其他解决方案可以实现代码重用但没有太多的泛型参数。
如果我开始应用泛型,这就是它的样子:
public interface IQuoteContext<TRuleEvaluationResult>
{
List<TRuleEvaluationResult> RuleEvaluationResults { get; }
}
public interface IRiskProcessorRequestInfo<TRuleCollectionName>
{
TRuleCollectionName RuleCollectionName { get; }
}
public interface IRiskProcessor<TQuoteContext, TQuote, TRequest, TQuoteWithStatus>
{
Task<TQuoteWithStatus> ApplyRules(TRequest info, TQuoteContext qtx, TQuote quote);
}
public interface IRiskRuleCollectionFactory<TQuoteContext, TQuote, TRuleCollectionName, TRuleCollection>
{
Task<TRuleCollection> TryResolveService(TRuleCollectionName collectionName, TQuoteContext qtx, TQuote quote);
}
public abstract class RiskProcessor<TQuoteContext, TQuote, TQuoteWithStatus, TRequest, TRiskRuleCollectionFactory, TRiskRuleFactory, TOverrideService, TRuleCollectionName, TRuleCollection, TRuleEvaluationResult>
: ServiceBase, IRiskProcessor<TQuoteContext, TQuote, TRequest, TQuoteWithStatus>
where TRequest : IRiskProcessorRequestInfo<TRuleCollectionName>
where TRiskRuleCollectionFactory : IRiskRuleCollectionFactory<TQuoteContext, TQuote, TRuleCollectionName, TRuleCollection>
where TQuoteContext : IQuoteContext<TRuleEvaluationResult>
{
public const string UiHardDeclineMessage = WorkflowFailure.UiDefaultWorkflowFailureMessage;
protected TRiskRuleCollectionFactory RiskRuleCollectionFactory { get; }
protected TRiskRuleFactory RiskRuleFactory { get; }
protected TOverrideService OverrideService { get; }
public RiskProcessor(
TRiskRuleCollectionFactory riskRuleCollectionFactory,
TRiskRuleFactory riskRuleFactory,
TOverrideService overrideService)
{
RiskRuleCollectionFactory = riskRuleCollectionFactory;
RiskRuleFactory = riskRuleFactory;
OverrideService = overrideService;
}
protected abstract TQuoteWithStatus ToNullRuleCollectionFailure(TRequest info, TQuoteContext qtx, TQuote quote);
public async Task<TQuoteWithStatus> ApplyRules(TRequest info, TQuoteContext qtx, TQuote quote)
{
var ruleCollection = await RiskRuleCollectionFactory.TryResolveService(info.RuleCollectionName, qtx, quote);
if (ruleCollection == null)
{
return ToNullRuleCollectionFailure(info, qtx, quote);
}
var results = await ApplyRules(ruleCollection, qtx, quote);
qtx.RuleEvaluationResults.AddRange(results);
return await ProcessResults(info, results, qtx, quote);
}
这只会使第一种方法ApplyRules 看起来不错。它下面的其他(私有)方法使用已声明类的更多方法/属性,这反过来会引入更多通用参数和更多约束。
【问题讨论】:
-
您可以尝试引入一些更高级别的抽象(如
IRiskProcessor)并实现类似Strategy 模式的东西。 -
考虑单一责任原则,指出代码应该只有一个改变的理由,也许有看起来重复的代码是有意义的,但代表了 2 个不同的过程。假设您确实重构了公共代码,然后处理“房主”和“商业”的方式会发生变化。现在您必须回溯以再次将其全部拆分。需要考虑的事情可能适用也可能不适用。
-
@NikP 我想您的建议可以作为答案,因为我们最近必须更改其中一组类中的签名,而不是其他类中的签名。如果它是通用的,那将是一场噩梦。因此,如果可以,请提交您的评论作为答案,我会接受。非常感谢!
-
我很欣赏这个想法,但我认为我所做的只是轻轻推了你一下。如果您一直尝试走通用路线然后对签名进行更改,听起来您无论如何都会自己到达我指向您的位置。我只是一个有意见的人。不管怎样,我很高兴我能提供见解,不管有没有帮助,这对我来说已经足够了。
标签: c# generics code-reuse