Design patterns part 1

Preview:

Citation preview

Шаблони проектування

Частина 1

Coupling and cohesion

Coupling (зв’язність)• Afferent– Скільки компонентів залежать від

модуля– Показує наскільки модуль

відповідальний

• Efferent– Від скількох компонентів залежить

модуль– Показує наскільки модуль

незалежний– Включає: наслідування, interface impl,

типи параметрів, типи змінних, exceptions,...

• Ca and Ce є метриками стабільності

Instability

I = Ce / (Ce + Ca)

• показує нестійкість до змін• 0 – стабільний модуль• 1 – нестабільний

Ми залежимо від

Залежать від нас

Cohesion (пов’язаність, зчеплення)

• Показує наскільки сильні взаємозв’язки між частинами одного модулю

• Чи всі методи класу використовують всі його поля

• Низька пов’язаність:– Клас з багатьма статичними

методами– “Несфокусований” клас

Програмне забезпечення

• Успішне– Legacy (унаслідуване, застаріле)–Maintainable (легко підтримуване)

• Неуспішне

Legacy software

• Довго експлуатується• Продовжує експлуатуватись• Задовільняє потреби замовника• Але разом з тим–Містить дефекти, не містить нових

функцій

• Важко покращується

Maintainable software

• Довго експлуатується• Продовжує експлуатуватись• Задовільняє потреби замовника• Але разом з тим–Містить дефекти, не містить нових

функцій

• Легко покращується

Бажані властивості

• Хочемо змінювати програму– Проясняються старі/з’являються нові

вимоги

• Не хочемо змінювати готові класи– Зміна – внесення нестабільності– Потрібно перетестувати– Залежності транзитивні

Low coupling + high cohesion сприяють легкому внесенню змін у

ПЗ

Більшість шаблонів проектування є рецептами для зниження coupling

та підвищення cohesion

Створення об’єктів, creational patterns

Dependencies

public class Survey { public SurveyState State { get; set; } public DateTime StartDate { get; set; } public DateTime FinishDate { get; set; }

public void Start() { State = SurveyState.InProgress; try { var email = new SmtpEmailSender(); email.Send("Survey has started!", Constants.AdminEmail); } catch (SmtpException e) { ... } }}

Survey

SmtpEmailSender

High level

module

Low level

module

Depends on

public class Survey { private readonly IEmailSender _email; public SurveyState State { get; set; } public DateTime StartDate { get; set; } public DateTime FinishDate { get; set; }

public Survey(IEmailSender email) { _email = email; }

public void Start() { State = SurveyState.InProgress; try { _email.Send("Survey has started!", Constants.AdminEmail); } catch (SmtpException e) { ... } }}

Survey

SmtpEmailSender

High level

module

IEmailSender

Implements

Survey

SmtpEmailSender

IEmailSender

Application

Survey

SmtpEmailSender

High level

module

IEmailSender

Dependency is inverted

Dependency inversion

• High level modules should not depend on low-level modules

• Both should depend on abstractions• Abstractions should not depend on

details• Details should depend on

abstractions

Що отримуємо• Залежності вказуються явно, немає

прихованих залежностей• Класи самодокументовані• Класи більше не відповідають за створення

об’єктів• Після створення клас гарантовано

знаходиться у робочому стані• Легко знаходити класи з багатьма

параметрами в конструкторі (і зменшувати їх відповідальності)

• Легше знаходити методи, яким потрібні не всі залежності класу

Приклад

