Date post: | 14-Jun-2015 |
Category: |
Software |
Upload: | epam-systems |
View: | 363 times |
Download: | 0 times |
О принципах проектирования
Сергей Тепляков
Обо мне
• Microsoft C# MVP 2011-2014
• MSFT Employee 2014- (VS Team)
• Blogger: SergeyTeplyakov.blogspot.com
• Контакты
• Sergey.Teplyakov [sobak] gmail.com
• @STeplyakov
О чем будем говорить?
• О принципах проектирования в целом
• О SOLID принципах
Нужны ли нам принципы проектирования?
Дизайн – штука сложная!
Поиск серебряной пули
• Ответ на вопрос жизни вселенной и всего такого?
Мы хотим использовать опыт и знания повторно!
Классические подходы
• Low coupling
• High cohesion
• Design by Contract
SOLID принципы
• S – Single Responsibility Principle
• O – Open-Closed Principle
• L – Liskov Substitution Principle
• I – Interface Segregation Principle
• D – Dependency Inversion Principle
Опасности принципов
• Что «сильнее» композиция или наследование?
• Остерегайтесь культа карго
В чем проблема с текущим описанием?
• Принципы слишком неформальны
• Берут свое начало в 90-х (С++)
• Не ясно, когда им следовать, а когда - нет
Как следовать этим принципам?
Принципы существуют для того, чтобы помогать в устранении дурных запахов в дизайне. Это не духи, которыми надо обильно поливать всю систему. Чрезмерная приверженность принципам ведет к пороку ненужной сложности.
Single Responsibility Principle
• Исходное определение: У класса должна быть только одна причина для изменения
• Смысл принципа SRP: Борьба со сложностью
• SRP violation means low cohesion!
http://bit.ly/SingleResponsibilityPrinciple
Любой класс из реального мира будет нарушать SRP
Типичные примеры нарушения SRP
Смешивание логики и инфраструктуры
• Windows сервис ходит в базу
• WCF-сервис сам содержит логику обработки запросов
• Azure Role содержит логику по перекладыванию данных из одного места в другое.
• Смешивание логики и «сохраняемости» (persistence)
Смешивание логики и представления
Выделяйте каждому сложному аспекту системы свой класс/модуль
Я хочу иметь возможность сосредоточиться на сложных аспектах системы по отдельности, поэтому когда мне становится сложно это делать, я начинаю разбивать классы и выделять новые.
Дизайн – это эволюционный процесс
Актуальность SRP возрастает вместе с увеличением сложности!
Пример увеличения сложности: добавление предусловий
Интерфейс ContextActionBase
public abstract class ContextActionBase{ public abstract void Execute(); public abstract bool IsAvailable(); public abstract string Text { get; }}
Эволюция дизайна
SRP – это способ поиска скрытых абстракций, достаточно сложных, чтобы им отвели отдельную именованную сущность и спрятали в их недрах все детали.
Open-Closed Principle
Классы, модули, функции и т.п.) должны быть открытыми для расширения, но
закрытыми для модификации
http://bit.ly/OpenClosedPrinciple
Как вы понимаете OCP?
Объяснение Боба Мартина
• Они открыты для расширения. Это означает, что поведение модуля можно расширить. Когда требования к приложению изменяются, мы добавляем в модуль новое поведение, отвечающее изменившимся требованиям. Иными словами, мы можем изменить состав функций модуля.
• Они закрыты для модификации. Расширение поведения модуля не сопряжено с изменениями в исходном или двоичном коде модуля. Двоичное исполняемое представление модуля, будь то компонуемая библиотека, DLL или EXE-файл, остается неизменным (выделено мною).
Нарушает ли фабрика OCP?abstract class Importer{ public abstract void ImportData();}
static class ImporterFactory{ public static Importer Create(string fileName) { var extension = Path.GetExtension(fileName); switch (extension) { case "json": return new JsonImporter(); case "xls": case "xlsx": return new XlsImporter(); default: throw new InvalidOperationException("Extension is not supported"); } }}
Принцип единственного выбора
Всякий раз, когда система программного обеспечения должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы.
Бербран Мейер
OCP != Расширяемость
Определение от Бертрана Мейера
• Модуль называют открытым, если он еще доступен для расширения. Например, имеется возможность расширить множество операций в нем или добавить поля к его структурам данных.
• Модуль называют закрытым, если он доступен для использования другими модулями. Это означает, что модуль (его интерфейс – с точки зрения скрытия информации) уже имеет строго определенное окончательное описание. На уровне реализации закрытое состояние модуля означает, что модуль можно компилировать, сохранять в библиотеке и делать его доступным для использования другими модулями (его клиентами).
Open-Closed Principle
• Что такое OCP? Это фиксация интерфейса класса/модуля, и возможность изменения или подмены реализации/поведения.
• Цели OCP: борьба со сложностью и ограничение изменений минимальным числом модулей.
• Как мы реализуем OCP? С помощью инкапсуляции, которая позволяет изменять реализацию без изменения интерфейса и с помощью наследования, что позволяет заменить реализацию, которая не затронет существующих клиентов базового класса.
Типичный пример нарушения расширяемости (OCP)
public static int Count<TSource>(this IEnumerable<TSource> source){ var collectionoft = source as ICollection<TSource>; if (collectionoft != null) return collectionoft.Count;
var collection = source as ICollection; if (collection != null) return collection.Count;
int count = 0; using (IEnumerator<TSource> e = source.GetEnumerator()) { while (e.MoveNext()) count++; }
return count;}
Расширяемость: OOP vs. FP
• В ООП: легко добавлять новый тип, сложно – новую операцию
• В ФП: легко добавлять новую операцию, сложно – новый тип
Expression Problem!
Расширяемость в ООП
Иногда мы хотим вынести логику за пределы иерархии!
Решение: Посетитель!
Пример
public interface IValidationResultVisitor{ void Visit(NoErrorValidationResult vr); void Visit(ContractErrorValidationResult vr); void Visit(ContractWarningValidationResult vr); void Visit(CustomWarningValidationResult vr);}
public abstract class ValidationResult{ private ICSharpStatement _statement;
protected ValidationResult(ICSharpStatement statement) { Contract.Requires(statement != null); _statement = statement; }
public abstract void Accept(IValidationResultVisitor visitor);}
class IsIssueFixableVisitor : IValidationResultVisitor{ public bool IsIssueFixable { get; private set; } public void Visit(NoErrorValidationResult vr) { IsIssueFixable = false; }
public void Visit(ContractErrorValidationResult vr) { IsIssueFixable = vr.Error == MalformedContractError.VoidReturnMethodCall; }
public void Visit(ContractWarningValidationResult vr) { IsIssueFixable = vr.Warning == MalformedContractWarning.NonVoidReturnMethodCall; }
public void Visit(CustomWarningValidationResult vr) { IsIssueFixable = false; }}
ФП подход: сопоставление с образцомpublic T Match<T>(
Func<CodeContractErrorValidationResult, T> errorMatch, Func<CodeContractWarningValidationResult, T> warningMatch, Func<ValidationResult, T> defaultMatch){ var errorResult = this as CodeContractErrorValidationResult; if (errorResult != null) return errorMatch(errorResult);
var warningResult = this as CodeContractWarningValidationResult; if (warningResult != null) return warningMatch(warningResult);
return defaultMatch(this);}
Пример использования
ValidationResult vr = GetValidationResult();bool isFixable = vr.Match( error => error.Error == MalformedContractError.VoidReturnMethodCall, warning => warning.Warning == MalformedContractWarning.NonVoidReturnMethodCall, @default => false);
Как пользуюсь принципами?
• Принципы – не самоцель!
• Дизайн постоянно эволюционирует. Валидируйте его согласно этим принципам на каждой итерации.
Цикл постов о SOLID-принципах
• http://bit.ly/SingleResponsibilityPrinciple
• http://bit.ly/OpenClosedPrinciple
• http://bit.ly/LiskovSubstitutionPrinciple
• http://bit.ly/InterfaceSegregationPrinciple
• http://bit.ly/DependencyInversionPrinciple
Чего почитать по теме• О культе карго в программировании
sergeyteplyakov.blogspot.com/2013/09/blog-post_24.html
• Критика книги Боба Мартина «Принципы, паттерны и методики гибкой разработки на языке C#»sergeyteplyakov.blogspot.com/2013/12/about-agile-principles-patterns-and.html
• Цикл постов об управлении зависимостямиhttp://sergeyteplyakov.blogspot.com/2013/10/articles.html#dependency_management
• Лучшая книга по ООП эвар! – Бертран Мейер «Объектно-ориентированное конструирование программных систем»http://sergeyteplyakov.blogspot.com/2012/03/blog-post_19.html