.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День триста тридцать девятый. #DesignPatterns
Паттерны проектирования
6. Паттерн «Посетитель» (Visitor). Окончание
Пример использования паттерна «Посетитель»
Каноническим примером демонстрации полиморфизма является иерархия фигур с базовым классом Shape, имеющим абстрактный метод Area (площадь фигуры), и несколькими потомками, например, Circle, Rectangle и Triangle, каждый из которых переопределяет метод Area. Допустим, что нам понадобилось добавить метод вычисления периметра фигуры. Ранее на канале приводился пример с использованием сопоставления по шаблону.
Рассмотрим, как можно добавлять функциональность, используя паттерн «Посетитель».
1. Интерфейс посетителя:
public interface IShapeVisitor {
void Visit(Circle circle);
void Visit(Triangle square);
void Visit(Rectangle rectangle);
}
2. Добавим в иерархию классов метод Accept, принимающий посетителя, и вызывающий в потомках метод Visit посетителя:
public abstract class Shape {
// … другие методы …
public abstract void Accept(IShapeVisitor visitor);
}
public class Circle : Shape {
// … другие методы …
public override void Accept(IShapeVisitor visitor)
=> visitor.Visit(this);
}
// … аналогично для других потомков
3. Реализуем конкретного посетителя для вычисления периметра:
public class PerimeterVisitor : IShapeVisitor
{
public double Perimeter { get; private set; }
public void Visit(Circle circle) {
Perimeter = 2 * Math.PI * circle.Radius;
}
public void Visit(Rectangle rect) {
Perimeter = 2 * (rect.Height + rect.Width);
}
public void Visit(Triangle tri) {
Perimeter = tri.SideA + tri.SideB + tri.SideC;
}
}
4. Создаём метод расширения:
public static class ShapeExtentions {
public static double Perimeter(this Shape shape) {
var visitor = new PerimeterVisitor();
shape.Accept(visitor);
return visitor.Perimeter;
}
}
// Вызываем
Shape shape = … //получаем форму
var p = shape.Perimeter();
Таким образом для любой новой функции (например, рисования фигуры) нам достаточно выполнить пункты 3 и 4:
- создать конкретный класс посетителя (DrawVisitor);
- создать и вызвать соответствующий метод расширения.
Понятно, что для такого простого примера единого метода расширения с сопоставлением по шаблону, вероятно, будет достаточно. Однако, если добавляемый функционал сложен и объёмен, реализация его в отдельном классе посетителя с разбиением на методы под каждый тип объекта будет предпочтительнее.

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 18.
День триста сороковой. #Оффтоп
Ещё одно ненапряжное видео. Абсолютно гениальный доклад Дилана Битти “The Art of Code” на конференции Build Stuff 2019. Философские рассуждения на тему, является ли программирование искусством. С кучей разнообразных примеров, красивой презентацией и тонким английским юмором. Я бы сказал, вот так нужно завлекать людей в программирование. Знаете, что такое квайн? Или что есть программа на языке Chef, которая в то же время является рецептом шоколадного пирога? А что изображено на картинке?
Располагайтесь поудобнее и насладитесь искусством программирования: https://youtu.be/gdSlcxxYAA8

Эээм… уже неловко об этом писать, конечно, но требуется хорошее знание английского.
День триста сорок первый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
17. Комментируйте только то, что код не может выразить
Разница между теорией и практикой гораздо очевиднее на практике, чем в теории – это как нельзя лучше относится к комментариям. Теоретически идея комментирования кода звучит убедительно: предложить читателю подробности, объяснение того, что происходит в коде. Что может быть полезнее, чем полезная добавка? Однако на практике комментарии часто становятся проблемой. Как и в любой другой форме письма, нужно уметь писать хорошие комментарии. В основном это умение заключается в том, чтобы знать, когда их не писать.

Когда код некорректен, компиляторы, интерпретаторы и другие инструменты обязательно найдут ошибку. Если код каким-либо образом некорректен в функциональном отношении, обзоры кода, статический анализ, тесты и повседневное использование в производственной среде устранят большинство ошибок. Но что насчет комментариев? В книге “The Elements of Programming Style” Керниган и Плаугер отмечают, что «комментарий имеет нулевую (или отрицательную) ценность, если он неправильный». И всё же такие комментарии часто засоряют код и остаются там даже дольше, чем ошибки. Они являются постоянной причиной отвлечения и источником дезинформации - маленьким, но препятствием в работе программиста.

Что можно сказать о комментариях, которые не являются технически неправильными, но не добавляют никакой ценности к коду? Такие комментарии являются шумом. Комментарии, которые повторяют код, не добавляют ничего полезного для читателя: сообщение о чём-либо один раз в коде и ещё раз на естественном языке не делает код более правильным или более красивым. Закомментированный код не выполняется, поэтому он не несёт никакой пользы ни для читателя, ни для среды исполнения. Такие комментарии также очень быстро устаревают. Комментарии, связанные с версией программы и закомментированный код пытаются рассказать о версиях и истории программы. На эти вопросы уже отвечают (гораздо более эффективно) инструменты контроля версий.

Большое количество бесполезных и неправильных комментариев в коде побуждает программистов игнорировать вообще все комментарии, либо пропуская их, либо принимая активные меры, чтобы их скрыть. Программисты находчивы и будут обходить всё, что воспринимается ими как помеха: скрывать комментарии, изменять цветовую схему так, чтобы комментарии и фон были одного цвета, создавать скрипты для фильтрации комментариев и т.п. Чтобы уберечь код от таких неправильных применений изобретательности программиста и уменьшить риск игнорирования комментариев, имеющих подлинную ценность, комментарии должны рассматриваться как код. Каждый комментарий должен приносить пользу читателю, иначе это мусор, который следует удалить или переписать.

