C# повторное использование кода без слишком большого количества общих c параметров - PullRequest
1 голос
/ 06 мая 2020

У меня есть класс под названием CommercialRiskProcessor. Всего 221 строка, включая комментарии и 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. Если я скопирую / вставлю весь файл, а затем переименую «Коммерческий» в «Домовладелец», тогда новый класс будет работать из коробки, потому что все вспомогательные классы и свойства вызываются одинаково в этих двух группах классов. Тем не менее, классы домовладельцев и коммерческие классы очень разные, поэтому все лежащие в основе объекты разные. Тем не менее, единственный возможный способ использования повторного использования кода, похоже, - введение дженериков. И здесь начинается ад: каждый из методов / свойств, используемых в классе обработчика рисков, должен быть изменен на generic вместе с введением некоторых общих интерфейсов c. Впоследствии, чтобы сделать весь класс generi c (включая частные члены), потребуется более 10 общих c параметров с множеством общих c ограничений! Это явно нечитаемо и, вероятно, сделает его даже хуже, чем копирование / вставка / изменение.

Интересно, есть ли какие-либо другие решения для повторного использования кода, но без слишком большого количества общих c параметров в цельный.

Вот как это будет выглядеть, если я начну применять дженерики:

    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 выглядеть нормально. Другие (частные) методы, расположенные ниже, используют еще несколько методов / свойств объявленных классов, что, в свою очередь, вводит еще больше общих c параметров и дополнительных ограничений.

1 Ответ

0 голосов
/ 06 мая 2020

Если я скопирую / вставлю весь файл, а затем переименую «Коммерческий» в «Домовладелец», тогда новый класс будет работать из коробки, потому что все вспомогательные классы и свойства вызываются одинаково в этих двух группах классы. Тем не менее, классы домовладельцев и коммерческие классы очень разные, поэтому все базовые объекты различны.

Похоже, хотя эти классы очень разные, вы можете извлечь из них интерфейсы, которые будут идентичными. Вам просто нужно ограничить интерфейсы только теми методами, которые необходимы для обработки рисков. Затем обработчик рисков может работать с любыми классами, реализующими эти интерфейсы.

...