【问题标题】:C# Code reuse without too many generic parameters没有太多泛型参数的 C# 代码重用
【发布时间】: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


【解决方案1】:

如果我复制/粘贴整个文件,然后将“商业”重命名为 “房主”,那么新类将开箱即用,因为所有 支持类和属性在这些中被相同地调用 两组班。然而,房主和商业阶层是 非常不同,因此所有底层对象都不同。

听起来即使这些类非常不同,您也可以从中提取相同的接口。您只需将接口限制为仅风险处理所需的方法。然后风险处理器可以对实现这些接口的任何类进行操作。

【讨论】:

  • 不,我无法从中提取相同的接口,我只能从中提取具有多个参数的通用接口:例如qtx.RuleEvaluationResults 是每个相关风险处理器等的“商业”或“房主”对象列表。
  • 策略模式使用相同的基本接口(示例中为IBillingStrategy)。这里我没有一个通用的接口来操作。我可以做一个泛型的通用接口,但是泛型参数太多了。
  • @KonstantinKonstantinov 那么简单的类重命名如何工作?
  • 从风险处理器的角度来看,所有相关的 HO / CO 类都具有完全相同的调用属性(当然,不同类型的类当然也有一些 其他 不同的属性/方法)。所以,我在风险处理器文件的副本中将“商业”重命名为“房主”,然后看到奇迹发生了 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多