Что же квалифицируется как ценность? Комментарии должны сказать что-то, что код не может выразить. Комментарий, объясняющий то, о чём уже должен говорить блок кода, свидетельствует о том, что нужно изменить структуру кода или соглашения о кодировании, чтобы код говорил сам за себя. Вместо того, чтобы объяснять плохие имена методов или классов, переименуйте их. Вместо того, чтобы комментировать отдельные блоки в длинной функции, разбейте функцию на меньшие по размеру, чьи имена отражают намерения этих блоков. Попробуйте выразить как можно больше через код. Каждое место, которое нельзя выразить в коде так же хорошо, как естественным языком, становится кандидатом на добавление полезного комментария. Комментируйте лишь то, что код не может сообщить в принципе, а не объясняйте то, что он не сообщает сейчас.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Kelvin Henney
День триста сорок второй.
Сегодня расскажу о паре интересных ресурсов для обучения.
Первый (вполне прозаично) – Microsoft Learn. Ресурс относительно новый, насколько я понимаю, пришёл на смену Microsoft Virtual Academy. Пока, как мне показалось на первый взгляд, там не так много информации, однако всё равно можно найти много интересного. Ресурс построен по принципу курсов: можно отслеживать прогресс (прочитанные статьи отмечаются). Кроме того, даются очки репутации и уровень. И хотя очки добавляются просто по мере открытия страниц курсов, без проверки ваших знаний, но возможно даже это послужит дополнительным стимулом.
Пока всё только на английском, но надеюсь, что в Майкрософт займутся локализацией рано или поздно.

Карта знаний .NET Web программиста
Авторы описывают ресурс как «Детализированную карту знаний для .NET Web программистов от Trainee до Senior.». И это действительно гигантский список, охватывающий язык C#, платформу .Net, принципы проектирования и написания кода, модульное тестирование, ASP.NET, микросервисы, фронтенд разработку, базы данных, облачные сервисы (похвастаюсь, добавили после моего комментария 😊), системы контроля версий и многое другое со ссылками либо на статьи или курсы в сети, либо на книги (с точностью до главы), где можно найти эту информацию.
В списке звёздочками обозначены темы, которые "должен знать" программист определённого уровня (интерн, джун, мидл или сеньор). От себя хочу заметить, что не стоит воспринимать этот список как обязательный (маст-ноу). Там такой объём информации, что, на мой взгляд, одному фулл-стек специалисту охватить всё это просто нереально. Но это нисколько не снижает ценность ресурса. Использовать его как справочник очень полезно. Добавил себе в закладки.
Список приведён как на русском, так и на английском, но сразу предупреждаю, что далеко не все ссылки ведут на русскоязычные источники (такова се ля ви).
ПС: За ссылку на второй ресурс большое спасибо участнику нашего чата @fjod10199.
День триста сорок третий. #ЗаметкиНаПолях
Отложенная (ленивая) инициализация
Ленивая инициализация объекта означает, что его создание откладывается до его первого использования. Она в основном используется для повышения производительности, предотвращения ненужных вычислений и снижения потребления памяти программой. Наиболее распространённые сценарии:
- Ресурсоёмкий объект, который программа может и не использовать. Например, объект Customer со свойством Orders (массив заказов), для инициализации которого требуется запрос из базы данных. Если пользователю не нужны заказы, то нет причин тратить память или время на создание массива Orders. Используя Lazy<Orders>, чтобы объявить отложенную инициализацию объекта Orders, можно избежать расхода системных ресурсов на неиспользуемый объект.
- Ресурсоёмкий объект, создание которого можно отложить. Например, программа загружает несколько экземпляров объекта при запуске, но только некоторые из них требуются немедленно. Можно ускорить запуск программы, отложив инициализацию ненужных объектов до тех пор, пока они не понадобятся.

Базовое использование
Lazy<Orders> orders = new Lazy<Orders>();
...
var listOfOrders = orders.Value; //использование
В этом случае объект параметра типа создаётся при первом использовании (обращении к свойству Value) с помощью Activator.CreateInstance, а сам тип Orders должен иметь конструктор без параметров. Кроме того, ленивому конструктору можно передать параметры:
- bool isThreadSafe – по умолчанию true, то есть создаётся потокобезопасный объект. Первый поток инициализирует объект, далее все потоки используют одни и те же данные. Если предполагается использование в единственном потоке, значение false позволяет получить небольшой выигрыш в быстродействии.
- Func<Orders> valueFactory - лямбда-выражение, позволяющее выполнить любые действия, необходимые для инициализации объекта (см. пример ниже).
- Orders value – предварительно инициализированное значение.

После создания объекта Lazy экземпляр Orders не создается до первого доступа к свойству Value переменной orders. Объект Lazy<T> всегда возвращает тот же объект или значение, которые использовались для его инициализации. Следовательно, свойство Value доступно только для чтения.
Чтобы реализовать открытое свойство с помощью отложенной инициализации, определите вспомогательное поле свойства как Lazy<T> и верните свойство Value из аксессора (get) свойства:
class Customer {
private Lazy<Orders> orders;
public string CustomerID {get; private set;}
public Customer(string id) {
CustomerID = id;
orders = new Lazy<Orders>(() =>
{
// дополнительная логика инициализации
return new Orders(this.CustomerID);
});
}
public Orders MyOrders {
get {
return orders.Value;
}
}
}

