У меня есть класс под названием 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 параметров и дополнительных ограничений.