public void Import(){ var importContext = _contextCreator.Create(_userName); // ... _fileImporter.Import(); // ...}

Прикладpublic void Import(){ var importContext = _contextCreator.Create(_userName); using (IFileImporter fileImporter = new FileImporter(importContext)) { fileImporter.Import(); }}

Abstract Factory

Потреба

• Залежність залежить від параметра, який не відомий клієнту на час його створення

• if/else та switch блоки для вирішення, яку залежність створити

• Залежність потрібно до-ініціалізувати

• Об’єкт повинен керувати часом життя залежності

SurveyImporter

FileImporter

SurveyImporter

FileImporter

IFileImporterFactory

IFileImporter

FileImporterFactory

Реалізація

• Виклик конструктора об’єкта+ передача параметра+ передача створених/отриманих залежностей

• switch, Dictionary<TParameter, Func<TResult>>

Часто фабрика повертає Null Object в default-вітці switch’а

public sealed class FileImporterFactory : IFileImporterFactory{ public IFileImporter Create(Context importContext) { switch (GetFileStorageType(importContext)) { case "csv": return new FileImporter(importContext, csvReader); case "xls": return new FileImporter(importContext, xlsReader); default: return new NullFileImporter(importContext); } }

Null Object

public SurveysImporter( IFileImporter headersFileImporter, IFileImporter dataFileImporter, IFileImporter additionalDataImporter){ if (headersFileImporter == null) { throw new ArgumentNullException("headersFileImporter"); } if (dataFileImporter == null) { throw new ArgumentNullException("dataFileImporter"); }

_headersFileImporter = headersFileImporter; _dataFileImporter = dataFileImporter; _additionalDataImporter = additionalDataImporter;}

Тут треба пояснюючий

коментар

public void Import(){ _headersFileImporter.Import(); // ... _dataFileImporter.Import(); // ... if (_additionalDataImporter != null) { _additionalDataImporter.Import(); }}

Буде розмножуват

ись по вашому коду

Потреба

• Код для NULL-випадку повинен бути ідентичним до решти випадків

Реалізаціяpublic sealed class NullFileImporter : IFileImporter{ public void Import() { }}

public sealed class SurveysImporter{ public void Import() { _headersFileImporter.Import(); // ... _dataFileImporter.Import(); // ... _additionalDataImporter.Import();}

Переваги

• Чистіший та стисліший код• Відсутність перевірок на null • Простіший код• Зменшення кількості

відповідальностей класів-клієнтів

Builder

Fluent Buildervar team = new Team( "Manchester United", "The Red Devils", Color.Red, "Manchester", "Old Trafford");

var builder = new TeamBuilder();var team = builder .CreateTeam("Real Madrid") .WithNickName("Los Merengues") .WithShirtColor(Color.White) .FromTown("Madrid") .PlayingAt("Santiago Bernabeu") .Build();

Призначення

• Конструювання складного об’єкта• Відділення логіки конструювання

від представлення даних об’єкта

Fluent Builder: Mocksvar workerPlanMock = new Mock<IWorkerPlan>();workerPlanMock .Setup(workerPlan => workerPlan.GetCountOfItemsToProcess()) .Returns(2);var worker = new Worker(workerPlanMock.Object);...

StringBuilderStringBuilder builder = new StringBuilder();builder.Append("...");builder.AppendFormat("...{0}", "value") .AppendLine() .Append("...");string result = builder.ToString();

SqlConnectionStringBuildervar builder = new SqlConnectionStringBuilder { DataSource = "localhost", InitialCatalog = "MyDB", IntegratedSecurity = true, ConnectTimeout = 10 // seconds };string connectionString = builder.ConnectionString;

Lazy Instantiation

Потреба public void Import(){ var importContext = _contextCreator.Create(_userName); if (ShouldReadFromFileSystem(importContext)) { FileImporter.Import(); }}

Реалізація private IFileImporter _fileImporter;private readonly Func<IFileImporter> _fileImporterFactory;

private IFileImporter FileImporter{ get { if (_fileImporter == null) { _fileImporter = _fileImporterFactory(); } return _fileImporter; }}

Призначення

• Відкладення створення об’єкта до моменту, коли об’єкт стає необхідний

• Оптимізація • Thread safety!

Lazy<T>private readonly Lazy<IFileImporter> _fileImporter;

private IFileImporter FileImporter{ get { return _fileImporter.Value; }}

Створення об’єктів• Coupling & Cohesion• Dependencies, Dependency

Inversion• Abstract Factory• Null Object• Fluent Builder• Lazy Instantiation

Recommended