Поскольку свойство Value доступно только для чтения, у свойства Orders нет мутатора (set). Изменить значение ленивого объекта нельзя, можно только инициализировать новый объект. Если требуется изменяемое ленивое свойство, мутатор должен:
- создать новое лямбда-выражение, возвращающее переданное мутатору значение,
- передать выражение в конструктор нового объекта Lazy<T>,
- присвоить новый объект вспомогательному полю.
Тогда последующее обращение к аксессору (get) свойства приведёт к новой инициализации ленивого объекта и вернёт новое значение.

Источник: https://docs.microsoft.com/en-us/dotnet/framework/performance/lazy-initialization
День триста сорок четвёртый. #DesignPatterns
Паттерны проектирования
7. Паттерн «Команда» (Command).
Паттерн «Команда» позволяет спрятать действие в объекте и отвязать источник этого действия от места его исполнения.
Назначение: инкапсулирует запрос как объект, позволяя тем самым задавать параметры клиентов для обработки соответствующих запросов, ставить запросы в очередь или протоколировать их, а также поддерживать отмену операций.

Варианты использования:
- Система управляется событиями. При появлении такого события (запроса) необходимо выполнить определённую последовательность действий. Например, в пользовательском интерфейсе пункт меню не должен знать, что происходит при его активизации пользователем, он должен знать лишь о некотором действии, которое нужно выполнить при нажатии кнопки. Кроме того, то же действие можно выполнять при нажатии комбинации клавиш.
- Необходимо параметризировать объекты выполняемым действием, ставить запросы в очередь, хранить историю запросов или поддерживать операции отмены (undo) и повтора (redo) действий.

Классическая диаграмма приведена на рисунке ниже:
- Client – создаёт объекты конкретных команд, передавая в них все необходимые параметры, среди которых могут быть и ссылки на объекты получателей.
- Invoker – инициатор команды. Хранит ссылку на объект команды и обращается к нему, когда нужно выполнить какое-то действие.
- Receiver - получатель команды. Определяет действия, которые должны выполняться в результате запроса.
- Command - интерфейс, представляющий команду. Обычно определяет метод Execute() для выполнения действия, который может принимать необязательный контекст исполнения. Также нередко включает метод Undo(), реализация которого должна заключаться в отмене действия команды.
- ConcreteCommand - конкретная реализация команды, реализует метод Execute(), в котором вызывается метод Action() получателя. Также команда может хранить состояние (параметры, с которыми должен быть вызван метод объекта-получателя).

Аналогией паттерна «Команда» из жизни может быть ресторан. Клиент делает заказ (команду) официанту (инициатору), который передаёт её повару (получателю). Повар не знает, кто конкретно послал ему заказ. Но это ему безразлично, так как вся необходимая информация есть в листе заказа.

Существует две основные реализации паттерна:
- Делегирующая команда, представленная выше, не выполняет операцию самостоятельно, но знает, кто это может сделать, поэтому вызывает метод получателя, содержащий бизнес-логику.
- Самостоятельная команда содержит бизнес-логику внутри самого объекта команды.

Помимо объектно-ориентированной версии, в языке C# очень распространено использование функциональной версии паттерна «Команда» на основе делегатов. С помощью анонимных методов легко получить делегат, который будет захватывать внешний контекст и выполнять требуемое действие.

Источники:
- Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 7.
-
https://refactoring.guru/ru/design-patterns/command
-
https://metanit.com/sharp/patterns/3.3.php
👍1
День триста сорок пятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
18. Непрерывное обучение
Мы живём в интересное время. Разработка ПО доступна практически из любой точки мира, поэтому для вас не должно быть секретом, что есть много людей, способных выполнять вашу работу. Вы должны продолжать учиться, чтобы оставаться конкурентоспособным. В противном случае вы станете динозавром, застрявшего на одной работе, пока однажды в вас не отпадёт необходимость, либо ваша работа не станет выполняться более дешёвым способом.
Что же делать? Некоторые работодатели достаточно щедры, чтобы обеспечить обучение сотрудников, другие могут не иметь возможности делать это. Чтобы не рисковать, вы должны взять ответственность за своё образование на себя.

