Извините за длинный пост. Я старался показать свои попытки и мыслительный процесс как можно больше.
Я получил интерфейс, демонстрирующий несколько возможных вариантов поведения, но существует только одна реализация этого интерфейса, которая была реализована, и только один из представленных методов, которые можно вызывать в каждом контексте, где реализован интерфейс.
Этот интерфейс будет использоваться в совершенно ином контексте приложения, и я хочу избежать раскрытия метода, который нельзя вызвать.
Я хочу найти способ, чтобы вызывающий IRescheduler
мог знать только одно поведение, несмотря на разные сигнатуры методов. Я подробно опишу и пример и то, что я пробовал до сих пор
public interface IRescheduler
{
AmountByTimeInterval RescheduleTomorrow(Amount amount);
AmountByTimeInterval RescheduleAtGivenDate(Amount amount, DateTime rescheduleDate);
// there will probably be more date strategies in the future
}
AmountByTimeInterval
содержит Amount
, а TimeInterval
связывает строку с интервалом времени от текущей даты. Например, "1Day"
будет временным интервалом с завтрашнего дня до завтра, а "1Year"
начнется через год и закончится годом позже.
public class AmountByTimeInterval
{
public Amount Amount { get; private set; }
public TimeInterval TimeInterval { get; private set; }
public AmountByTimeInterval(Amount amount, TimeInterval timeInterval)
{
Amount = amount;
TimeInterval = timeInterval;
}
}
public class Amount
{
public double Value { get; private set; }
public string Currency { get; private set; }
public Amount(double amount, string currency)
{
Value = amount;
Currency = currency;
}
}
public class TimeInterval
{
public string Name { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
public TimeInterval(string name, DateTime startDate, DateTime endDate)
{
Name = name;
StartDate = startDate;
EndDate = endDate;
}
}
Для примера рассмотрим интерфейс IRescheduleAmountCalculator
, который Amount
использует для создания других Amount
public interface IRescheduleAmountCalculator
{
Amount ComputeRescheduleAmount(Amount amount);
}
Вот пример реализации моего IRescheduler
интерфейса. У меня есть шаблон репозитория, который связывает меня TimeInterval
с DateTime
.
public interface ITimeIntervalRepository
{
TimeInterval GetTimeIntervalByName(string name);
TimeInterval GetTimeIntervalByDate(DateTime date);
}
public class Rescheduler : IRescheduler
{
private const string _1Day = "1Day";
private readonly ITimeIntervalRepository _timeIntervalRepository;
private readonly TimeInterval _tomorrow;
private readonly IRescheduleAmountCalculator _calculator;
public Rescheduler (ITimeIntervalRepository timeIntervalRepository, IRescheduleAmountCalculator calculator)
{
_calculator = calculator;
_timeIntervalRepository = timeIntervalRepository;
_tomorrow = timeIntervalRepository.GetTimeIntervalByName(_1Day);
}
public BucketAmount RescheduleTomorrow(Amount amount)
{
Amount rescheduledAmount = _calculator.ComputeRescheduleAmount(amount);
return new TimeInterval(_tomorrow, transformedAmount);
}
public AmountByTimeInterval RescheduleAtGivenDate(Amount amount, DateTime reschedulingDate)
{
TimeInterval timeInterval = _timeIntervalRepository.GetTimeIntervalByDate(reschedulingDate);
Amount rescheduledAmount = _calculator.ComputeRescheduleAmount(amount);
return new TimeInterval(timeInterval, transformedAmount);
}
}
Я не знаю заранее, в каком контексте будет вызываться IRescheduler
, он предназначен для использования многими компонентами. Вот абстрактный класс, который я собираюсь предоставить, и пример конкретной реализации
public abstract class AbstractReschedule<TInput, TOutput>
{
private readonly ITransformMapper<TInput, TOutput> _mapper;
protected readonly IRescheduler Rescheduler;
protected AbstractReschedule(IMapper<TInput, TOutput> mapper, IRescheduler rescheduler)
{
_mapper = mapper;
Rescheduler = rescheduler;
}
public abstract TOutput Reschedule(TInput entityToReschedule);
protected TOutput MapRescheduledEntity(TInput input, TimeInterval timeInterval)
{
return _mapper.Map(input, timeInterval);
}
}
public class RescheduleImpl : AbstractReschedule<InputImpl, OutputImpl>
{
public RescheduleImpl(IRescheduleMapper<InputImpl, OutputImpl> mapper, IRescheduler rescheduler) : base(mapper, rescheduler)
{
}
public override OutputImpl Reschedule(InputImpl entityToReschedule)
{
AmountByTimeInterval rescheduledAmountByTimeInterval = Rescheduler.RescheduleTomorrow(entityToReschedule.AmountByTimeInterval.Amount);
return Map(entityToReschedule, rescheduledAmountByTimeInterval);
}
}
public interface IMapper<T, TDto>
{
TDto Map(T input, AmountByTimeInterval amountByTimeInterval);
}
Использование интерфейса для общего параметра TInput
не подлежит сомнению, так как компонент предназначен для использования в большом количестве ограниченных контекстов. Каждый будущий пользователь всего этого компонента перепланирования будет реализовывать свою собственную реализацию AbstractReschedule
и IMapper
.
Я попробовал шаблон стратегии , но другой аргумент метода заблокировал меня, так как я не мог определить интерфейсный контракт, который позволял бы все поведение, не подвергая действительной реализации IRescheduler
.
Затем я реализовал шаблон посетителя, где IRescheduler
будет иметь Accept
метод и реализацию по поведению:
public interface IRescheduler
{
AmountByTimeInterval Accept(IReschedulerVisitor visitor, Amount amount);
}
public class RescheduleTomorrow : IRescheduler
{
public AmountByTimeInterval Accept(IReschedulerVisitor visitor, Amount amount)
{
return visitor.Visit(this, amount);
}
}
public class RescheduleAtGivenDate : IRescheduler
{
public AmountByTimeInterval Accept(IReschedulerVisitor visitor, Amount amount)
{
return visitor.Visit(this, amount);
}
}
Как вы заметили, DateTime
здесь отсутствует, потому что я на самом деле вставляю его в посетителя , который построен на Factory
public interface IReschedulerVisitor
{
AmountByTimeInterval Visit(RescheduleTomorrow rescheduleTomorrow, Amount amount);
AmountByTimeInterval Visit(RescheduleAtGivenDate rescheduleAtGivenDate, Amount amount);
}
public class ReschedulerVisitor : IReschedulerVisitor
{
private readonly ITimeIntervalRepository _timeIntervalRepository;
private readonly DateTime _chosenReschedulingDate;
private readonly IRescheduleAmountCalculator _rescheduleAmountCalculator;
private const string _1D = "1D";
public ReschedulerVisitor(ITimeIntervalRepository timeIntervalRepository, IRescheduleAmountCalculator rescheduleAmountCalculator)
{
_timeIntervalRepository = timeIntervalRepository;
_rescheduleAmountCalculator = rescheduleAmountCalculator
}
public ReschedulerVisitor(ITimeIntervalRepository timeIntervalRepository, IRescheduleAmountCalculator rescheduleAmountCalculator, DateTime chosenReschedulingDate)
{
_timeIntervalRepository = timeIntervalRepository;
_chosenReschedulingDate = chosenReschedulingDate;
_rescheduleAmountCalculator = rescheduleAmountCalculator
}
public AmountByTimeInterval Visit(RescheduleTomorrow rescheduleTomorrow, Amount amount)
{
TimeInterval reschedulingTimeInterval = _timeIntervalRepository.GetTimeIntervalByName(_1D);
Amount rescheduledAmount = _rescheduleAmountCalculator(amount);
return new AmountByTimeInterval(reschedulingTimeInterval, rescheduledAmount);
}
public AmountByTimeInterval Visit(RescheduleAtGivenDate rescheduleAtGivenDate, Amount amount)
{
TimeInterval reschedulingTimeInterval = _timeIntervalRepository.GetTimeIntervalByDate(_chosenReschedulingDate);
Amount rescheduledAmount = _rescheduleAmountCalculator(amount);
return new AmountByTimeInterval(reschedulingTimeInterval, rescheduledAmount);
}
}
public interface IRescheduleVisitorFactory
{
IRescheduleVisitor CreateVisitor();
IRescheduleVisitor CreateVisitor(DateTime reschedulingDate);
}
public class RescheduleVisitorFactory : IRescheduleVisitorFactory
{
private readonly ITimeIntervalRepository _timeIntervalRepository;
public RescheduleVisitorFactory(ITimeIntervalRepository timeIntervalRepository)
{
_timeIntervalRepository = timeIntervalRepository;
}
public IRescheduleVisitor CreateVisitor()
{
return new RescheduleVisitor(_timeIntervalRepository);
}
public IRescheduleVisitor CreateVisitor(DateTime reschedulingDate)
{
return new RescheduleVisitor(_timeIntervalRepository, reschedulingDate);
}
}
Наконец (извините за длинный пост), RescheduleImpl
, который должен был бы реализовать каждый пользователь, выглядел бы так:
public class RescheduleImpl : AbstractReschedule<InputImpl, OutputImpl>
{
public RescheduleImpl(IRescheduler rescheduler, IRescheduleVisitorFactory visitorFactory, IRescheduleMapper<InputImpl, OutputImpl> mapper)
: base(cancel, visitorFactory, mapper) {}
public override OutputImpl Reschedule(InputImpl entityToReschedule)
{
AmountByTimeInterval rescheduledAmountByTimeInterval = rescheduler.Accept(visitorFactory.CreateVisitor(), entityToReschedule.AmountByTimeInterval.Amount);
// the second case would be :
// AmountByTimeInterval rescheduledAmountByTimeInterval = rescheduler.Accept(visitorFactory.CreateVisitor(entityToReschedule.Date), entityToReschedule.AmountByTimeInterval.Amount);
return Mapper.Map(entityToReschedule, rescheduledAmountByTimeInterval);
}
}
Хотя это работает, я весьма недоволен решением. Я чувствую, что разработчик моего решения примет решение о стратегии реструктуризации дважды. Первый раз при выборе реализации IRescheduler
для использования сборки последний класс RescheduleImpl
, который я показал, и второй раз при решении, какой метод фабрики вызывать.
В настоящее время у меня нет идей, и я открыт для любого, кто может решить исходную проблему. Я также открыт для совершенно другой реализации, чем моя попытка посетитель + фабрика.
Спасибо, что нашли время, чтобы прочитать или ответить на мою проблему.