Как реализовать всеобъемлющий интерфейс для разных типов классов

#c# #.net #interface

#c# #.net #интерфейс

Вопрос:

Я пытаюсь реализовать интерфейс с некоторыми базовыми функциями для изменения настроек BIOS у нескольких производителей компьютеров.

На данный момент у меня есть следующее:

 public interface IBiosConfigurator
{
    bool IsBiosPasswordSet();

    bool ValidateBiosPassword(string password);

    bool SetBiosPassword(string currentPassword, string newPassword);

    T GetSetting<T>(string settingName, string password);

    T GetSetting<T>(string settingName, string propertyName, string password);

    bool SetSetting<T>(string settingName, T value, string password);
}
  

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

  • Если что-то не удается, я понятия не имею, почему. Это был неверный пароль? Значение, которое вы пытаетесь установить, недопустимо? Не существует ли имени параметра?

  • У каждого производителя есть свой способ отображения ошибок: некоторые используют целые числа, другие uints, третьи strings, общего типа нет.

  • Даже у таких простых методов, как bool IsBiosPasswordSet() , есть шанс потерпеть неудачу, и это не учитывается.

Единственный способ, который я могу придумать, чтобы смягчить тот факт, что у каждого производителя есть свой собственный способ установки ошибок (и разные ошибки для каждого поставщика), — это создать enum BiosErrorCode и попытаться сопоставить коды поставщиков со списком, который я поддерживаю для каждой реализации класса IBiosConfigurator .

Но мне все еще нужно вернуть фактический код ошибки, который что-то создало, и хотя мне это кажется нормальным в том, что касается объявления метода:

 BiosErrorCode GetSetting<T>(string settingName, string password, out T value)
  

Я не могу сказать то же самое для:

 BiosErrorCode IsBiosPasswordSet(out bool passwordSet);
  

Но единственный другой способ, который я могу придумать для решения этой проблемы, — это свойство BiosErrorCode LastErrorCode , которое позволило бы мне сохранить объявление моих методов такими, какие они есть, но немного напоминает PInvokes …

Итак, я думаю, у меня есть два вопроса:

  1. Кто-нибудь может придумать лучший способ сделать это?
  2. Если нет, что более уместно, использование out params или BiosErrorCode свойства?

Комментарии:

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

Ответ №1:

Я бы сказал, что это слово Bios избыточно. 😉 Более серьезно: я бы сказал, что если что-то пойдет не так, вы создадите пользовательское исключение. При нормальных обстоятельствах ваши реализации должны просто возвращать то, что вы ожидаете. Так что, если это не так, это действительно исключение, и именно для этого существуют исключения.

Это позволяет скрыть специфичную для поставщика логику для создания этих исключений для специфичной для поставщика реализации.

Ответ №2:

В принципе, класс или интерфейс не должен охватывать множество несвязанных бизнес-процессов в одном объекте.

Комментарии:

1. Как это не связано?

2. На мой взгляд, я должен разделить 2 интерфейса, один — для настройки, другой — для проверки. В будущем, если я захочу добавить другой метод, который хочет что-то сконфигурировать, я добавлю это в конфигурацию интерфейса. Это всего лишь мое мнение.

Ответ №3:

Если что-то не удается, тогда генерируйте стандартизированные исключения в конкретных классах (например, InvalidPasswordException), причем детали зависят от конкретной реализации, вместо того, чтобы пытаться использовать коды возврата или исходящие переменные. Вы можете включить гораздо больше деталей в исключение, и ваша вызывающая программа сможет обрабатывать их более структурированным образом.

Ответ №4:

Вы должны создать исключение, если что-то пойдет не так. Концептуально исключения связаны с вашими интерфейсами, и вы никогда не должны создавать ничего, что не упоминается в документации по интерфейсу.

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

В большинстве случаев это действительно сводится к вашей документации.

Вот пример:

 public class PasswordExpection : Exception
{
    public PasswordExpection(string msg) : base(msg) { }
}

public class BiosExpection : Exception
{
    public BiosExpection(string msg, Exception inner) : base(msg, inner) { }
}

interface IBiosControl
{
    /// <summary>
    /// Checks if a password is set.
    /// </summary>
    /// <exception cref="PasswordExpection">if the concept of password does not apply</exception>
    /// <exception cref="BiosExpection">if there was an execution error</exception>
    /// <returns>Weather or not the password was set</returns>
    bool IsPasswordSet();
}

public class MyBios : IBiosControl
{
    private readonly Action<string> _logger;

    public MyBios(Action<string> logger)
    {
        _logger = logger;
    }

    public bool IsPasswordSet()
    {
        try
        {
            _logger("MyBios IsPasswordSet Connecting To Bios...");
            throw new NotImplementedException();
        }
        catch(Exception e)
        {
            throw new BiosExpection("Execution Error", e);
        }
    }
}