Даб работа по методам программирования. обновлено пронько. 1. Классы в C#. Модификаторы доступа. Поля, свойства, индексаторы
Скачать 0.78 Mb.
|
24. Шаблон проектирования Шаблонный метод (Template Method). Назначение. Пример использования. Шаблонный метод (Template Method) определяет общий алгоритм поведения подклассов, позволяя им переопределить отдельные шаги этого алгоритма без изменения его структуры. Данный шаблон проектирования используется: Когда планируется, что в будущем подклассы должны будут переопределять различные этапы алгоритма без изменения его структуры Когда в классах, реализующим схожий алгоритм, происходит дублирование кода. Вынесение общего кода в шаблонный метод уменьшит его дублирование в подклассах. В качестве UML шаблон проектирования можно изобразить таким образом: Пример:
25. Отделение логики от представления. MVC. Пример. Термин модель-представление-контроллер (model-view-controller) используется с конца 70-х гг. прошлого столетия. Эта модель явилась результатом проекта Smalltalk в компании Xerox PARC, где она была задумана как способ организации некоторых из ранних приложений графического пользовательского интерфейса. Некоторые из нюансов первоначальной модели MVC были связаны с концепциями, специфичными для Smalltalk, такими как экраны и инструменты, но более глобальные понятия все еще применимы к приложениям, и особенно хорошо они подходят для веб-приложений (MVC нашел отличное применение в ASP.NET, но ниже мы рассмотрим этот паттерн в WPF). Если оперировать понятиями высокого уровня, архитектурный шаблон MVC означает, что приложение MVC будет разделено, по крайней мере, на три части: Модели Содержат или представляют данные, с которыми работают пользователи. Они могут быть простыми моделями представлений, которые только представляют данные, передаваемые между представлениями и контроллерами; или же они могут быть моделями предметной области, которые содержат бизнес-данные, а также операции, преобразования и правила для манипулирования этими данными. Представления Применяются для визуализации некоторой части модели в виде пользовательского интерфейса. Контроллеры Обрабатывают поступающие запросы, выполняют операции с моделью и выбирают представления для визуализации пользователю. Ниже структура MVC показана на диаграмме: Если рассматривать приложение в призме бизнес-логики, то можно выделить три уровня на которых строится приложение: Уровень представления Данный уровень отвечает за отображение данных, обеспечение обратной связи с пользователем, сбор пользовательской информации, которая передается в уровень бизнес-логики для обработки. Бизнес-уровень Бизнес-уровень или, если говорить проще, уровень приложения, обеспечивает логику взаимодействия представления и данных. В MVC бизнес-уровень реализует структуру модели. Уровень данных Уровень данных отвечает за получение, передачу и сохранение данных в файле, базе данных, службе или XML. Данная структура представлена ниже на рисунке: Чтобы лучше вникнуть в этот паттерн, стоит применить его на практике. Для этого создайте WPF-приложение и сверстайте такую форму: Это и есть View — его видит пользователь. Тут есть кнопка, при нажатии на которую вызывается Controller: private void CalculateButton_Click(object sender, RoutedEventArgs e) //Метод, который вызывается при нажатии на кнопку "Посчитать" { //Валидация полученных данных string text1 = Num1TextBox.Text.Trim(); string text2 = Num2TextBox.Text.Trim(); int num1 = 0; int num2 = 0; if (!string.IsNullOrEmpty(text1) && !string.IsNullOrEmpty(text2)) { try { num1 = Convert.ToInt32(text1); num2 = Convert.ToInt32(text2); } catch (Exception exc) { } Calculate(num1, num2); //Передача данных модели } } Контроллер получает пользовательский ввод и обрабатывает данные. Он также может проверять права пользователя. Если валидация проходит успешно, данные передаются в Model: public void Calculate(int num1, int num2) { result = num1 + num2; //Проведение операций с полученными данными UpdateView(); //Вызов обновления представления } Модель проводит с этими данными необходимые операции, а затем вызывает метод обновления вида: public void UpdateView() { ResultTextBlock.Text = result.ToString(); //Изменение вида } 26. Отделение логики от представления. MVP. Реализация в Windows Forms. Основные черты: Интерфейс View (IView), который предоставляет некий контракт для отображения данных, увеличивает тестируемость; Presenter — конкретная реализация IView, которая умеет отображать саму себя в конкретном интерфейсе (будь то Windows Forms, WPF или даже консоль) и ничего не знает о том, кто ей управляет; Model — предоставляет некоторую бизнес-логику (примеры: доступ к базе данных, репозитории, сервисы). Может быть представлена в виде класса или опять же, интерфейса и реализации; Presenter содержит ссылку на View через интерфейс (IView), управляет им, подписывается на его события, производит простую валидацию введенных данных; также содержит ссылку на Model или на ее интерфейс, передавая в нее данные из View и запрашивая обновления. Код доступа к данным находится вне модели, а вся бизнес-логика приложения концентрируется в модели (эта схожесть подтверждает то, что MVP является производным от MVC, изменилась только логика представления). Один из вариантов организации проекта на Windows Forms: DomainModel — содержит сервисы и всевозможные репозитории, одним словом — модель; Presentation — содержит логику приложения, не зависящую от визуального представления, т.е. все Представители, интерфейсы Представлений и остальные базовые классы; UI — Windows Forms приложение, содержит только лишь формы (реализацию интерфейсов Представлений) и логику запуска; Tests — unit-тесты. Здесь малая связанность классов (использование интерфейсов, событий) позволяет относительно свободно менять логику любого компонента, не ломая остального и предоставляет большие возможности при unit-тестировании. 27. Отделение логики от представления. MVPM. MVVM. Реализация в WPF/Avalonia UI. 28. Чистая архитектура Robert Martin. Мотивация. Слои и связи между ними. Правило зависимостей. Работа со средствами доставки (ГПИ настольного приложения, REST API, веб-браузер). 29. Чистая архитектура Robert Martin. Мотивация. Слои и связи между ними. Правило зависимостей. Работа с базами данных. 30. Управляемая тестами разработка (TDD). Тестовые двойники: fake-, stub-, mock-объекты. Fake (имитация) Имитация (или поддельные объекты) — это объекты, имеющие рабочие реализации, но не такие, как у настоящих рабочих объектов. Обычно они идут коротким путём и имеют упрощённую версию реального объекта. В качестве примера может быть реализация в оперативной памяти объектов доступа к данным (Data Access Object) или репозиторий (Repository). Реализация поддельных объектов не будет привлекать базу данных, но будет использовать простую коллекцию для хранения данных. Это позволяет нам выполнять интеграционный тест сервисов без участия базы данных и выполнения тем самым трудоёмких запросов. @Profile("transient") public class FakeAccountRepository implements AccountRepository { Map public FakeAccountRepository() { this.accounts.put(new User("john@bmail.com"), new UserAccount()); this.accounts.put(new User("boby@bmail.com"), new AdminAccount()); } String getPasswordHash(User user) { return accounts.get(user).getPasswordHash(); } } Помимо тестирования, реализация поддельных объектов может пригодиться для прототипирования. Мы можем быстро внедрить и запустить нашу систему с хранилищем в оперативной памяти, откладывая решения относительно проектирования базы данных. Другим примером может быть поддельная платёжная система, которая всегда будет возвращать успешные платежи. Stub (заглушка) Заглушка— объект, содержащий предопределённые данные и использует их для ответа на вызовы во время тестов. Она используется, когда мы не можем или не хотим привлекать объекты, которые бы отвечали реальными данными или имели бы нежелательные побочные эффекты. В качестве примера может быть объект, который должен получить некоторые данные из базы данных в качестве результата при вызове метода. Вместо реального объекта, мы вводим заглушку и определяем в ней, какие данные она должна вернуть. public class GradesService { private final Gradebook gradebook; public GradesService(Gradebook gradebook) { this.gradebook = gradebook; } Double averageGrades(Student student) { return average(gradebook.gradesFor(student)); } } Вместо того, чтобы делать вызов к базе данных магазина к таблице с оценками (gradebook), чтобы получить настоящие оценок студентов, мы предварительно настраиваем заглушку с оценками, которые будут возвращены. Мы определяем именно столько данных для тестирования, сколько нужно для вычисления средней оценки. public class GradesServiceTest { private Student student; private Gradebook gradebook; @Before public void setUp() throws Exception { gradebook = mock(Gradebook.class); student = new Student(); } @Test public void calculates_grades_average_for_student() { when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook double averageGrades = new GradesService(gradebook).averageGrades(student); assertThat(averageGrades).isEqualTo(8.0); } } Принцип разделения команд и запросов Методы, возвращающие некоторый результат и не изменяющие состояние системы, называют Query (далее — запрос). Хорошим примером является метод averageGrades, возвращающий среднее оценки учащихся. Double averageGrades(Student student); Он возвращает значение и не имеет побочных эффектов. Как мы видели в примере оценки студентов, для тестирования этого типа метода мы используем заглушку. Мы заменяем настоящую функциональностью, предоставляя значений, необходимые для выполнения работы фактической реализации. Затем значения, возвращаемые методом, могут использоваться для утверждений в тестах. Существует также другая категория методов — Command (далее — команда). Это когда метод выполняет некоторые действия, которые изменяют состояние системы, но мы не ожидаем от него никакого возвращаемого значения. void sendReminderEmail(Student student); Хорошей практикой является разделение методов объекта на две категории. Такая практика называется принципом разделения команд и запросов к данным (Command Query separation или command-query responsibility segregation, CQRS), описанная Бертраном Мейером (Bertrand Meyer) в его книге “Object Oriented Software Construction”. Для тестирования методов типа запроса мы должны отдать предпочтение заглушкам, так как можем проверить возвращаемое значение метода. Но как насчёт типа методов команд, к примеру, метода отправки электронной почты? Как проверить его, если он не возвращает никаких значений? Ответ — использовать подставной объект, последний тип тестовых двойников, которые мы собираемся рассмотреть. Подставной объект (Mock) Подставные объекты регистрируют получаемые вызовы. В тестовом утверждении мы можем проверить, используя подставной объект, что все ожидаемые действия (иначе — вызовы) были выполнены. Мы используем подставной объект, когда не хотим вовлекать реально работающий код или когда нет простого способа проверки, что код действительно был выполнен. Нет возвращаемого значения, а значит нет лёгкого пути проверить изменение состояния системы. В качестве примера может послужить функциональность, вызывающая сервис отправки электронной почты. Мы не хотим отправлять электронные письма каждый раз, когда запускаем тест. Кроме того, в тестах нелегко проверить, что письмо на самом деле было отправлено. Единственно, что мы можем сделать — это проверить результаты функциональности, которые выполняется в нашем тесте. Другими словами — убедиться, что был вызван сервис отправки электронной почты. Похожий случай представлен в следующем примере: public class SecurityCentral { private final Window window; private final Door door; public SecurityCentral(Window window, Door door) { this.window = window; this.door = door; } void securityOn() { window.close(); door.close(); } } Мы вряд ли захотим закрывать настоящие двери, чтобы проверить, работает ли метод обеспечения защиты, верно? Вместо этого мы создаём подставные объекты для дверей и окон и используем их в тестовом коде. public class SecurityCentralTest { Window windowMock = mock(Window.class); Door doorMock = mock(Door.class); @Test public void enabling_security_locks_windows_and_doors() { SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock); securityCentral.securityOn(); verify(doorMock).close(); verify(windowMock).close(); } } После выполнения метода securityOn, подставные объекты окон и дверей зарегистрируют все взаимодействия с ними (читай — вызовы их). Это позволяет нам проверить, что окна и двери были проинструктированы о закрытии. Это всё, что нам нужно проверить, с точки зрения SecurityCentral. Вы можете спросить, как мы можем утверждать, будут ли закрыты настоящие окна и двери, если мы тестируем на подставных объектах? Ответ в том, что мы не можем. Это не ответственность SecurityCentral. Это ответственность только двери и окна, чтобы они закрыли себя, когда получают верный сигнал об этом. Мы можем протестировать их независимо в разных модульных тестах. |