Вот список способов чему-нибудь научиться. Многие из них можно найти в Интернете бесплатно:
1. Читайте книги, журналы, блоги, телеграм-каналы 😉 и веб-сайты. Если вы хотите углубиться в тему, подпишитесь на рассылку новостей.
2. Если вы действительно хотите погрузиться в технологию, попробуйте написать немного кода.
3. Всегда старайтесь работать с наставником, потому что быть лучшим в команде может помешать вашему развитию. Хотя научиться чему-то можно у кого угодно, вы можете узнать намного больше от кого-то, кто умнее или опытнее, чем вы. Если вы не можете найти наставника в своей команде, подумайте над тем, чтобы двигаться дальше.
4. Используйте виртуальных наставников. Найдите в сети авторов и разработчиков, которые вам действительно нравятся, и изучите всё, что они пишут. Подпишитесь на их блоги.
5. Познакомьтесь с фреймворками и библиотеками, которые вы используете. Зная, как что-то работает, вы узнаете, как использовать это лучше. Если эти инструменты с открытым исходным кодом, вам действительно повезло. Используйте отладчик, чтобы пройтись по коду и увидеть, что происходит «под капотом». Вы увидите код, написанный и проверенный действительно умными людьми.
6. Всякий раз, когда вы делаете ошибку, исправляете ошибку или сталкиваетесь с проблемой, постарайтесь по-настоящему разобраться, что произошло. Вполне вероятно, что кто-то уже сталкивался с такой же проблемой и опубликовал решение в сети.
7. Хороший способ чему-то научиться - учить или говорить об этом. Когда люди будут слушать вас и задавать вопросы, у вас будет высокая мотивация к обучению. Попробуйте «обучение за ланчем» на работе, в группе соцсети или на местной конференции.
8. Присоединитесь или создайте группу (сообщество) для языка, технологии или дисциплины, которые вас интересуют.
9. Посещайте конференции. Если нет возможности посетить, многие конференции размещают выступления в сети бесплатно.
10. Далеко ездите? Слушайте подкасты в дороге.
11. Вы когда-нибудь запускали инструмент статического анализа кода или просматривали предупреждения в вашей IDE? Разберитесь, что они сообщают и почему.
12. Следуйте советам «Программистов-прагматиков» и изучайте новый язык каждый год. По крайней мере, изучите новую технологию или инструмент. Расширение кругозора даст вам новые идеи, которые вы можете использовать в своей текущей работе.
13. Не всё, что вы изучаете, должно быть о технологиях. Изучите домен, в котором вы работаете, чтобы лучше понять требования и помочь решить бизнес-проблему. Изучение того, как повысить свою производительность – еще одна очень полезная вещь, которую не стоит игнорировать.

Возвращайтесь в школу. Было бы неплохо иметь возможность, как у Нео в Матрице, просто загружать необходимую нам информацию в мозг. Но мы этого не умеем, поэтому нам требуется время. Не нужно тратить на обучение всё свободное время, но стоит делать это регулярно. Немного времени даже раз в неделю всё равно лучше, чем ничего. Но не забывайте, что есть и жизнь помимо работы.

Технология быстро меняется. Не отставайте.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Clint Shank
День триста сорок шестой. #Оффтоп #ЗадачиНаСобеседовании
Встречаем первые «рабочие» выходные года новой задачкой. Эту задачу предлагали на собеседовании в Amazon.
Допустим, у нас есть лестница с N ступеньками. Вы начинаете снизу и можете шагнуть на 1 ступеньку вверх, либо на 2. Нужно написать функцию, которая, получая N, будет выдавать количество вариантов, которыми вы можете пройти снизу вверх.

Например, на рисунке N=3. Мы можем сделать 3 шага по 1 ступеньке (красная линия), шаг на 1 ступеньку, потом на 2 (зелёная), и шаг на 2, потом на 1 (синяя). Понятно, что мы не можем делать шаг на 2 ступеньки со второй, поскольку впереди только одна. Таким образом, у нас всего 3 варианта.

Усложнённый вариант задания: дан набор шагов, которые вы можете сделать. Например, X = {1, 3, 5} – то есть вы можете сделать шаг на 1, 3 или 5 ступенек.

Как обычно, приглашаю всех предлагать варианты решения в чате.
День триста сорок седьмой.
Microsoft 14 января 2020 проводит конференцию ".NET Conf: Focus", темой которой будет ASP.NET Blazor. Для тех, кто ещё не в курсе, Blazor позволяет создавать клиентские приложения на C#.
Это однодневное событие с бесплатным онлайн стримом, в котором выступят спикеры из комьюнити разработчиков и команды, работающей над проектом Blazor. Расписание докладов можно посмотреть тут. Начало в 19:00 мск (16:00 по Гринвичу).
Обещают, что запись будет доступна и после ивента, но прелесть просмотра онлайн в том, что можно задавать вопросы в твиттере с хэштегом #dotNETConf

Я, похоже, знаю, что буду делать в ночь со вторника на среду)))
День триста сорок восьмой. #ЗадачиНаСобеседовании
Ответ на задачу:
Допустим, у нас есть лестница с N ступеньками. Вы начинаете снизу и можете шагнуть на 1 ступеньку вверх, либо на 2. Нужно написать функцию, которая, получая N, будет выдавать количество вариантов, которыми вы можете пройти снизу вверх.

Начнём с нескольких примеров. Перечислим в зависимости от N все возможные пути по номерам ступенек, начиная с 0 (пола):
N = 0: [0]
N = 1: [0, 1]
N = 2: [0, 1, 2], [0, 2]
N = 3: [0, 1, 3],
[0, 1, 2, 3], [0, 2, 3]
N = 4: [0, 1, 2, 4], [0, 2, 4],
[0, 1, 3, 4], [0, 1, 2, 3, 4], [0, 2, 3, 4]
Я специально расположил пути таким образом, чтобы была видна закономерность.
В случае с 0 ступенек у нас 1 путь (мы уже на ней стоим). Т.е. f(0) = 1.
Как добраться до ступеньки 1? Один путь: шагнуть на 1 ступеньку. Т.е. f(1) = 1.
Как добраться до ступеньки 2? Либо добраться до ступеньки 1 и сделать шаг на 1 ступеньку ([0, 1, 2]), либо со ступеньки 0 (с пола) сделать шаг на 2 ступеньки ([0, 2]). Т.е. f(2) = f(1) + f(0) = 2.
Как добраться до ступеньки 3? Либо добраться до ступеньки 2 (двумя способами) и сделать шаг на 1, либо добраться до ступеньки 1 и сделать шаг на 2 ([0, 1, 3]). Т.е. f(3) = f(2) + f(1) = 3.
Со ступенькой 4 аналогично: либо тремя способами добраться до ступеньки 3 и шагнуть на 1, либо двумя способами добраться до ступеньки 2 и шагнуть на 2. Т.е. f(4) = f(3) + f(2) = 5.

