Я чувствую, что, вероятно, нарушаю здесь один или два принципа дизайна, поэтому я буду признателен за любые ошибки или антипаттерны, которые будут названы и указаны.
Я пытаюсь отобразить систему в коде, который представляет собой дерево решений, чтобы на основе ввода пользователя они могли сделать следующий выбор, затем от их ввода к этому следующему и так далее.
Ответ на каждый выбор:
- Двоичный вариант Да / Нет, поэтому возможны два варианта действий.
- Диапазон от 1 до 10, так что несколько возможных вариантов для продолжения.
Любой тип выбора может возвращать любой другой тип выбора, поэтому я стремился к тому, чтобы последующий выбор всегда был общим IChoice.
BinaryChoice current = NotifyPersonOrNot();
IChoice next = current.MakeChoice(true); // Binary choice
IChoice next1 = next.MakeChoice(5); // Range choice
IChoice next2 = next1.MakeChoice(3); // Range Choice
Проблема, которую я вижу в своем текущем решении, — это использование dynamic
ключевое слово в моем IChoice MakeChoice(dynamic answer)
функция. Я использовал это, чтобы избежать необходимости делать конкретное приведение для T
аргумент в Func<T, IChoice> GetNextChoice
функция, которую MakeChoice
вызовы функций с вводом ответа.
Я подумывал добавить
Итак, интерфейс IChoice похож на такой, поскольку это единственная функция, которую я хочу использовать.
public interface IChoice
{
// I feel like having to use dynamic here is a sign of a mistake in the design
public IChoice MakeChoice(dynamic answer);
}
Мой базовый класс выглядит так
public abstract class Choice<T> : IChoice
{
public Choice(Func<T, IChoice> getNextChoice)
{
GetNextChoice = getNextChoice;
}
public Func<T, IChoice> GetNextChoice { get; set; }
// This seems cleaner than type checking but I'm thinking having to use dynamic indicates a mistake in class design
public IChoice MakeChoice(dynamic answer)
{
return GetNextChoice(answer);
}
}
И реализация базового класса выглядит так, где Choice задан Type. Версия RangeChoice реализует Choice<int>
public class BinaryChoice : Choice<bool>
{
public override ChoiceType ChoiceType => ChoiceType.Binary;
public BinaryChoice(Func<bool, IChoice> getNextChoice) : base(getNextChoice) { }
}
Идея в том, что GetNextChoice
может быть установлен с уникальной логикой, необходимой для обеспечения правильного следующего выбора в дереве.
private BinaryChoice NotifyPersonOrNot()
{
// If we were returning a RangeChoice here we'd have more conditions in the function
return new BinaryChoice((sendNotification) =>
{
if (!sentNotification)
{
// This is another BinaryChoice
return AskAnotherYesNoQuestion();
}
// This is a RangeChoice
return AskRatingQuestion();
});
}
Я подумывал об изменении GetNextChoice
делегировать Func<object, IChoice>
но я хотел бы избежать накладных расходов, связанных с приведением из object
в требуемый тип при определении каждого варианта. Было бы предпочтительнее сделать это один раз в одном месте, если это должно происходить всегда.
Я смотрел добавление аннотации ChoiceType
перечислить в Choice
class и обработка приведения типа в MakeChoice
функции, но я не уверен, как это сделать без явной установки типа для GetNextChoice
делегат.
1 ответ
Я считаю, что ваш вопрос больше нацелен на то, что, где и когда использовать dynamic
ключевое слово больше, чем философия дизайна. Так уж получилось, что здесь есть хорошее объяснение: https://cloudncode.blog/2016/07/12/c-dynamic-keyword-what-why-when/
Коротко и просто по ссылке:
- Проверка типов во время компиляции отсутствует, это означает, что если вы не уверены на 100% в своих модульных тестах (кашель), вы подвергаетесь риску.
- Ключевое слово dynamic использует больше циклов ЦП, чем ваш старомодный статически типизированный код из-за дополнительных накладных расходов времени выполнения, если производительность важна для вашего проекта (обычно это так), не используйте динамический.
- Распространенные ошибки включают возврат анонимных типов, заключенных в ключевое слово dynamic, в общедоступных методах. Анонимные типы специфичны для сборки, возврат их через сборку (через общедоступные методы) вызовет ошибку, хотя простое тестирование обнаружит это, теперь у вас есть общедоступный метод, который вы можете использовать только из определенных мест, и это просто плохой дизайн .
- Это скользкая дорожка, неопытные разработчики, которым не терпится написать что-то новое и изо всех сил стараются избегать большего количества классов (это не обязательно ограничивается неопытными), начнут все больше и больше использовать динамические, если они увидят это в коде, обычно я бы сделал код проверка анализа на динамичность / добавление в обзор кода.
По моему личному мнению — эти функции принимают довольно простые типы данных (bool, int). На ЦП / оборудование более громоздко иметь dynamic
используется, чем просто использовать перегрузку функций для выполнения той же задачи без необходимого напряжения. Верно, что это может устранить необходимость проверки типов, но вы подвергаетесь очень большому риску, если эти функции используются в неконтролируемых событиях.
Кроме того, ваша уверенность в данных, вводимых для MakeChoice()
или любые другие функции динамических параметров или функции, использующие динамический, должны быть такими же точными, как восходящее солнце завтра. В противном случае вы можете получить ошибки во время выполнения.
Сообщите мне, если это поможет — спасибо.