#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 …
Итак, я думаю, у меня есть два вопроса:
- Кто-нибудь может придумать лучший способ сделать это?
- Если нет, что более уместно, использование
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);
}
}
}