Думаю, закономерность понятна
f(n) = f(n - 1) + f(n - 2).
Это числа Фибоначчи.

Аналогичным образом можно доказать, что, если нам дан набор доступных шагов, например, X = {1, 3, 5}, то количество путей будет считаться по формуле
f(n) = f(n - 1) + f(n - 3) + f(n - 5),
либо в более общем случае
f(n, X) = f(n - X[0]) + f(n – X[1]) + f(n – X[2]) + …
Мы уже рассмотрели, что в случае 0 ступенек у нас получается 1 путь. А в случае отрицательного количества ступенек путей нет совсем, т.е.:
- f(n) = 0, если n < 0;
- f(0) = 1.

Напрашивается рекурсия, однако рекурсивный подсчёт будет слишком затратным. Поэтому вместо этого составим массив значений функции от 0 до N и вернём последнее значение:
static long GetNumWays(int N, int[] X)
{
long[] cache = new long[N+1];
cache[0] = 1;

// перебираем ступеньки с 1 по N
for (int i=1; i<=N; i++)
{
//перебираем доступные шаги x из набора X
//и считаем сумму значений f(i-x)
long sum = 0;
foreach (var x in X)
if ((i - x) >= 0)
sum += cache[i - x];
cache[i] += sum;
}

return cache[n];
}
Таким образом алгоритм занимает O(N*|X|) времени и O(N) памяти.

Источники:
-
https://youtu.be/5o-kdjv7FD0
-
https://www.dailycodingproblem.com/
День триста сорок девятый.
Продолжаю готовиться к сертификации по ASP.NET MVC. Поскольку в экзамене вопросы будут как по Core, так и по Framework, для начала решил освежить знания по Framework. До этого читал «ASP.NET MVC 3 Framework» Фримена. Сейчас удалось заполучить вот такую книжку «Professional ASP.NET MVC 5» Джона Гэллоуэя.

Книги очень похожи по структуре и содержанию. Но есть подозрение, что Фримен всё-таки, производный от Гэллоуэя. Например, у Гэллоуэя описан интернет-магазин MusicStore – открыто распространяемый Microsoft код с руководством по его созданию (от самого же Гэллоуэя).
*Руководство описывает аж версию MVC 3, а распространяемый в GitHub код уже на Core, код на Framework убран в архив и больше не поддерживается.
У Фримена это SportsStore. Однако во многих нюансах у Гэллоуэя описание гораздо более подробное, плюс сдобренное неплохим юмором. Хотя, возможно, многое потерялось в переводе, Фримена я в оригинале не видел. Так что тем, у кого проблем с английским нет, советую читать Гэллоуэя.
День триста пятидесятый. #ЗаметкиНаПолях
ASP.NET MVC 5.
Контроллеры
Контроллеры в шаблоне MVC отвечают за реакцию на действия пользователя, зачастую внося изменения в модель, используя данные, введённые пользователем. Таким образом, контроллеры в шаблоне MVC связаны с потоком приложения, отвечая за работу с входящими данными и предоставление результирующих данных, в соответствующем представлении.

Изначально веб-серверы предоставляли статический HTML, хранящийся в файлах на диске. Динамические веб страницы позволили генерировать HTML на лету с помощью скриптов. Однако соответствие между URL и физическим файлом на диске более-менее сохранялось. В MVC реализован другой подход.
Вместо того, чтобы иметь прямую связь между URL и файлом на жестком диске веб-сервера, существует связь между URL и методом действия (Action), в классе контроллера. То есть работу MVC в веб-сценарии можно представить как возврат результата вызова метода, а не динамически генерируемой страницы.

По соглашению в MVC Framework классы контроллеров хранятся в папке Controllers и имеют суффикс Controller в имени класса, например HomeController, AccountController.

В принципе методу-действия можно указать любой тип возврата, но чаще всего в качестве типа результата используется один из типов, унаследованных от абстрактного класса ActionResult:
1. ContentResult – текст;
2. EmptyResult – отсутствие результата;
3. FileResult – загружаемый файл (базовый класс);
3.1 FileContentResult – двоичные данные;
3.2 FilePathResult – выдаёт файл по указанному пути;
3.3 FileStreamResult – поток данных;
4. JavaScriptResult - код JavaScript;
5. JsonResult – JSON данные, обычно возвращаемые как результат AJAX запроса;
6. ViewResult – разметка HTML;
6.1 PartialviewResult – частичная разметка HTML, используется для включения блока (например, меню) в другую страницу;
7. HttpStatusCodeResult – выдаёт указанный код ответа HTTP;
7.1 HttpNotFound – выдаёт код ответа 404 (не найдено);
7.2 HttpUnauthorizedResult - выдаёт код ответа 401 (не авторизовано);
7.3 RedirectResult – перенаправление на другой URL (возвращает код постоянного 302 или временного 301 перенаправления в зависимости от параметра Permanent);
7.4 RedirectToRouteResult – перенаправление на заданные метод-действие и контроллер.

Бывают случаи, когда у вас есть очень простой метод действия, предназначенный только для возврата примитивного типа данных. В этом случае полезно, чтобы сигнатура метода действия отражала тип возврата, например:
public double Distance(int x1, int y1, int x2, int y2) { … }
Обратите внимание, что возвращаемый тип double, а не производный от ActionResult. Это вполне приемлемо. Преимущества такого подхода в том, что он проясняет ваши намерения, а сам метод проще для модульного тестирования.
Когда ASP.NET MVC вызывает этот метод и видит, что возвращаемый тип не является производным от ActionResult, он автоматически создаёт ContentResult, содержащий результат метода действия. Следует иметь в виду, что ContentResult требует строковое значение, поэтому результат должен быть сначала преобразован в строку. Для этого ASP.NET MVC вызывает метод ToString() для результата, используя InvariantCulture. Если вам нужно отформатировать результат в соответствии с определенной культурой, вы должны явно возвращать ContentResult самостоятельно.

Источник: Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Главы 2, 16.
День триста пятьдесят первый. #DesignPatterns
Паттерны проектирования
8. Паттерн «Состояние» (State).
Некоторые объекты предметной области могут выполнять разные наборы операций в зависимости от состояния. Паттерн Состояние моделирует конечный автомат с набором допустимых переходов.

Назначение: позволяет объекту варьировать свое поведение в зависимости от внутреннего состояния. Извне создаётся впечатление, что изменился класс объекта. Основная идея в том, что программа может находиться в одном из нескольких состояний, которые всё время сменяют друг друга. Набор этих состояний, а также переходов между ними, предопределён и конечен. Находясь в разных состояниях, программа может по-разному реагировать на одни и те же события, которые происходят с ней.

Причины использования:
Допустим, объект Документ может принимать три состояния: Черновик, Модерация или Опубликован. Для каждого состояния документа метод обработки документа выполняет разные функции. В статусе Черновик он переведёт документ на модерацию (возможно, уведомит модератора о новом задании), в статусе Модерация метод опубликует документ, а в статусе Опубликован не будет делать ничего.
Основная проблема в таком решении проявится в том случае, если в Документ добавить ещё десяток состояний. Каждый метод обработки будет состоять из увесистого условного оператора, перебирающего доступные состояния. Такой код крайне сложно поддерживать. Малейшее изменение логики переходов заставит вас перепроверять работу всех методов, которые содержат условные операторы машины состояний.

Классическая диаграмма приведена на рисунке ниже:
- Context – делегирует операции по переходу между своими состояниями объектам State, вызывая методы Handle();
- State - определяет интерфейс состояния;
- ConcreteState1 и ConcreteState2 - конкретные реализации состояний.

Паттерн Состояние предлагает создать отдельные классы для каждого состояния, в котором может пребывать объект, а затем вынести туда поведение, соответствующее конкретному состоянию. Вместо того, чтобы хранить код всех состояний, объект контекста будет содержать ссылку на один из объектов-состояний и делегировать ему работу, зависящую от состояния. Благодаря тому, что объекты состояний имеют общий интерфейс, контекст сможет делегировать работу состоянию, не привязываясь к его классу. Поведение контекста можно будет изменить в любой момент, подключив к нему другой объект-состояние.
Важное отличие от паттерна Стратегия в том, что и контекст, и сами конкретные состояния могут знать друг о друге и инициировать переходы от одного состояния к другому.

Паттерн «Состояние» в полноценном виде довольно редко применяется на практике. Его применимость определяется сложностью конечного автомата. Чаще всего количество состояний объекта не столь велико, а логика переходов между состояниями не слишком сложна, поэтому её включают в сам объект контекста.

Источники:
- Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 7.
-
https://refactoring.guru/ru/design-patterns/state
День триста пятьдесят второй. #юмор
День триста пятьдесят третий. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
19. Удобство как Качество
О важности и проблемах разработки хороших API уже было сказано очень много. Сделать первого раза очень сложно, а изменить потом ещё сложнее. В этом создание API похоже на воспитание детей. Большинство опытных программистов пришли к пониманию, что хороший API образует согласованную абстракцию, демонстрирует логичность и симметрию и формирует словарь для выразительного языка общения с ним. Увы, одно лишь знание руководящих принципов влечёт за собой надлежащее поведение. «Есть сладости вредно», ага)))

Вместо того, чтобы ходить вокруг да около, я хочу обратить внимание на конкретную «стратегию» разработки API, с которой я сталкиваюсь снова и снова: «Главный аргумент – удобство». Обычно дизайнерские решения принимаются на основании одной из следующих «гениальных» идей:
- «Я не хочу, чтобы другие классы делали два разных вызова, чтобы сделать одно и то же
- «Зачем мне делать другой метод, если он почти такой же, как этот метод? Я просто добавлю обычный параметр-переключатель.»
- «Смотрите, это очень просто: если второй параметр оканчивается на .txt, метод автоматически предполагает, что первый параметр является именем файла, поэтому два метода действительно не нужны.»

Несмотря на то, что такие аргументы могут быть хорошо спланированы и логично обоснованы, они снижают читабельность кода, использующего API. Вызов метода вроде parser.processNodes(text, false); не несёт никакого смысла, если не знать о реализации или, по крайней мере, не ознакомиться с документацией. Этот метод, скорее всего, был разработан для удобства писавшего, а не для удобства использующего. На самом деле, фразу: «Я не хочу, чтобы вызывающий код делал два разных вызова» следует читать как «Мне было лень реализовывать два отдельных метода». Нет ничего принципиально плохого в удобстве, если оно призвано стать противоядием от монотонности, корявости или неловкости. Однако, если подумать, противоядия от этих симптомов - это эффективность, последовательность и элегантность, а вовсе не удобство. Предполагается, что API-интерфейсы скрывают сложность, поэтому стоит ожидать, что разработка хорошего API потребует некоторых усилий. Один большой метод, безусловно, может быть более удобным для написания, чем хорошо продуманный набор операций, но будет ли его проще использовать?

