Лучший подход для шаблона проектирования для нескольких планировщиков с использованием интерфейса и абстрактного класса

У меня есть требование получать данные для разных типов пользователей (клиентов, издателей и т. Д.) Для трех разных временных шкал (ретро, ​​текущий и прошлый), у всех разные URL-адреса. Есть планировщик, который будет работать с разными интервалами (например, 2 минуты для ретро-данных, 5 минут для текущих данных и 10 минут для будущих датированных данных). Я приложил изображение для лучшего понимания.

введите описание изображения здесь

 interface IDataPublisher { 
   void GetUrl(); 
   void Publish(); 
 }

  interface ITimeLineGrabber { 
     TimeSpan GetTimeSpanForNextRun(); 
  }

 abstract class BasePublisher : IDataPublisher, ITimeLineGrabber, BackgroundService 
 {
    private readonly ILogger<BasePublisher> _logger;
    public BasePublisher (ILogger<BasePublisher> logger)
    {
        _logger = logger;
    }

    public abstract string GetUserType();
    public abstract string GetDataType();
    public abstract TimeSpan GetTimeSpanForNextRun();
    public abstract string GetUrl();

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
      await base.StartAsync(cancellationToken);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
      await base.StopAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      var userType = GetUserType();
      var dataType = GetDataType();
      TimeSpan timeSpan = GetTimeSpanForNextRun();
     
      while (!stoppingToken.IsCancellationRequested)
      {
            try
            {
                // Do Something with userType and dataType 
                await Task.Delay(timeSpan , stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error - {ex.Message}", DateTimeOffset.Now);
            }
        }
   }

  public sealed class CustomerRetroPublisher: BasePublisher 
  {
    public override string GetUserType()
    {
       return "Customer";
    }

    public override string GetDataType()
    {
      return "Retro";
    }

    public override Timespan GetTimeSpanForNextRun()
    {
      return Timespan.FromMinutes(2); // read from config file..
    }

    public override string GetUrl()
    {
       return "CustomerRetroUrl";
    }
  }

но в итоге получится 12 разных классов, и масса кода будет дублироваться / усложняться. Я чувствую, что ко всему этому должен быть простой подход. Просто интересно, что может быть лучшим подходом для моих требований.

2 ответа
2

Жесткое кодирование различных констант — не лучшая идея. Я бы вставил их через конструктор (я использую свойства вместо Get-методы):

public sealed class Publisher : interfaces
{
    public void Publisher (string userType, string dataType, Timespan interval, string url)
    {
        UserType = userType;
        DataType = dataType;
        Interval = interval;
        Url = url;
    }

    public string UserType { get; }
    public override string DataType { get; }
    public override Timespan Interval { get; }
    public override string Url { get; }
}

Затем создайте фабрику

public static class PublisherFactory
{
    public static Publisher CreateCustomerRetroPublisher()
    {
        return new Publisher("Customer", "Retro", Timespan.FromMinutes(2), "CustRetroUrl");
    }

    ...
}

Если вам нужно различное поведение для разных издателей, вы также можете ввести их таким же образом. Например, внедрить сервисы для поведения, зависящего от временной шкалы (через ITimelineService) и поведение, зависящее от типа пользователя (через IUserService). Таким образом, вы можете комбинировать разные типы поведения по-разному. 3 сервиса временной шкалы + 4 сервиса пользователя = 7 сервисов лучше, чем 12 различных классов.

Тогда у конструктора будут дополнительные параметры

public void Publisher (string userType, string dataType, Timespan interval, string url,
    ITimelineService timeLineService, IUserService userService)

Сервисы могут быть назначены частным полям, так как они не должны быть общедоступными.

Смотрите также: Композиция важнее наследования (Википедия)

  • 2

    Это лучше, чем подход, который я предложил, если OP не нуждается в моделировании этих типов как фактических объектах C #, а требует только строковых идентификаторов для использования системой планирования. Также лучше, если этот тип будет, например, сериализован для шины событий или чего-то подобного, поскольку он предлагает легкий объект, ориентированный исключительно на захват данных, необходимых для обработки сообщения.

    — Джереми Кейни

Похоже, это хорошая возможность для универсального интерфейса. Например,

interface IDataTimeType<T> where T : IDataType 
{
    T RootData { get; }
}

Затем ваши типы времени могут быть реализованы с помощью дженериков:

class Retro<T> : IDataTimeType<T>
{
    Retro(T rootData) 
    {
        RootData = rootData;
    }
    T RootData { get; }
}

И создается, например,

var data = new Data1();
var dataTime = new Retro<Data1>(data);

Интерфейсы должны фокусироваться на том, какие точки взаимодействия необходимо поддерживать с объектами, а не на том, какие типы у каждого объекта; вы можете использовать Object.GetType() для этого:

var dataTimeType = dataTime.GetType().Name;
var dataType = dataTime.RootData.GetType().Name;

Примечание: Я бы не рекомендовал использовать такие общие идентификаторы, как Type1 и IDataType, но я сохраняю их для согласованности с вашим вопросом. Тем не менее, я предполагаю, что они предназначены только для иллюстрации концепции, абстрагируя вопрос от специфики вашей модели данных.

  • 3

    Добро пожаловать в Code Review! Ваш пост выглядит хорошо, приятного вам пребывания!

    — ферада

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *