Как правильно реализовать передачу параметров в конструктор?

Вопрос состоит в следующем: имеем базовый класс с некими параметрами, который реализует интерфейс IOptions:

public interface IOptions
{
    int Fdis { get; set; }
}
public class BaseOptions : IOptions
{
    public int Fdis { get; set; }
}

Имеется ряд приборов, которые наследуют базовые настройки и добавляют свои уникальные свойства:

public class PhotoDeviceOptions : BaseOptions
{
    public int LightLevel { get; set; }
    public int LightColor { get; set; }
}

public class PhohoDeviceOptions : BaseOptions
{
    public bool NoiseMasking { get; set; }
    public int SoundWaveType { get; set; }
}

И также существует два класса, который используют эти настройки:

public interface IDeviceProcessor 
{
    void Process();
}
public class PhotoDeviceProcessor : IDeviceProcessor 
{
    public void Process()
    {
         // здесь нужно использовать PhotoDeviceOptions
         photoDevice.UpdateLightSettings();
    }
}
public class PhonoDeviceProcessor : IDeviceProcessor 
{
    public void Process()
    {
         // здесь нужно использовать PhonoDeviceOptions
         phonoDevice.UpdateSoundSettings();
    }
}

Как правильно реализовать передачу настроек в DeviceProcessor ? Мне в голову приходят только два варианта:

  1. Передавать интерфейс IOptions и затем преобразовывать его в нужный тип:

     private PhotoDeviceOptions _options;
    
     public PhotoDeviceProcessor (IOptions options)
     {
         if(options is not PhotoDeviceOptions)
             throw new InvalidCastException(nameof(options));
    
         _options = options as PhotoDeviceOptions;
     }
    
  2. Передавать тип настроек, соответствующих классу:

     private PhotoDeviceOptions _options;
    
     public PhotoDeviceProcessor (PhotoDeviceOptions options)
     {
         _options = options;
     }
    

Или есть более правильный способ реализации?


Ответы (1 шт):

Автор решения: aepot

Написал ответ до того как вы обновили пример, поэтому ответ будет абстрактный. :)

У вас есть несколько конкретных моделей данных, здесь будет пример одной, потому что для контраста я буду использовать примитивный тип в качестве второй.

public class MathArguments
{
    public double A { get; set; }
    public double B { get; set; }
}

У них нет общих интерфейсов, вообще ничего общего. Но могут быть и общие базовые типы, это не запрещается.

И есть вот такие интерфейсы.

public interface ICalculator
{
    double Calculate();
}

public interface ICalculator<T> : ICalculator
{
    T Args { get; }
}

И вот здесь начинается интересное.

Давайте к примеру посчитаем вот такое

Math.Sqrt(A + B);

С помощью моделей данных и абстракций.

Реализуем 2 раза интерфейс.

public class SumCalculator : ICalculator<MathArguments>
{
    public MathArguments Args { get; }

    public SumCalculator(MathArguments args)
    {
        Args = args;
    }

    public double Calculate()
    {
        return Args.A + Args.B;
    }
}

public class SqrtCalculator : ICalculator<double>
{
    public double Args { get; }

    public SqrtCalculator(double args)
    {
        Args = args;
    }

    public double Calculate()
    {
        return Math.Sqrt(Args);
    }
}
private static void Main(string[] args)
{
    var arguments = new MathArguments { A = 2, B = 2 };
    ICalculator sumCalc = new SumCalculator(arguments);
    double sum = sumCalc.Calculate();

    ICalculator sqrtCalc = new SqrtCalculator(sum);
    double result = sqrtCalc.Calculate();

    Console.WriteLine(result); // 2
    Console.ReadKey();
}

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

К сожалению обобщений для конструкторов не существует, поэтому правильная реализация конструктора - на совести разработчика. Свойство { get; } как бы намекает, что его можно задать только в конструкторе. Если вам не нужно публичное свойство, то и интерфейс с обобщением не потребуется.

Отсюда делаю вывод, что вариант Передавать тип настроек, соответствующих классу: более правильный.

→ Ссылка