В таком случае метафора API как человеческого языка может привести нас к лучшим проектным решениям. API должен обеспечивать выразительный язык, который даёт следующему уровню абстракции словарный запас, чтобы задавать необходимые вопросы и получать на них ответы. Это не означает, что он должен предоставлять ровно один метод или глагол для каждого вопроса. Разнообразный словарный запас позволяет нам выразить тонкости в значении. Например, мы предпочтём использовать run вместо walk(true), даже если эти действия можно рассматривать как по сути одно и то же, просто выполняемое на разных скоростях. Последовательный и хорошо продуманный словарь API обеспечивает выразительный и простой для понимания код на следующем уровне. Что ещё более важно, составляемый словарь позволяет другим программистам использовать API способами, о которых вы, возможно, не задумывались, а это большое удобство для пользователей API! В следующий раз, когда у вас возникнет соблазн объединить несколько вещей в один метод API, помните, что в человеческом языке нет одного слова для ПриберисьВКомнатеСидиТихоСделайУроки, хотя это было бы очень удобно для такой часто запрашиваемой операции.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Gregor Hohpe
👍1
День триста пятьдесят четвёртый. #ЗаметкиНаПолях
ASP.NET MVC 5.
Представления. Начало
Большинство методов действия контроллера должны отображать динамическую информацию в формате HTML. После того, как контроллер выполнил соответствующую логику для запрошенного URL, он делегирует отображение результата представлению.
В отличие от файловых веб-платформ сами представления непосредственно недоступны. Нельзя задать в браузере путь к представлению и сделать так, чтобы оно отобразилось. Вместо этого представление всегда отображается контроллером, который передаёт представлению данные для отображения.

В ASP.NET представления — это файлы .cshtml, в которых используется C# в разметке Razor. По соглашению представления размещаются в папке Views, сгруппированы по папкам соответственно контроллеру, к которому они относятся, и называются по имени метода действия. Например, представления, относящиеся к контроллеру HomeController, размещаются в папке Views/Home. Представления для метода действия Index() называется Index.cshtml, для метода About()About.cshtml. Общие представления, которые используются несколькими контроллерами, а также макеты страниц размещаются в папке Views/Shared.

Представления, как правило, возвращаются из методов действия в виде объекта ViewResult. Можно создавать и возвращать объект напрямую, однако обычно используется вспомогательный метод View:
public class HomeController : Controller {
public ActionResult About() {
return View();
}
}
Соглашение об именовании помогает системе найти нужное представление в файле Views/Home/About.cshtml. Методу View можно передать строковый параметр с указанием пути к нужному представлению, а также объект модели, данные которой нужно отобразить.

ViewBag и ViewData
Помимо модели, для передачи небольших объёмов данных в представление из метода действия используется специальный словарь ViewData:
ViewData["CurrentTime"] = DateTime.Now;
С введением динамических типов в C#4 стало возможно использовать динамическую обёртку ViewBag, в котором можно задавать значения как свойства:
ViewBag.CurrentTime = DateTime.Now;
Этот код эквивалентен предыдущему, и технически ни один вариант не имеет преимущества перед другим. Однако в некоторых случаях ViewData может иметь преимущество, например, следующий код не выполнится из-за динамической природы ViewBag: @Html.TextBox("name", ViewBag.Name)
Здесь нужно либо использовать ViewData["Name"], либо приводить значение к нужному типу: (string)ViewBag.Name.

Строго типизированные представления
Если в представление нужно передать модель, то создаётся строго типизированное представление. Оно отличается от обычного указанием в заголовке файла представления типа модели через декларацию @model. Следующая декларация объявляет строго типизированное представления для списка товаров:
@model IEnumerable<Product>
Это позволяет компилятору проверять типы данных модели, в отличие от передачи данных через ViewBag/ViewData, а разработчикам использовать преимущества IntelliSense в представлении.

Модели представления
Иногда в представлении нужно отобразить дополнительную информацию, помимо данных из модели домена. Например, в представлении корзины покупок помимо списка товаров нужно отобразить общую сумму покупки и сообщение (допустим, информацию о доставке).
Есть два пути добиться этого: использовать ViewBag/ViewData, либо создать специализированный класс модели представления, содержащий как список продуктов, так и необходимые дополнительные свойства:
public class ShoppingCartViewModel {
public IEnumerable<Product> Products { get; set; }
public decimal CartTotal { get; set; }
public string Message { get; set; }
}
Как правило, лучше использовать второй вариант. Можно рассматривать модель представления как модель, существующую только для передачи информации в представление.

Источник: Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 3.
День триста пятьдесят пятый. #ЗаметкиНаПолях
ASP.NET MVC 5.
Представления. Razor
Механизм представления Razor является механизмом создания представлений по умолчанию. Он предоставляет оптимизированный синтаксис, который сводит к минимуму количество необходимого кода. Cовет по использованию Razor: помните, что он был разработан, чтобы быть простым и интуитивно понятным. Чаще всего вам не нужно беспокоиться о синтаксисе: просто пишите HTML и используйте знак @, когда нужно вставить какой-то код.

