О принципах 
проектирования 
Сергей Тепляков
Обо мне 
• 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_manage 
ment 
• Лучшая книга по ООП эвар! – Бертран Мейер «Объектно-ориентированное 
конструирование программных систем» 
http://sergeyteplyakov.blogspot.com/2012/03/blog-post_19.html

SOLID Principles in the real world

  • 1.
  • 2.
    Обо мне •Microsoft C# MVP 2011-2014 • MSFT Employee 2014- (VS Team) • Blogger: SergeyTeplyakov.blogspot.com • Контакты • Sergey.Teplyakov [sobak] gmail.com • @STeplyakov
  • 3.
    О чем будемговорить? • О принципах проектирования в целом • О SOLID принципах
  • 4.
    Нужны ли нампринципы проектирования?
  • 5.
  • 6.
    Поиск серебряной пули • Ответ на вопрос жизни вселенной и всего такого?
  • 7.
    Мы хотим использоватьопыт и знания повторно!
  • 8.
    Классические подходы •Low coupling • High cohesion • Design by Contract
  • 9.
    SOLID принципы •S – Single Responsibility Principle • O – Open-Closed Principle • L – Liskov Substitution Principle • I – Interface Segregation Principle • D – Dependency Inversion Principle
  • 10.
    Опасности принципов •Что «сильнее» композиция или наследование? • Остерегайтесь культа карго
  • 11.
    В чем проблемас текущим описанием? • Принципы слишком неформальны • Берут свое начало в 90-х (С++) • Не ясно, когда им следовать, а когда - нет
  • 12.
    Как следовать этимпринципам? Принципы существуют для того, чтобы помогать в устранении дурных запахов в дизайне. Это не духи, которыми надо обильно поливать всю систему. Чрезмерная приверженность принципам ведет к пороку ненужной сложности.
  • 13.
    Single Responsibility Principle • Исходное определение: У класса должна быть только одна причина для изменения • Смысл принципа SRP: Борьба со сложностью • SRP violation means low cohesion! http://bit.ly/SingleResponsibilityPrinciple
  • 14.
    Любой класс изреального мира будет нарушать SRP
  • 15.
  • 16.
    Смешивание логики иинфраструктуры • Windows сервис ходит в базу • WCF-сервис сам содержит логику обработки запросов • Azure Role содержит логику по перекладыванию данных из одного места в другое. • Смешивание логики и «сохраняемости» (persistence)
  • 17.
    Смешивание логики ипредставления
  • 18.
    Выделяйте каждому сложномуаспекту системы свой класс/модуль
  • 19.
    Я хочу иметьвозможность сосредоточиться на сложных аспектах системы по отдельности, поэтому когда мне становится сложно это делать, я начинаю разбивать классы и выделять новые.
  • 20.
    Дизайн – этоэволюционный процесс
  • 21.
    Актуальность SRP возрастаетвместе с увеличением сложности!
  • 22.
    Пример увеличения сложности:добавление предусловий
  • 23.
    Интерфейс ContextActionBase publicabstract class ContextActionBase { public abstract void Execute(); public abstract bool IsAvailable(); public abstract string Text { get; } }
  • 25.
  • 26.
    SRP – этоспособ поиска скрытых абстракций, достаточно сложных, чтобы им отвели отдельную именованную сущность и спрятали в их недрах все детали.
  • 27.
    Open-Closed Principle Классы,модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации http://bit.ly/OpenClosedPrinciple
  • 28.
  • 29.
    Объяснение Боба Мартина • Они открыты для расширения. Это означает, что поведение модуля можно расширить. Когда требования к приложению изменяются, мы добавляем в модуль новое поведение, отвечающее изменившимся требованиям. Иными словами, мы можем изменить состав функций модуля. • Они закрыты для модификации. Расширение поведения модуля не сопряжено с изменениями в исходном или двоичном коде модуля. Двоичное исполняемое представление модуля, будь то компонуемая библиотека, DLL или EXE-файл, остается неизменным (выделено мною).
  • 30.
    Нарушает ли фабрика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"); } } }
  • 31.
    Принцип единственного выбора Всякий раз, когда система программного обеспечения должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы. Бербран Мейер
  • 32.
  • 33.
    Определение от БертранаМейера • Модуль называют открытым, если он еще доступен для расширения. Например, имеется возможность расширить множество операций в нем или добавить поля к его структурам данных. • Модуль называют закрытым, если он доступен для использования другими модулями. Это означает, что модуль (его интерфейс – с точки зрения скрытия информации) уже имеет строго определенное окончательное описание. На уровне реализации закрытое состояние модуля означает, что модуль можно компилировать, сохранять в библиотеке и делать его доступным для использования другими модулями (его клиентами).
  • 34.
    Open-Closed Principle •Что такое OCP? Это фиксация интерфейса класса/модуля, и возможность изменения или подмены реализации/поведения. • Цели OCP: борьба со сложностью и ограничение изменений минимальным числом модулей. • Как мы реализуем OCP? С помощью инкапсуляции, которая позволяет изменять реализацию без изменения интерфейса и с помощью наследования, что позволяет заменить реализацию, которая не затронет существующих клиентов базового класса.
  • 35.
    Типичный пример нарушения расширяемости (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; }
  • 36.
    Расширяемость: OOP vs.FP • В ООП: легко добавлять новый тип, сложно – новую операцию • В ФП: легко добавлять новую операцию, сложно – новый тип Expression Problem!
  • 37.
  • 38.
    Иногда мы хотимвынести логику за пределы иерархии! Решение: Посетитель!
  • 39.
  • 40.
    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); }
  • 41.
    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; } }
  • 42.
    ФП подход: сопоставлениес образцом 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); }
  • 43.
    Пример использования ValidationResultvr = GetValidationResult(); bool isFixable = vr.Match( error => error.Error == MalformedContractError.VoidReturnMethodCall, warning => warning.Warning == MalformedContractWarning.NonVoidReturnMethodCall, @default => false);
  • 44.
    Как пользуюсь принципами? • Принципы – не самоцель! • Дизайн постоянно эволюционирует. Валидируйте его согласно этим принципам на каждой итерации.
  • 45.
    Цикл постов оSOLID-принципах • http://bit.ly/SingleResponsibilityPrinciple • http://bit.ly/OpenClosedPrinciple • http://bit.ly/LiskovSubstitutionPrinciple • http://bit.ly/InterfaceSegregationPrinciple • http://bit.ly/DependencyInversionPrinciple
  • 46.
    Чего почитать потеме • О культе карго в программировании 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_manage ment • Лучшая книга по ООП эвар! – Бертран Мейер «Объектно-ориентированное конструирование программных систем» http://sergeyteplyakov.blogspot.com/2012/03/blog-post_19.html

Editor's Notes

  • #7 Молодые разработчики и менеджеры стараются найти ответ на вопрос жизни
  • #20 Сказать о фабриках!