Правильный способ доступа событий формы к приложению

#c# #interface

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

Вопрос:

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

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

В качестве упрощенного примера представьте, что у меня есть этот логический код:

 public class Logic
{
    public SpecificState SpecificState { get; private set; }
    public IGenericState GenericState { get; private set; }
}
  

И этот код формы:

 private void DebugMethod_Click(object sender, EventArgs e)
{
    if (myLogic.SpecificState != null)
    {
        myLogic.SpecificState.MessWithStuff();
    }
}
  

Итак, я пытаюсь избавиться от SpecificState ссылки. Он был удален отовсюду в приложении, но я не могу придумать, как переписать функции отладки. Должны ли они перенести свою реализацию в Logic класс? Если да, то что тогда? Было бы пустой тратой времени помещать множество MessWithStuff методов, IGenericState поскольку все другие классы будут иметь пустые реализации.

Редактировать

В течение жизни приложения многие IGenericState экземпляры приходят и уходят. Это что-то вроде шаблона DFA / strategy. Но только одна реализация имеет функциональность отладки.

Кроме того: существует ли другой термин для «отладки» в этом контексте, относящийся к функциям только для тестирования? «Отладка» обычно просто относится к процессу исправления вещей, поэтому трудно искать этот материал.

Ответ №1:

Создайте отдельный интерфейс для хранения функций отладки, таких как:

 public interface IDebugState
{
    void ToggleDebugMode(bool enabled); // Or whatever your debug can do
}
  

Затем у вас есть два варианта: вы можете вводить IDebugState так же, как вы вводите IGenericState , как в:

 public class Logic
{
    public IGenericState GenericState { get; private set; }
    public IDebugState DebugState { get; private set; }
}
  

Или, если вы ищете более быстрое решение, вы можете просто выполнить тестирование интерфейса в своих методах, чувствительных к отладке:

 private void DebugMethod_Click(object sender, EventArgs e)
{
    var debugState = myLogic.GenericState as IDebugState;
    if (debugState != null)
        debugState.ToggleDebugMode(true);
}
  

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

Внутри, конечно, у вас все еще есть SpecificState реализация обоих IGenericState и IDebugState , поэтому существует только один экземпляр — но это зависит от вашего контейнера IoC, ни один из ваших зависимых классов не должен знать об этом.

Ответ №2:

Я бы настоятельно рекомендовал прочитать пошаговое руководство Ninject по внедрению зависимостей (обязательно прочитайте весь учебник). Я знаю, что это может показаться странной рекомендацией, учитывая ваш вопрос; однако я думаю, что это сэкономит вам много времени в долгосрочной перспективе и сделает ваш код более чистым.

Похоже, что ваш отладочный код зависит от SpecificState ; поэтому я ожидаю, что ваши пункты меню отладки будут запрашивать у контейнера DI свои зависимости или у поставщика, который может вернуть зависимость или null . Если вы уже работаете над рефакторингом для включения DI, то предоставление вашим пунктам меню отладки соответствующих внутренних битов вашего приложения в качестве зависимостей (через контейнер DI) кажется подходящим способом добиться этого, не нарушая принципов solid design. Так, например:

 public sealed class DebugMenuItem : ToolStripMenuItem
{
    private SpecificStateProvider _prov;

    public DebugMenuItem(SpecificStateProvider prov) : base("Debug Item")
    {
       _prov = prov;
    }
    // other stuff here

    protected override void OnClick(EventArgs e)
    {
        base.OnClick(e);


        SpecificState state = _prov.GetState();
        if(state != null)
           state.MessWithStuff();
    }
}
  

Это предполагает, что экземпляр SpecificState не всегда доступен и должен быть доступен через поставщика, который может возвращать значение null. Кстати, этот метод имеет дополнительное преимущество в виде меньшего количества обработчиков событий в вашей форме.

Кроме того, я бы рекомендовал не нарушать принципы проектирования ради отладки, и пусть ваши методы отладки «muck with stuff» взаимодействуют с вашими внутренними классами так же, как и любой другой фрагмент кода — через его интерфейс «contract». Вы избавите себя от головной боли =)

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

1. Я случайно нахожусь в середине большого рефакторинга специально для использования некоторого DI, и я прочитал эту страницу. Но я не понимаю, как это помогает в данном случае. Вы предлагаете моему SpecificState классу реализовать функции отладки? Я собираюсь добавить больше деталей к вопросу, посмотреть, поможет ли это.

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

Ответ №3:

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

Я предполагаю, что вы каким-то образом помещаете экземпляр Logic в части вашего приложения — либо через static классы или поля, либо путем передачи в конструктор.

Затем я бы расширил Logic этот интерфейс:

 public interface ILogicDebugger
{
    IDisposable PublishDebugger<T>(T debugger);
    T GetFirstOrDefaultDebugger<T>();
    IEnumerable<T> GetAllDebuggers<T>();
    void CallDebuggers<T>(Action<T> call);
}
  

Затем глубоко внутри вашего кода некоторый класс, который вы хотите отладить, вызовет этот код:

 var subscription =
    logic.PublishDebugger(new MessWithStuffHere(/* with params */));
  

Теперь в вашем коде верхнего уровня вы можете вызвать что-то вроде этого:

 var debugger = logic.GetFirstOrDefaultDebugger<MessWithStuffHere>();
if (debugger != null)
{
    debugger.Execute();
}
  

Более короткий способ вызова методов в вашем классе debug — использовать CallDebuggers следующим образом:

 logic.CallDebuggers<MessWithStuffHere>(x => x.Execute());
  

В глубине вашего кода, когда ваш класс, который вы отлаживаете, вот-вот выйдет из области видимости, вы должны вызвать этот код, чтобы удалить его отладчик:

 subscription.Dispose();
  

Это работает для вас?