Всем привет, меня зовут Аркадий, я студент НИУ ВШЭ и в данной статье мы с вами поговорим о задании PeerReview №6 NotePad++, а именно об архитектурах, которые подойдут для данного задания и некоторых паттернов.
Задание
В данном задании основной задачей является реализация приложения с графическим интерфейсом. Предлагаю далеко не отходить от темы и сразу же посмотреть на то, что такое архитектуры и как они могут нам помочь в данном случае.
Информация из этого поста не покрывает всех существующих архитектур и паттернов, но является вполне достаточной для реализации задания №6
Архитектуры приложений
Под архитектурой мы понимаем архитектурный шаблон проектирования, охватывающий всё приложение или какую-то его часть. Часто такую часть называют модулем. Из этих модулей и строится приложение. Под модулями в данном случае понимаются не C# модули, а архитектурные модули. Им может являться, например, один экран приложения или несколько связанных между собой экранов.
Тема архитектур вызывает много споров. Во-первых, у каждого разработчика свое представление о том, какой должна быть хорошая архитектура. Во-вторых, даже если взять какую-то конкретную архитектуру, то вы сможете найти несколько различающихся между собой реализаций. Ну и третья причина заключается в том, что идеальных архитектур не существует. У каждой из них есть преимущества и недостатки. Поэтому при обсуждении архитектурных подходов всегда есть за что поругать любое из решений.
Я расскажу лишь о трёх архитектурных решениях, которые считаю самыми подходящими для данного проекта, к тому же они имеют достаточно низкий порок вхождения
1. Apple MVC
Первая архитектура, которую мы разберем – MVC. Мы сразу сталкиваемся с тем, что под одним названием могут скрываться разные подходы. Например я предлагаю рассмотреть архитектуру Apple MVC, которая чуть лучше стандартной Classic MVC
Как и во многих других архитектурах, название – это аббревиатура. Каждая буква обозначает один из компонентов. Рассмотрим их по порядку.
M – Model. Роль модели могут играть как простые классы или структуры, так и сложные сущности, запрашивающие информацию из базы данных или из сети. Модель не знает о существовании UI (user interface). Можно рассматривать ее как ядро приложения, которое можно переиспользовать с другим интерфейсом. Например, использовать одну и ту же модель для версии программы под разные устройства
V – View. Вью – это представление. То, что пользователь видит и с чем взаимодействует. Ответственности вью – это отрисовка себя на экране и реагирование на действия пользователя, но оно не содержит логики и просто передает событие в Controller. В качестве представления в С#-приложениях используются Form и его наследники. Одну и ту же вью можно использовать на разных экранах приложения или даже перенести в другое.
C – Controller. Это связующее звено между моделью и представлением. Его ответственность заключается в том, чтобы принимать действия пользователя от View, обрабатывать их и в случае необходимости обновлять модель, а при изменении модели обновлять вью. Т.к. контроллеры содержат логику, специфичную для приложения, то их сложно переиспользовать.
Один контроллер не всегда соответствует одному экрану. Их можно вкладывать друг в друга, разбив таким образом большой контроллер на несколько маленьких. Например, можно реализовать TabBarController который будет являться контейнером для нескольких контроллеров. При нажатии на вкладки он просто подменяет один на другой.
MVC – хороший выбор для данного приложения. Эта архитектура имеет очень низкий порог вхождения (количество знаний и сил для её реализации). Однако есть одна проблема в реализации MVC от Apple. Они наделили ViewController обязанностью следить за жизненным циклом View. Из-за этого они очень сильно связаны. В итоге контроллер содержит не только обработку модели, но и взаимодействие с вью.
Для решения этой проблемы во всех остальных архитектурах ViewController считается частью вью. В нем остается обработка жестов, анимации и прочее взаимодействие с View. А всю остальную логику переносят в другое место. Таким образом мы избегаем так называемого «Massive View Controller»
Еще одна важная проблема, которая решается таким образом – сложность тестирования. Если вы хотите проверить какую-то логику, находящуюся в ViewController, то вам придется иметь дело с жизненным циклом вью.
2. MVP
Очевидным развитием MVC является MVP. Раз ViewController – это часть вью, то вместо него нужно добавить какую-то другую сущность, никак не связанную с View, в результате получаем Presenter.
Получившаяся схема очень похожа на MVC от Apple. Разница в том, что вся логика, не связанная с представлением, перемещается из ViewController в Presenter.
Обратите также внимание на то, что вью, как и в MVC, пассивная. Presenter передает в нее данные, но сама она их не запрашивает, хотя обращение к презентеру не запрещено. Оно может происходить, например, для вызова обработчика нажатия на кнопку.
3. MVVM
MVVM очень похоже на MVP. Разница в том, что вместо презентера в нем используется ViewModel. Вью модель хранит в себе состояние представления. Например, текст на экране или значение поля для ввода. Но в отличие от MVP, вью сама берет данные для отображения. Это можно реализовать разными способами, но часто используется data binding (паттерн в следующей главе). Это подход, при котором один объект следит за состоянием другого. Т.е. вью будет следить за своим значением, хранящимся во ViewModel, и изменять свое отображение при его изменении.
MVVM и MVP решают проблему большого контроллера и облегчают тестирование. Однако порог вхождения в проекты,
написанные на них, немного выше. Особенно это касается MVVM с биндингами, т.к. часто в таких проектах связывание данных используется не только между View и ViewModel. Такой код сложнее понять новичку, а еще сложнее отлаживать.
В данном проекте можно использовать эти или любые другие архитекрутрные решения. Лично я бы советовал использовать MVVM, так как он упростит реализацию настроек и вкладок.
Паттерны
Существует великое множество паттернов проектирования. Другое их название – шаблоны проектирования.
Паттерны – это многократно используемые решения часто встречающихся проблем при разработке. Они помогают писать код, который можно легко понять и использовать повторно. Паттерны помогают создавать слабо связанный код, в котором относительно легко и безболезненно можно заменять компоненты.
Мы рассмотрим самые распространенные паттерны, которые можно использовать в данном проекте. Давайте перечислим их:
Синглтон (Singleton)
Target-Action
Наблюдатель (Observer)
Команда (Command)
Я намеренно не включил в данные список Делегирование, т.к. считаю, что в данном проекте его использование будет излишним, в силу простоты программы.
В нескольких примерах кода буду использовать Swift, т.к. во-первых я его учу и надо практиковаться, а во-вторых **** я ваш C#, лучше **** *** *** ***** *** **** , чем буду на нём писать не на оценку. Спасибо за понимание.
1. Синглтон (Singleton)
Синглтон-паттерн широко используется при разработке программного обеспечения. Он гарантирует, что будет создан только один экземпляр класса и обеспечивает глобальную точку доступа для ресурсов, которые он предоставляет.
Применяется, когда нужно создать один и только один объект какого-либо класса на весь жизненный цикл приложения и доступ к которому необходимо получать из разных частей кода.
Примером применения этого паттерна является создания класса настроек приложения. Очевидно, что настройки приложения являются единственными в своём роде на всё приложение.
Другой пример применения данного паттерна – сервис GPS-навигации. Допустим, нам в приложении нужно отслеживать местоположение пользователя. В телефоне установлен единственный модуль GPS, и определять текущие координаты умеет только он. Ресурсы, которые предоставляет этот модуль, можно использовать совместно. В этом случае можно создать один глобальный синглтон-объект. И этот объект уже внутри себя будет использовать экземпляр класса LocationManager, предоставляющий единственную точку входа ко всем сервисам GPS-модуля.
class NetworkManager {
private(set) static var sharedInstance: NetworkManager = {
let manager = NetworkManager()
// additional setup code
return manager
}()
private init () {
}
func sendRequest() {
print("sending request")
}
}
// main program
let networkManager = NetworkManager.sharedInstance
networkManager.sendRequest() // sending request
2. Target-Action
Следующий паттерн, который мы рассмотрим, называется Target-Action. Обычно пользовательский интерфейс приложения состоит из нескольких графических объектов, и зачастую в роли таких объектов используются т.н. элементы управления. Это могут быть кнопки, переключатели, поля для ввода текста. Роль элемента управления в пользовательском интерфейсе довольно проста: он воспринимает намерение пользователя сделать какое-либо действие и дает указание другому объекту обработать этот запрос. Для связи между элементом управления и объектом, который может обработать запрос, и используется паттерн Target-Action. Target-Action – паттерн, в котором объект содержит информацию, необходимую для вызова метода у другого объекта при возникновении некоторого события, например, нажатия кнопки. Информация состоит из двух типов данных: селектора, который идентифицирует вызываемый метод (action), и объекта, чей метод вызывается (target). Остановимся чуть подробнее на том, что такое селекторы.
Фактически, селектор – это имя метода объекта или структуры, или уникальный идентификатор, который заменяет имя метода при компиляции исходного кода. Селектор сам по себе ничего не делает. Он просто идентифицирует какой-то метод.
Данный паттерн встроен в язык WindowsForm проект, так что в самостоятельной реализации не нуждается
3. Наблюдатель
В паттерне «Наблюдатель» один объект уведомляет другие объекты об изменениях своего состояния. Объектам, связанным таким образом, не нужно знать друг о друге – это и есть слабо связанный (а значит, гибкий) код. Этот паттерн чаще всего используют, когда надо уведомить «наблюдателя» об изменении свойств нашего объекта или о наступлении каких-либо событий в этом объекте. Обычно наблюдатель «регистрирует» свой интерес о состоянии другого объекта.
Когда состояние меняется, все объекты-наблюдатели будут уведомлены об изменении.
Уведомление – это сообщение, отправленное наблюдателям, чтобы оповестить их о каком-либо событии. Уведомления основаны на модели «подписка—публикация». Согласно ей, объект «издатель» (publisher) рассылает сообщения подписчикам (subscribers). Издатель ничего не должен знать о подписчиках.
interface IObservable {
void AddObserver(IObserver o);
void RemoveObserver(IObserver o);
void NotifyObservers();
}
class ConcreteObservable : IObservable {
private List<IObserver> observers;
public ConcreteObservable(){
observers = new List<IObserver>();
}
public void AddObserver(IObserver o){
observers.Add(o);
}
public void RemoveObserver(IObserver o){
observers.Remove(o);
}
public void NotifyObservers(){
foreach (IObserver observer in observers)
observer.Update();
}
}
interface IObserver {
void Update();
}
class ConcreteObserver :IObserver {
public void Update() {
// Some Action
}
}
В данном примере ( к сожалению написанном на c#, т.к. реализация с другими языками отличается существенно ):
IObservable: представляет наблюдаемый объект. Определяет три метода:
AddObserver()
(для добавления подписчика(наблюдателя)),RemoveObserver()
(удаление набюдателя) иNotifyObservers()
(уведомление наблюдателей)ConcreteObservable: конкретная реализация интерфейса IObservable. Определяет коллекцию объектов наблюдателей.
IObserver: представляет наблюдателя, который подписывается на все уведомления наблюдаемого объекта. Определяет метод
Update()
, который вызывается наблюдаемым объектом для уведомления наблюдателя.ConcreteObserver: конкретная реализация интерфейса IObserver
При этом наблюдаемому объекту не надо ничего знать о наблюдателе кроме того, что тот реализует метод Update()
. С помощью отношения агрегации реализуется слабосвязанность обоих компонентов. Изменения в наблюдаемом объекте не виляют на наблюдателя и наоборот.
В определенный момент наблюдатель может прекратить наблюдение. И после этого оба объекта - наблюдатель и наблюдаемый могут продолжать существовать в системе независимо друг от друга.
Команда
Команда — это поведенческий паттерн проектирования, который превращает запросы в объекты, позволяя передавать их как аргументы при вызове методов, ставить запросы в очередь, логировать их, а также поддерживать отмену операций......
т.к. В WindowsForms уже есть реализация обработчиков, то этот паттерн нам особо не понадобится, но я всё же оставлю ссылку на него
подробная информация о паттернах :
Если вам есть что добавить или вы не поняли о чем пошла речь, можно указать это в комментариях, а не писать минус в рейтинг, всем спасибо.
На этом я заканчиваю эту статью. Если она была вам полезна и поможет чем-нибудь, я буду очень рад.
Не игнорируйте паттерны и архитектуры, они вас спасут!