Я создаю набор классов, элементов графического интерфейса, для небольшого консольного приложения.
Элементы представляют собой прямоугольники, текстовые поля и списки. Они могут быть показаны и скрыты с помощью методов, которые определены в общем интерфейсе элементов.
Элементы рисуют себя сами. Некоторые из них могут иметь цвет, так что при рисовании они изменяют цвет текста\фона в консоли. После рисования элемента нужно восстановить цвет консоли, который был до его рисования.
Методы рисования элементов стали повторять один и тот же код, чтобы выполнить восстановление цветов:
void Draw()
{
//Сохраняем предыдущие цвета консоли.
ConsoleColor oldForegroundColor = Console.ForegroundColor;
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
//Устанавливаем свои цвета.
//Рисуем что-то.
//Восстанавливаем предыдущие цвета.
Console.ForegroundColor = oldForegroundColor;
Console.BackgroundColor = oldBackgroundColor;
}
Как избавится от повторения одного и того же кода в методах рисования? В классе может быть несколько методов рисования, у них могут быть разные сигнатуры.
Чтобы решить проблему, я заменял общий интерфейс элементов на абстрактный класс, который определял метод Draw():
abstract class GraphicalElement
{
public void Draw()
{
//Сохраняем предыдущие цвета консоли.
ConsoleColor oldForegroundColor = Console.ForegroundColor;
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
//Устанавливаем свои цвета и
//рисуем что-то в этом методе.
DoDraw();
//Восстанавливаем предыдущие цвета.
Console.ForegroundColor = oldForegroundColor;
Console.BackgroundColor = oldBackgroundColor;
}
protected abstract void DoDraw();
}
Но он оборачивал в код сохранения цвета только метод DoDraw().
Элемент, отображающий список, при изменении списка должен обновляться. Использовать метод Draw() для этой задачи оказалось медленно, так как Draw() вызывает DoDraw(), который рисует весь список заново.
Я создал метод DrawPartOfList(int drawBeginIndex), рисующий только часть списка, начиная с определенного элемента. Теперь его тоже нужно как-то обернуть в повторяющийся код.
Варианты, которые я могу предложить:
- Заменить
Draw()в абстрактном классе на следующий метод:
protected void DrawWithMethod(Action drawMethod)
{
//Сохраняем предыдущие цвета консоли.
ConsoleColor oldForegroundColor = Console.ForegroundColor;
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
drawMethod();
//Восстанавливаем предыдущие цвета.
Console.ForegroundColor = oldForegroundColor;
Console.BackgroundColor = oldBackgroundColor;
}
И использовать его вот так:
DrawWithMethod(() => DrawPartOfList(drawBeginIndex));
- Выделить методы рисования в отдельные классы. Все они будут реализовывать абстрактный класс:
abstract class Painter
{
public void Paint()
{
//Сохраняем предыдущие цвета консоли.
ConsoleColor oldForegroundColor = Console.ForegroundColor;
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
DoPaint();
//Восстанавливаем предыдущие цвета.
Console.ForegroundColor = oldForegroundColor;
Console.BackgroundColor = oldBackgroundColor;
}
protected abstract void DoPaint();
}
Конкретные классы будут реализовывать DoPaint() и смогут добавить нужные для этого метода параметры, как свои поля:
class ListPainter : Painter
{
private int paintBeginIndex;
public ListPainter(int paintBeginIndex)
{
this.paintBeginIndex = paintBeginIndex;
}
protected override void DoPaint()
{
//Рисуем список, используя paintBeginIndex...
}
}
В коде графических элементов можно просто создавать объекты этих классов и использовать их:
Painter painter = new ListPainter(drawBeginIndex);
painter.Paint();
Правка: Я мог бы принять ответ, связанный с АОП. Но мне стоило стоило задать вопрос именно об архитектуре программы, а не о моменте с повторениями кода. В приложении я последую совету @tym32167 в комментариях и создам абстрактный холст для рисования, оградив элементы от проблем консоли.
Правка 2: Я принимаю ответ @Alexander Petrov, связанный с АОП.
В приложении же я создал класс, представляющий холст по примеру класса ConsoleWriter из ответа @tym32167. Созданный мною класс отличается только тем, что он хранит точки для обновления на холсте в Dictionary<>, по парам, состоящим из позиции точки и самой точки. Благодаря этому удалось выполнять меньше итераций в методе Flush(), что еще больше увеличило скорость обновления вывода.
В конце вы пришли к решению проблемы?
- Находил ответы с упоминанием АОП на английском stackoverflow. Для АОП нужен фреймворк? К сожалению, мне нужно решение простом C#.
– Azerum Aug 08 '20 at 12:58