Выражения
В большинстве случаев не нужно использовать закрывающий тэг:
<p>Всего @items.Length элементов</p>
<p>Всего элементов @items.Length</p>
<p>Всего элементов @items.Length.</p>
Razor понимает, что точка, за которой следует набор букв, означает обращение к свойству объекта, а пробел или знак < не являются валидными символами. Однако, если нужно вывести переменную, а за ней точку и текст, можно использовать круглые скобки: <p>@(siteName).Com</p>
Также скобки можно использовать для вычисления выражений: <p>@(1+2)</p>

E-mail
Razor достаточно умён, чтобы понимать стандартные форматы e-mail адресов: <span>[email protected]</span>
В более сложных случаях, а также для указания аккаунтов Twitter или Telegram, можно использовать @@: <p>@@NetDeveloperDiary</p>

Блоки кода
Это части представления, которые выполняются компилятором. Их заключают в фигурные скобки:
@{
int x = 123;
string y = "hello";
}

Совмещение кода и разметки
@foreach (var item in items) {
<span>@item.Name.</span>
}

Совмещение кода и текста
Razor ищет начало HTML тэга для определения окончания кода и начала разметки, однако если нужно вывести текст сразу после блока кода, можно использовать либо специальный тег <text>, который не выводится на странице, либо конструкцию @: (она позволяет вывести только одну строку):
@if (showMessage) {
<text>Это простой текст</text>
}
или
@if (showMessage) { 
@:Это простой текст
}

Серверный комментарий
Для комментариев на стороне сервера используется конструкция @* *@ :
@* Это многострочный комментарий.
@if (showMessage) {
<h1>@ViewBag.Message</h1>
}
Всё это закомментировано.
*@

Вызов обобщённого метода
Поскольку угловые скобки используются как для HTML тегов, так и в обобщённых методах, вызвать обобщённый метод можно, заключив выражение в круглые скобки:
@(Html.SomeMethod<AType>())

Кодирование HTML
По умолчанию весь код HTML, выводящийся через Razor кодируется для предотвращения XSS атак (< становится &lt;, > - &gt; и т.п.). Поэтому, если вы действительно хотите вывести HTML код, как есть, используйте вспомогательный метод HTML.Raw:
<span>@Html.Raw(model.Message)</span>
Внимание: Javascript не кодируется по умолчанию, как HTML. Поэтому любые данные, которые будут использоваться внутри кода Javascript, нужно кодировать с помощью @Ajax.JavaScriptStringEncode:
<script type="text/javascript">
$(“.title”).text('Hello @Ajax.JavaScriptStringEncode(ViewBag.Username)');
</script>

Источник: Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 3.
День триста пятьдесят шестой. #DesignPatterns
Паттерны проектирования
9. Паттерн «Цепочка обязанностей» (Chain of Responsibility).
Как и многие другие поведенческие паттерны, Цепочка обязанностей базируется на том, чтобы превратить отдельные поведения в объекты-обработчики. Кроме того, паттерн подразумевает связывание объектов-обработчиков в одну цепь.

Назначение: позволяет избежать привязки отправителя запроса к его получателю, давая шанс обработать запрос нескольким объектам. Связывает объекты-получатели в цепочку и передает запрос вдоль этой цепочки, пока его не обработают. Отправитель запроса знает только о первом обработчике, но не знает, кто в итоге обработает запрос.

Причины использования:
Необходимость обработки пользовательского запроса несколькими независимыми друг от друга методами: аутентификация и авторизация, валидация данных, предотвращение массовых запросов ботами, собственно выполнение запроса и т.п. Каждая новая проверка раздувает код обработки, а для обработки схожих запросов код приходится дублировать.
Решением будет вынести каждую проверку в отдельный класс обработчика, реализующий общий интерфейс, и выстроить эти классы в цепочку обработки.

Классическая диаграмма приведена на рисунке ниже:
- Client - отправляет запрос объекту Handler.
- Handler - определяет интерфейс для обработки запроса. Также может определять ссылку на следующий обработчик запроса.
- ConcreteHandler1 и ConcreteHandler2 - конкретные обработчики, которые реализуют функционал обработки запроса.

Логику обработки можно реализовать по-разному. В примере выше каждый обработчик передавал бы запрос следующему по цепочке только в случае успешной обработки. Например, только в случае успешной авторизации пользователя мы проверяем валидность данных его запроса. В противном случае обработка запроса прекращается.
Но есть и другой подход, при котором обработчики прерывают цепь только когда они могут обработать запрос. В этом случае запрос движется по цепи, пока не найдётся обработчик, который может его обработать. Очень часто такой подход используется для передачи событий, создаваемых классами графического интерфейса в результате взаимодействия с пользователем.
Например, когда пользователь кликает по кнопке, программа выстраивает цепочку из объекта этой кнопки, всех её родительских элементов и общего окна приложения на конце. Событие клика передаётся по этой цепи до тех пор, пока не найдётся объект, способный его обработать.

Важно, чтобы все объекты цепочки имели общий интерфейс. Обычно каждому конкретному обработчику достаточно знать только то, что следующий объект в цепи имеет метод HandleRequest(). Благодаря этому связи между объектами цепочки будут более гибкими. Кроме того, можно формировать цепочки на лету из разнообразных объектов, не привязываясь к конкретным классам.

Источники:
- Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 7.
-
https://refactoring.guru/ru/design-patterns/chain-of-responsibility