правки по ревью
This commit is contained in:
@@ -14,12 +14,16 @@
|
||||
Итак, начнём.
|
||||
Наш `MovieQuiz` - это не просто набор задач, это проект показывающий реальную работу. Мы с вами не просто пишем код, мы смотрим, как проект развивается поэтапно, как принимаются решения.
|
||||
|
||||
Часто вы будете слышать о «паттернах проектирования». Что это такое? Представьте себе путь, полный граблей. Паттерны — это результат того, что множество разработчиков уже наступили на все возможные грабли до вас и в процессе этого нашли ответы, как делать хорошо, чтобы не было так больно. Это концентрат опыта, позволяющий строить надёжные и понятные системы.
|
||||
По ходу выполнения запланированных на эту тему задач мы воспользуемся и параллельно разберёмся с паттернами: делегат, внедрение зависимостей, фабрика. Далее вы будете часто слышать о «паттернах проектирования». Давайте заглянем немного вперёд и посмотрим что это такое.
|
||||
|
||||
Сейчас, на пороге изучения темы «Ответственности» и последующего рефакторинга, у вас могут возникнуть мысли: «Зачем всё это? Итак всё работает!», «Да кода тут мало, я и так всё помню, где что у меня находится!». Это очень распространённая точка зрения, особенно когда проект ещё небольшой. По мере роста приложения код усложняется, команда может расширяться, и то, что было очевидно вам сегодня, через месяц может стать загадкой даже для вас самих, не говоря уже о коллегах.
|
||||
Представьте себе путь, полный граблей. Паттерны — это результат того, что множество разработчиков уже наступили на все возможные грабли до вас и в процессе этого нашли ответы, как делать хорошо, чтобы не было так больно. Это концентрат опыта, позволяющий строить надёжные и понятные системы.
|
||||
|
||||
Сейчас, на пороге изучения темы «Ответственности» и последующего улучшения структуры кода, у вас могут возникнуть мысли: «Зачем всё это? Итак всё работает!», «Да кода тут мало, я и так всё помню, где что у меня находится!». Это очень распространённая точка зрения, особенно когда проект ещё небольшой. По мере роста приложения код усложняется, команда может расширяться, и то, что было очевидно вам сегодня, через месяц может стать загадкой даже для вас самих, не говоря уже о коллегах.
|
||||
|
||||
Поэтому переделки, которые мы будем делать, жизненно необходимы. Умение грамотно разделять ответственности — это не прихоть, а важный практический навык. Он закладывает фундамент для создания качественного, поддерживаемого и масштабируемого кода. Да, поначалу это может показаться дополнительной работой, но поверьте опыту предыдущих поколений разработчиков: усилия, вложенные в правильную структуру сейчас, окупятся в будущем, сэкономив вам часы, а то и дни отладки и головной боли.
|
||||
|
||||
> Если есть желание углубиться в тему, то паттерны проектирования можно изучать по книгам и статьям в интернете. Одна из самых известных книг это [«Паттерны объектно-ориентированного проектирования»](https://books.yandex.ru/books/oYgnESD2), авторов которой часто называют «Бандой четырёх» (Gang of Four, GoF) - Джон Влиссидес, Ральф Джонсон, Ричард Хелм, Эрих Гамма.
|
||||
|
||||
## Понятие ответственности
|
||||
|
||||
Вы узнаете о понятии ответственности и связности в коде; проведёте аудит проекта и увидите, на каких сущностях ответственности слишком много.
|
||||
|
||||
@@ -127,6 +127,8 @@ struct QuizStepViewModel {
|
||||
|
||||
Когда связанность кода низкая, вы можете менять программу, затрагивая как можно меньше её отдельных частей. В таком случае меньше шансов сделать ошибку, например забыть изменить обновление экрана после того, как получены модели вопросов. Низкая связанность кода делает его более гибким, потому что позволяет легко менять генерацию вопросов на загрузку из сети. Если же код связан или у вас есть объект, который в программе отвечает за всё, — любое изменение будет даваться с трудом.
|
||||
|
||||
Низкая связанность также упрощает тестирование - очень важную часть разработки, когда разработчики и разработчицы проверяют написанный код. Когда компоненты слабо связаны, их можно тестировать изолированно друг от друга, что проще чем несколько связанных друг с другом компонентов. Это означает, что для тестирования одного компонента вам не нужно настраивать или имитировать множество других зависимых объектов. Например, если ваш код загрузки данных не зависит от кода сохранения данных на устройстве, вы можете тестировать его, предоставляя тестовые данные и проверяя результат, не беспокоясь о том, как эти данные будут сохраняться. И наоборот, вы можете проверять сохранение данных без долгих походов сеть. (Тестирование вы будете изучать позже, в тринадцатом спринте)
|
||||
|
||||
> Загрузка из сети не должна зависеть от того, какой у вас UI, сколько на экране кнопок и других элементов. Классы нужно делать как можно менее зависимыми от друг друга — или не опираться на конкретную реализацию класса.
|
||||
|
||||
КНОПКА Договорились!
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
3. Обновление состояния текстов и картинок с помощью `ViewModel`.
|
||||
4. Решение отобразить алерт о результатах игры или следующий вопрос.
|
||||
|
||||
Сейчас вы попрактикуетесь в разделении ответственностей и начнёте с `QuizQuestion`. Для этого вы создадите новую сущность — фабрику вопросов.
|
||||
Сейчас вы попрактикуетесь в разделении ответственностей и начнёте с `QuizQuestion`. Для этого вы создадите новую сущность — фабрику вопросов. *Фабрикой называют класс, который создаёт и возвращает экземпляры других классов или структур.* В нашем случае фабрика будет создавать вопросы для квиза.
|
||||
|
||||
КНОПКА Начнём!
|
||||
|
||||
|
||||
+7
-1
@@ -102,7 +102,13 @@ guard let currentQuestion = currentQuestion else {
|
||||
}
|
||||
```
|
||||
|
||||
КНОПКА Понятно!
|
||||
### Проверим наши изменения
|
||||
|
||||
На этом этапе уже можно запустить приложение и проверить его работоспособность. После того как вы исправили все ошибки компиляции запустите проект и убедитесь что: проект собирается и запускается без ошибок; приложение корректно показывает вопросы один за другим; количество правильных ответов верно считается; финальный экран с результатами квиза отображается корректно.
|
||||
|
||||
Если после запуска вы заметили, что приложение работает не так, как ожидалось (например, вопросы не отображаются, счетчик работает неправильно или проект не запускается), не переживайте. Внимательно перепроверьте все шаги, которые вы выполнили в этом уроке, особенно изменения в коде, связанные с `questionFactory` и `currentQuestion`. Часто проблема кроется в небольшой опечатке или пропущенном шаге. Сравните ваш код с примерами, приведенными в уроке.
|
||||
|
||||
КНОПКА Понятно и всё работает!
|
||||
|
||||
Тогда перейдём к практике, чтобы материал лучше усвоился.
|
||||
|
||||
|
||||
@@ -51,9 +51,9 @@ private let questionFactory: QuestionFactoryProtocol = QuestionFactory()
|
||||
|
||||
Теперь давайте закрепим знания.
|
||||
|
||||
# Проверим изученное
|
||||
## Проверим изученное
|
||||
|
||||
## Выберите верные утверждения об абстракции в разработке.
|
||||
**Вопрос:** Выберите верные утверждения об абстракции в разработке
|
||||
|
||||
- [x] Абстракция — это процесс выделения и описания характеристик объекта или явления без несущественных деталей.
|
||||
|
||||
@@ -76,7 +76,7 @@ private let questionFactory: QuestionFactoryProtocol = QuestionFactory()
|
||||
5. Да, всё так!
|
||||
6. Абстракция не связана с преобразованием конкретных объектов в обобщённые.
|
||||
|
||||
# Подведём итоги
|
||||
## Подведём итоги
|
||||
|
||||
Вы рассмотрели класс `MovieQuizViewController` и выбрали часть работы, которая не относится к нему напрямую — генерацию вопросов. Вы вынесли её в отдельный класс и соединили `MovieQuizViewController` с фабрикой вопросов с помощью композиции. Можно было сделать это с помощью агрегации, но это бы усложнило работу.
|
||||
|
||||
|
||||
+17
-11
@@ -6,7 +6,7 @@
|
||||
|
||||
Вы уже знаете, что код можно разделять на небольшие участки логики и потом их связывать — такой код легче читать, отлаживать и менять.
|
||||
|
||||
В прошлых модулях вы вынесли участок логики из контроллера в фабрику `QuestionFactory`. Но пока только контроллер обращается к фабрике через метод `requestNextQuestion`. Нужно сделать так, чтобы «общение» шло в обе стороны и фабрика передавала контроллеру результаты работы в момент готовности.
|
||||
В прошлых уроках вы вынесли участок логики из контроллера в фабрику `QuestionFactory`. Но пока только контроллер обращается к фабрике через метод `requestNextQuestion`. Нужно сделать так, чтобы «общение» шло в обе стороны и фабрика передавала контроллеру результаты работы в момент готовности.
|
||||
|
||||
ДИАЛОГ
|
||||
|
||||
@@ -25,11 +25,9 @@
|
||||
|
||||
## Паттерн «делегат»
|
||||
|
||||
ДИАЛОГ
|
||||
Что такое паттерн мы уже обсуждали в уроке "введении в тему". Здесь лишь напомним, что паттерны (от англ. Patterns) в разработке — это идеи решений, которые кто-то уже придумал до нас.
|
||||
|
||||
**Студент:** Что такое паттерн?
|
||||
|
||||
**Практикум:** Паттерны (от англ. Patterns) в разработке — это шаблоны, или идеи решений, которые кто-то уже придумал до вас. Паттернов довольно много, они решают разные задачи, например как организовать или структурировать код. Со временем вы начнёте ориентироваться в паттернах — на курсе мы разберём те, что используются чаще всего.
|
||||
Паттернов довольно много, они решают разные задачи, например как организовать или структурировать код. Со временем вы начнёте ориентироваться в паттернах — на курсе мы разберём те, что используются чаще всего.
|
||||
|
||||

|
||||
|
||||
@@ -281,9 +279,9 @@ engineer.fixStarship()
|
||||
|
||||
С кодом разобрались — теперь пора закреплять знания.
|
||||
|
||||
# Проверим изученное
|
||||
## Проверим изученное
|
||||
|
||||
## Что из этого верно про паттерны программирования?
|
||||
**Вопрос 1:** Что из этого верно про паттерны программирования?
|
||||
|
||||
- [x] Это повторяемые архитектурные решения, с помощью которых можно решить типичные проблемы разработки.
|
||||
|
||||
@@ -305,8 +303,8 @@ engineer.fixStarship()
|
||||
- Нет, их можно применять в любых проектах.
|
||||
- Верно!
|
||||
- Нет, паттерны программирования — это рекомендации и стандарты, ими пользоваться необязательно.
|
||||
|
||||
## Выберите верные утверждения о паттерне «Делегат».
|
||||
|
||||
**Вопрос 2:** Выберите верные утверждения о паттерне «Делегат»
|
||||
|
||||
- [x] Это поведенческий паттерн: он позволяет передать ответственность за определённое поведение одного объекта другому.
|
||||
|
||||
@@ -326,7 +324,7 @@ engineer.fixStarship()
|
||||
- Нет, его можно применять в процедурном и объектно-ориентированном программировании.
|
||||
- Так и есть!
|
||||
|
||||
## Где используется паттерн «Делегат»?
|
||||
**Вопрос 3:** Где используется паттерн «Делегат»?
|
||||
|
||||
- [ ] Пример 1
|
||||
- [x] Пример 2
|
||||
@@ -433,10 +431,18 @@ class ViewController: SliderDelegate {
|
||||
|
||||
(Пояснение) Тут у класса `Slider` свойство `delegate`. А свойство `delegate` — это ссылка на объект, который реализует протокол `SliderDelegate`. Когда метод `didChangeValue(_:)` вызывается на объекте `Slider`, он делегирует выполнение метода `sliderValueChanged(value:)` объекту, который находится в свойстве `delegate`. Значит, объект класса `ViewController` — делегат для слайдера.
|
||||
|
||||
# Подведём итоги
|
||||
## Подведём итоги
|
||||
|
||||
Вы познакомились с делегированием и инъекцией зависимостей и применили их в коде. Всё получилось!
|
||||
|
||||
Давайте кратко повторим ключевые моменты:
|
||||
|
||||
**Паттерн «Делегат»** — способ проектирования позволяющий одному объекту передавать часть своих обязанностей другому объекту. Делегат помогает уменьшить связанность между компонентами и повысить гибкость кода.
|
||||
|
||||
**Инъекция зависимостей (Dependency Injection, DI)** — способ позволяющий объекту получать необходимые ему другие объекты, снаружи, а не создавать их самостоятельно. DI делает код более тестируемым, модульным и легко конфигурируемым.
|
||||
|
||||
Понимание и применение этих паттернов поможет вам создавать более структурированные и поддерживаемые приложения.
|
||||
|
||||
В следующем уроке вы реализуете паттерн «Делегат» для фабрики вопросов в приложении MovieQuiz. Как всегда, мы будем рядом и поможем разобраться с темой.
|
||||
|
||||
**Кнопка** Отлично. Вперёд!
|
||||
|
||||
+5
-3
@@ -320,7 +320,7 @@ override func viewDidLoad() {
|
||||
|
||||
В случае инъекции через свойство нам понадобится:
|
||||
|
||||
- `QuestionFactory` с пустым инициализатором или без него; удалите самостоятельно реализованный иницилизатор фабрики `init(delegate: QuestionFactoryDelegate)` из предыдущего примера.
|
||||
- `QuestionFactory` с пустым инициализатором или без него; удалите самостоятельно реализованный инициализатор фабрики `init(delegate: QuestionFactoryDelegate)` из предыдущего примера.
|
||||
- объявленное свойство `var questionFactory: QuestionFactoryProtocol?` в контроллере;
|
||||
- предварительно созданный экземпляр `QuestionFactory` внутри `viewDidLoad` для установки делегата;
|
||||
- сохранить экземпляр фабрики в свойство класса.
|
||||
@@ -347,11 +347,13 @@ override func viewDidLoad() {
|
||||
1. Мы снова пользуемся отложенной инициализацией фабрики — чтобы и использовать `self`, и иметь доступ к свойству делегата(`delegate`) у **класса** фабрики, ведь в **протоколе** такого свойства нет.
|
||||
2. Создаём экземпляр фабрики для её настройки.
|
||||
3. Устанавливаем связь «фабрика − делегат».
|
||||
4. Сохраняем подготовленный экземляр в свойство контроллера — для этого используем обращение через `self`. Обращение к `self` нужно, потому что название переменной внутри функции `viewDidLoad` совпадает с названием свойства класса. Если названия будут разными (вы можете это проверить!) — `self` можно опустить.
|
||||
4. Сохраняем подготовленный экземпляр в свойство контроллера — для этого используем обращение через `self`. Обращение к `self` нужно, потому что название переменной внутри функции `viewDidLoad` совпадает с названием свойства класса. Если названия будут разными (вы можете это проверить!) — `self` можно опустить.
|
||||
|
||||
Вы должны передавать делегата в фабрику через свойство — измените объявление `questionFactory`.
|
||||
|
||||
Мы разобрали два способа инъекции зависимостей из трёх. С последним, через метод, вы справитесь сами — мы в этом уверены! Способ очень похож на инъекцию через свойство.
|
||||
### Самостоятельное задание
|
||||
|
||||
Мы разобрали два способа инъекции зависимостей из трёх. С последним, через метод, вы справитесь сами — мы в этом уверены! Способ очень похож на инъекцию через свойство. Попробуйте реализовать его самостоятельно.
|
||||
|
||||
> Вы можете оставить любой из вариантов реализации инъекции зависимостей — какой больше нравится.
|
||||
|
||||
|
||||
@@ -26,23 +26,18 @@
|
||||
|
||||
> По нажатию на кнопку алерта контроллер должен обновить состояние и запустить игру заново.
|
||||
|
||||
СКРЫВАШКА
|
||||
СКРЫВАШКА Подсказка
|
||||
|
||||
`AlertPresenter` должен «знать» о контроллере, на котором будет отображаться алерт. Инъектируйте его в `AlertPresenter`.
|
||||
`AlertPresenter` должен «знать» о контроллере, на котором будет отображаться алерт. Внедрите его в `AlertPresenter`.
|
||||
|
||||
Если вам сложно решить задачу, вернитесь к предыдущим урокам и посмотрите, какие шаги мы проделали для фабрики вопросов. Если не выходит разобраться — смело обращайтесь к кураторам, они помогут!
|
||||
|
||||
Когда сделаете задание, запустите проект и пройдите квиз. Если проект собрался и после прохождения квиза вы увидели алерт — принимайте поздравления!
|
||||
***
|
||||
Надеемся, эта задачу удалось решить быстро. Тема Responsibilities, или «Ответственность» закончена — встретимся в новой и поговорим про хранение данных.
|
||||
|
||||
КНОПКА До встречи!
|
||||
КОНЕЦ СКРЫВАШКИ
|
||||
|
||||
# Для проверяющего (НЕ НА ПЛАТФОРМУ)
|
||||
СКРЫВАШКА Авторское решение
|
||||
|
||||
`ResultAlertPresenter` может работать как на делегатах, так и на замыканиях — на усмотрение студента. Обратите внимание на структуру проекта: студент создал файл презентера где попало или структурировал иерархию?
|
||||
|
||||
**Авторское решение**
|
||||
`AlertModel`
|
||||
|
||||
```Swift
|
||||
@@ -57,7 +52,7 @@ struct AlertModel {
|
||||
`AlertPresenter`
|
||||
|
||||
```Swift
|
||||
class AlertPresenter {
|
||||
final class AlertPresenter {
|
||||
func show(in vc: UIViewController, model: AlertModel) {
|
||||
let alert = UIAlertController(
|
||||
title: model.title,
|
||||
@@ -78,20 +73,34 @@ class AlertPresenter {
|
||||
`MovieQuizViewController`
|
||||
|
||||
Добавляем презентер
|
||||
|
||||
```Swift
|
||||
private var alertPresenter = AlertPresenter()
|
||||
```
|
||||
|
||||
Меняем метод `show(quiz result:)`
|
||||
|
||||
```Swift
|
||||
func show(quiz result: QuizResultsViewModel) {
|
||||
let message = presenter.makeResultsMessage()
|
||||
let model = AlertModel(title: result.title, message: message, buttonText: result.buttonText) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let message = presenter.makeResultsMessage()
|
||||
let model = AlertModel(title: result.title, message: message, buttonText: result.buttonText) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.presenter.restartGame()
|
||||
}
|
||||
|
||||
alertPresenter.show(in: self, model: model)
|
||||
self.presenter.restartGame()
|
||||
}
|
||||
|
||||
alertPresenter.show(in: self, model: model)
|
||||
}
|
||||
```
|
||||
|
||||
КОНЕЦ СКРЫВАШКИ
|
||||
|
||||
Надеемся, эта задачу удалось решить быстро. Тема Responsibilities, или «Ответственность» закончена — встретимся в новой и поговорим про хранение данных.
|
||||
|
||||
КНОПКА До встречи!
|
||||
|
||||
---
|
||||
|
||||
**Для проверяющего (НЕ НА ПЛАТФОРМУ)**
|
||||
|
||||
`ResultAlertPresenter` может работать как на делегатах, так и на замыканиях — на усмотрение студента. Обратите внимание на структуру проекта: студент создал файл презентера где попало или структурировал иерархию?
|
||||
|
||||
@@ -4,7 +4,7 @@ MVC состоит из трёх компонентов:
|
||||
|
||||
- модели — в ней содержится основная бизнес-логика приложения;
|
||||
- представления (View) — то, что пользователь видит на экране и с чем может взаимодействовать нажатиями, свайпами и другими жестами;
|
||||
- контроллера — это медиатор (посредник) между моделью и представленим. Он переводит данные из «модельной» формы в ту, которую можно наглядно отобразить для пользователя (числа и даты преобразовываются в строки, `Data` ` в `UIImage`, и так далее). Также контроллер получает информацию о пользовательских событиях от View и обновляет состояние модели (например, вызывая на ней соответствующие методы).
|
||||
- контроллера — это медиатор (посредник) между моделью и представлением. Он переводит данные из «модельной» формы в ту, которую можно наглядно отобразить для пользователя (числа и даты преобразовываются в строки, `Data` в `UIImage`, и так далее). Также контроллер получает информацию о пользовательских событиях от View и обновляет состояние модели (например, вызывая на ней соответствующие методы).
|
||||
|
||||
В этом уроке мы поговорим о недостатках MVC, его «нездоровой» форме — `Massive View Controller`, а также о том, как справиться с проблемами `Massive VC` при помощи паттерна `Model View Presenter` (MVP).
|
||||
|
||||
@@ -18,7 +18,7 @@ MVC — замечательный паттерн! Он помогает пис
|
||||
Как это возможно?
|
||||
|
||||
Ответ
|
||||
Такое может случиться при разных обстоятельствах. Наиболее частая причина — наличие существенных ограничений по времени, из-за которых мы можем не успевать «сделать всё правильно».
|
||||
Такое может случиться при разных обстоятельствах. Наиболее частая причина — наличие существенных ограничений по времени, из-за которых мы можем не успевать «сделать всё правильно».
|
||||
|
||||
Например, в `MovieQuizViewController` есть свойства:
|
||||
|
||||
@@ -42,7 +42,7 @@ MVC — замечательный паттерн! Он помогает пис
|
||||
|
||||
Такое положение вещей говорит нам о том, что эти свойства и алгоритм их преобразований, по идее, должны принадлежать миру моделей, а не контроллеру или View. Но из-за ограничений по срокам или ещё по каким-то причинам код остаётся в контроллере.
|
||||
|
||||
> В нашем проекте мы допустили такую ситуацию исключительно в иллюстративных целях (^_-)! Но в следующем уроке мы раскажем, как можно это исправить.
|
||||
> В нашем проекте мы допустили такую ситуацию исключительно в иллюстративных целях (^_-)! Но в следующем уроке мы расскажем, как можно это исправить.
|
||||
|
||||
К сожалению, чем больше и сложнее устроен проект, тем чаще возникает такая ситуация.
|
||||
|
||||
@@ -60,7 +60,7 @@ MVC — замечательный паттерн! Он помогает пис
|
||||
|
||||
> Этот феномен в [шутку](https://yandex.ru/search/?text=massive+view+controller&lr=10335) называют `Massive View Controller` («массив» вместо «модель»).
|
||||
|
||||
Шутки шутками, но как решить проблему `Massive VC`? В этом нам поможет использование другого, но очень похожего паттерна, который называется `Model-View-Presenter`, или MVP (ГЛОССАРИЙ: От англ. «Модель–Представление-Презентация»).
|
||||
Шутки шутками, но как решить проблему `Massive VC`? В этом нам поможет использование другого, но очень похожего паттерна, который называется `Model-View-Presenter`, или MVP (ГЛОССАРИЙ: От англ. «Модель–Представление-Презентация»).
|
||||
|
||||
# Model View Presenter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user