#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();
Это работает для вас?