Міністерство освіти та науки України
Національний університет «Львівська політехніка»
Розрахунково-графічна робота
з дисципліни «Основи автоматизованого проектування складних об’єктів і систем»
на тему «Шаблони проектування»
Львів-2013
Зміст
Що таке шаблони проектування?
Породжуючі шаблони
Структурні шаблони
Шаблони поведінки
Вступ
Сьогодні, більшість компаній, які займаються розробкою програмних ужитків використовуючи шаблони проектування. Отже, цікавим питанням для дослідження постає питання, що являє собою шаблони проектування, як їх застосовувати та як ними оперувати.
На початку 1990-х років, коли об'єктно-орієнтовані технології тільки зароджувалися, всіх нас захопила ідея спадкування. Це ставлення обіцяло грандіозні перспективи. За допомогою спадкування можна було програмувати тільки відмінності!
Тобто маючи клас, який робить щось корисне, ми могли створити його підклас і змінити лише ті частини, які нас не влаштовували. Ми могли повторно використовувати код, просто успадкувавши його! Ми могли організовувати цілі ієрархії програмних конструкцій, в яких на кожному рівні використовувався код з попередніх рівнів. Нам відкрився прекрасний новий світ. Але, як більшість прекрасних нових світів, цей на перевірку теж виявився не цілком придатним до проживання. До 1995 року стало ясно, що спадкуванням дуже просто зловжити, а обходиться таке зловживання вкрай дорого. Гамма, Хелм, Джонсон і Вліссідес навіть визнали доречним підкреслити: «Віддавайте перевагу композиції об'єктів, а не спадкоємства класів». Тому ми стали рідше застосовувати спадкування, частенько замінюючи його композицією або делегуванням.
Перші шаблони проектування для мови програмування Smalltalk представили в 1987 р. Кент Бек і Вард Каннінгем. Але це було тільки початком, справжнє визнання в світі програмування вони отримали після публікації книги «Прийоми об'єктно-орієнтованого проектування. Шаблони проектування», написаної програмістами Е. Гамма, Р. Хелмом, Р. Джонсоном і Дж. Вліссідесом. У ній були представлені 23 шаблони, що стали зараз основними. Дана робота дала поштовх до вивчення шаблонів програмістами. Видання «банди чотирьох» (так жартома прозвали авторів книги) до цих пір залишається одним з ІТ-бестселерів, і його постійно публікують.
Втім, застосування шаблонів проектування пов'язане і з певними проблемами. Зокрема, поширена думка, що тільки фахівець, що володіє достатньо високою кваліфікацією і розуміючий, які з шаблонів йому потрібні, зуміє правильно використовувати їх у своїх програмах. Крім того, найчастіше деякі розробники, які вивчили лише кілька шаблонів проектування, починають вживати їх всюди, навіть там, де вони не дуже добре справляються із завданням і ускладнюють створюване ПЗ.
В даній розрахунковій роботі я розповім, що таке шаблони проектування дам зрозуміти їхню структуру, спробую вказати на їхні помилки та наведу декілька прикладів популярних шаблонів різного типу.
Що таке шаблони проектування?
Шаблони програмного забезпечення (software design patterns) — ефективні методи вирішення завдань проектування ПЗ. Шаблон не є кінцевим зразком, який можна відразу переводити в програмний код. Об'єктно-орієнтований шаблон часто є зразком розв’язання проблеми і показує відношення між об'єктами та класами, без вказівки на те, як буде зрештою виконане це відношення.
Типи шаблонів GOF бувають:
Шаблони поведінки
Твірні шаблони
Структурні шаблони
Породжуючі шаблони
Шаблони, що породжують (Creational patterns) — це шаблони, що абстрагують процес інстанціювання. Вони можуть зробити систему відмінною від способу створення, композиції та виявлення її об'єктів.
Шаблон, який створює класи, застосовує спадкування, щоб змінювати створюваний клас, а шаблон, що створює об'єкти, передає інстанціювання іншому об'єктові.
Ці шаблони є важливими, коли система більше залежить не від спадкування класів, а від композиції об'єктів.
Так, замість прямого кодування фіксованого набору поведінок, визначається невеликий набір основних поведінок, завдяки композиції яких можна одержати складніші. Для реалізації об'єктів з певною поведінкою потрібно щось більше, ніж просто інстанціювання прикладу класу.
Отже, породжуючі шаблони. Основним завданням таких шаблонів є спростити створення об’єктів.
Час від часу ви працюєте із деяким набором об’єктів через групу інтерфейсів. Після того хочете створювати певні об’єкти тільки із іншого набору, щоб задіяти ваш код до інших умов. Безсумнівно, група інтерфейсів, крізь які ви оперуєте, буде залишатись така ж сама.
Шаблони, що породжують, інкапсулюють дані про певні класи, які використовуються у системі та не відображають деталі того, як ці класи створюються і з’єднюються між собою.
Перелік шаблонів, що породжують
Фабричний метод (Factory Method)
Прототип (Prototype)
Одинак (Singleton)
Абстрактна фабрика (Abstract Factory)
Будівник (Builder)
Абстрактна фабрика (Abstract Factory) — шаблон, що відноситься до класу твірних шаблонів.
Призначення: подає інтерфейс для створення родин зв'язаних або залежних об'єктів, не специфікуючи їхніх певних класів.
Застосування: слід застосовувати шаблон Абстрактна фабрика тоді:
система не має залежати від того, як створюються, компонуються та представляються вхідні до неї об'єкти;
вхідні до родини зв'язані об'єкти повинні застосовуватися разом і необхідно забезпечити виконання цього обмеження;
система повинна конфігуруватися однією з родин створюючих її об'єктів;
треба подати бібліотеку об'єктів, розкриваючи тільки їхні інтерфейси, але не реалізацію.
Абстрактна фабрика для створення об’єктів надає простий інтерфейс, які відносяться до того чи іншого сімейства.
В нашому прикладі сімейством є певний набір іграшок, які реалізують базові класи ведмедика (Bear), кота (Cat), та інші. Тобто увесь звіринець певної реалізації, дерев’яної або м’якої іграшки, і буде сімейством.
Абстрактна фабрика вибирає інтерфейс, що повертає об’єкти кота або ведмедя (базові класи). Певні реалізації фабрики повертають конкретні застосування іграшок потрібного сімейства.
// абстрактна фабрика (abstract factory)
public interface IToyFactory
{
Bear GetBear();
Cat GetCat();
}
// конкретна фабрика (concrete factory)
public class TeddyToysFactory : IToyFactory
{
public Bear GetBear()
{
return new TeddyBear();
}
public Cat GetCat()
{
return new TeddyCat();
}
}
// і ще одна конкретна фабрика
public class WoodenToysFactory : IToyFactory
{
public Bear GetBear()
{
return new WoodenBear();
}
public Cat GetCat()
{
return new WoodenCat();
}
}
Все таки зрозуміло, як тільки ми будемо мати якийсь екземпляр фабрики, ми зможемо плодити сімейство потрібних нам іграшок. Тому розглянемо використання:
// Спочатку створимо «дерев'яну» фабрику
IToyFactory toyFactory = new WoodenToysFactory();
Bear bear = toyFactory.GetBear();
Cat cat = toyFactory.GetCat();
Console.WriteLine("I've got {0} and {1}", bear.Name, cat.Name);
// Вивід на консоль буде: [I've got Wooden Bear and Wooden Cat]
// А тепер створимо «плюшеву» фабрику, наступна лінійка є єдиною різницею в коді
IToyFactory toyFactory = new TeddyToysFactory();
// Як бачимо код нижче не відрізняється від наведеного вище
Bear bear = toyFactory.GetBear();
Cat cat = toyFactory.GetCat();
Console.WriteLine("I've got {0} and {1}", bear.Name, cat.Name);
// А вивід на консоль буде інший: [I've got Teddy Bear and Teddy Cat]
Два шматки коду майже повністю ідентичні – різниця полягає тільки в певному «мішку». Якщо вас ще зацікавлять реалізації іграшок, то вони доволі тривіальні.
// Базовий клас для усіх котиків, базовий клас AnimalToy містить Name
public abstract class Cat : AnimalToy
{
protected Cat(string name) : base(name) { }
}
// Базовий клас для усіх ведмедиків
public abstract class Bear : AnimalToy
{
protected Bear(string name) : base(name) { }
}
// Конкретні реалізації
class WoodenCat : Cat
{
public WoodenCat() : base("Wooden Cat") { }
}
class TeddyCat : Cat
{
public TeddyCat() : base("Teddy Cat") { }
}
class WoodenBear : Bear
{
public WoodenBear() : base("Wooden Bear") { }
}
class TeddyBear : Bear
{
public TeddyBear() : base("Teddy Bear") { }
}
Абстрактна Фабрика є дуже широко використовуваним дизайн-шаблоном. Дуже яскравим прикладом буде ADO.NET DbProviderFactory13, яка є абстрактною фабрикою, що визначає інтерфейси для отримання DbCommand, DbConnection і так далі. Певна фабрика SqlClientFactory поверне відповідно SqlCommand, SqlConnection і так далі. Це дозволить працювати із різними джерелами даних.
Структура
Будівник (Builder) — шаблон проектування, який належить класу твірних шаблонів.
Призначення: виділяє конструювання певного об'єкта від його подання, так у результаті одного й того ж процесу реалізації можуть бути отримані різні подання.
Застосування: слід використовувати Будівник при:
алгоритм створення складного об'єкта не має залежати від того, з яких частин створений об'єкт;
процес створення повинен забезпечити різні подання об'єкта.
Будівельник вимальовує стандартний структурований процес створення певного складного об’єкта, виділяючи логіку будування об’єкта від його реалізації.
Структура
Структурні шаблони
Структурні шаблони (structural patterns) — шаблони проектування, що розглядають питання, які саме класи та об'єкти утворюють більші структури (за розмірами).
Для утворення композицій із різних інтерфейсів та реалізацій структурні шаблони (рівня класу) можуть і повинні використовувати спадковість.
Для отримання нової функціональності структурні шаблони реалізують компонування об'єктів. Можливість змінювання композицій об'єктів під час виконання взаємодіє з додатковою гнучкістю, що не є припустимим для статичної композиції наших класів.
Перелік структурних шаблонів:
Міст (Bridge)
Фасад (Facade)
Компонувальник (Composite)
Замісник (Proxy)
Адаптер (Adapter)
Легковаговик (Flyweight)
Декоратор (Decorator)
Основним завданням структурних шаблонів є формування найбільш подібної структури та взаємозв’язку між певними класами для реалізації певних завдань.
Коли потрібно, щоб один об’єкт повинен бути зрозумілим під іншим інтерфейсом, використовуємо Адаптер.
Якщо вам хочеться розділити імплементацію та абстракцію так, щоб на одній стороні ви матимете абстракцію, а з іншої декілька реалізацій, при доступі до модифікацій, то варто задуматися над об’єднанням таких незалежних абстракцій за допомогою шаблону Міст.
Коли елемент вміщує собі подібні елементи, а вони, в свою чергу, також вміщують елементи, то таку структуру легше виконати за допомогою Компонувальника.
Адаптер (Adapter) — шаблон проектування (структурний), недоступний для модифікації, створений для організації застосування функцій об'єкта, через створений інтерфейс.
Призначення: адаптація інтерфейсу одного класу в інший. Коли система підтримує необхідні дані ми застосовуємо адаптер, що забезпечує роботу класів з несумісними інтерфейсами, але має невідповідний інтерфейс.
Застосування: адаптер зумовлює створення класу(оболонки) з необхідним інтерфейсом.
Адаптер дає можливість користуватись об’єктом, що не є прийнятним у нашій системі і не можна змінити. Ми адаптуємо його ж функціональність через інший, відомий нам інтерфейс.
Структура
Реалізація
Для реалізації даного шаблону ми включаємо існуючий клас в інший. Даний інтерфейс нашого включаючого класу приводимо у відповідні з ним вимоги. Виклики методів трансформується у виклики методів включеного класу.
Приклад С++
#include <string>
#include <iostream>
// Target
class CShape
{
public:
virtual ~CShape() {};
virtual void Draw() {
std::cout << "Rectangle" << std::endl;
};
};
// Adaptee
class CTextViewer
{
public:
CTextViewer() {};
virtual ~CTextViewer() {};
void DrawText() {
std::cout << "Text" << std::endl;
};
};
// Adapter
class CTextShape: public CShape, protected CTextViewer
{
public:
CTextShape() {};
virtual ~CTextShape() {};
virtual void Draw() {
DrawText(); // Call Adaptee function
};
};
// Client
void DrawObject(CShape* p)
{
p->Draw();
}
int main()
{
// Create adapter and draw a object
CShape* obj = new CTextShape();
DrawObject(obj);
delete obj;
return 0;
}
Компонувальник (Composite) — дозволяє уніфіковане звертання для всіх елементів дерева, шаблон який згуртовує об'єкти в ієрархічну структуру (деревовидну).
Призначення: для формування більших компонентів проектувальник може згуртовувати дрібні компоненти, вони в свою чергу, в будь-який момент можуть стати основою, що створюють більші. Дозволяє користувачам будувати великі структури з простіших компонентів.
Структура
Шаблони поведінки
Шаблони поведінки (behavioral patterns) — шаблони, які пов'язані з розподілом та алгоритмами обов'язків серед об'єктів. Ми говоримо не тільки про самі класи та об'єкти, але й про взаємодію їхніх типових способів. Дані шаблони (поведінки) характеризують великий потік керування, що під час виконання програми досить важко прослідкувати. Ми акцентуємо увагу на зв'язках між об'єктами, а не на потоці керування, що дуже важливо.
Щоб поміж різних класів розподілити поведінку у даних шаблонах рівня класу використовуємо спадкування.
Композиція використовується у шаблонах поведінки рівня об'єкта. Багато рівноправних об'єктів завдяки кооперації пораються із завданням, яке з них жодному не під силу. Важливим є те, як саме об'єкти можуть отримувати інформацію про існування один одного. Об'єкти(колеги) зберігають посилання один на одного, тому ступінь зв'язаності системи посилює саме це. Всім об'єктам довелось би мати інформацію про всі інші, при максимальному рівні зв'язаності. Нижче наведені шаблони, що вирішують нашу проблему.
Перелік шаблонів поведінки
Шаблонний метод (Template Method)
Ітератор (Iterator)
Знімок (Memento)
Стратегія (Strategy)
Відвідувач (Visitor)
Ланцюг обов'язків (CoR)
Стан (State)
Спостерігач (Observer)
Посередник (Mediator)
Команда (Command)
Інтерпретатор (Interpreter)
Ще однією групою шаблонів є ті, що звертають свою увагу на поведінці. Тобто вони або інкапсулюють поведінку та дозволяють її поділити.
Використовують Ланцюжок Відповідальностей для забезпечення роботи від одного класу до іншого поки робота не буде виконана.
Ітератор (Iterator) — шаблон, саме він належить до великого класу шаблонів поведінки.
Призначення: не розкриваючи внутрішнього налаштування до всіх елементів надає спосіб послідовного доступу складеного об'єкта,.
Мотивація: інколи потрібно обходити список різними способами, залежно від задачі, що вирішуємо. Складений об'єкт рівня, припустимо список, змушений надавати спосіб доступу до даних елементів, при цьому не розкривати внутрішню структуру. Оскільки немає ніякого бажання та потреби засмічувати інтерфейс класу. Іноді треба, щоб в один момент часу існувало б декілька активних операцій для обходу списку.
Ітератор дає дозвіл доступатися почергово до певних елементів будь-якої колекції без освоєння суті її імплементації.
Уявимо, що ми розробляємо стратегічну гру. Вона має складну структуру: складається із героя і трьох груп. Коли керівник видає указ і ресурси щоб вилікувати всіх воїнів, ви хочете проітерувати по всіх воїнах і викликати метод Treat() на кожному екземплярі.
Таким чином, в застосуванні щодо нашої проблеми, ми хочемо щоб SoldiersIterator «пробігся» по всіх солдатах. Код нижче показує використання Ітератора.
var iterator = new SoldiersIterator(earthArmy);
while (iterator.HasNext())
{
var currSoldier = iterator.Next();
currSoldier.Treat();
}
Як бачимо, ми отримали екземпляр класу ітератора SoldiersIterator. І простим циклом проходимося по всіх солдатах армії. Це дуже легко, що і є основним завданням ітератора.
Армія складається із одного героя і може містити багато груп, кожна із яких може містити багато солдатів. Отже, як ми бачимо, структура армії складна і деревовидна. Код нижче показує створення армії:
var andriybuday = new Hero("Andriy Buday");
var earthArmy = new Army(andriybuday);
var groupA = new Group();
for (int i = 1; i < 4; ++i) groupA.AddNewSoldier(new Soldier("Alpha:" + i));
var groupB = new Group();
for (int i = 1; i < 3; ++i) groupB.AddNewSoldier(new Soldier("Beta:" + i));
var groupC = new Group();
for (int i = 1; i < 2; ++i) groupC.AddNewSoldier(new Soldier("Gamma:" + i));
earthArmy.AddArmyGroup(groupB);
earthArmy.AddArmyGroup(groupA);
earthArmy.AddArmyGroup(groupC);
Герой (Hero) — це клас унаслідуваний від солдата (Soldier) і основна різниця полягає в тому, що він має вищий початковий рівень здоров’я.
class Soldier
{
public String Name;
public int Health;
private const int SoldierHealthPoints = 100;
protected virtual int MaxHealthPoints { get { return SoldierHealthPoints; } }
public Soldier(String name)
{
Name = name;
}
public void Treat()
{
Health = MaxHealthPoints;
Console.WriteLine(Name);
}
}
class Hero : Soldier
{
private const int HeroHealthPoints = 500;
protected override int MaxHealthPoints { get { return HeroHealthPoints; } }
public Hero(String name)
: base(name)
{
}
}
Тож, якщо ми можемо рухатися по складній колекції так легко, де ж вся складність? Звісно, вона інкапсульована в конкретному класі ітератора.
class SoldiersIterator
{
private readonly Army _army;
private bool _heroIsIterated;
private int _currentGroup;
private int _currentGroupSoldier;
public SoldiersIterator(Army army)
{
_army = army;
_heroIsIterated = false;
_currentGroup = 0;
_currentGroupSoldier = 0;
}
public bool HasNext()
{
if (!_heroIsIterated) return true;
if (_currentGroup < _army.ArmyGroups.Count) return true;
if (_currentGroup == _army.ArmyGroups.Count - 1)
if (_currentGroupSoldier < _army.ArmyGroups[_currentGroup].Soldiers.Count)
return true;
return false;
}
{
Soldier nextSoldier;
if (_currentGroup < _army.ArmyGroups.Count)
{
// В кожній групі ітеруємо по кожному солдату
if (_currentGroupSoldier < _army.ArmyGroups[_currentGroup].Soldiers.Count)
{
nextSoldier =
_army.ArmyGroups[_currentGroup].Soldiers[_currentGroupSoldier];
_currentGroupSoldier++;
}
else
{
_currentGroup++;
_currentGroupSoldier = 0;
return Next();
}
}
// Герой останнім покидає поле бою
else if (!_heroIsIterated)
{
_heroIsIterated = true;
return _army.ArmyHero;
}
else
{
// Викидуємо виняток
throw new Exception("End of colletion");
}
return nextSoldier;
}
}
Даний приклад трохи відхиляється від стандартного. Я спробував поставити собі за мету виділити головне завдання, котре вирішує шаблон. Основною різницею між поясненням і великою кількості інших пояснень є те, що стандартні є більше абстраговані. Я створював потрібний нам ітератор так:
var iterator = new SoldiersIterator(earthArmy);
Висновок
У цій розрахунково-графічній роботі я коротко розглянув основні концепції та принципи роботи шаблонів проектування.
Типи шаблонів бувають:
Шаблони поведінки
Твірні шаблони
Структурні шаблони
Оскільки шаблонів проектування є дуже багато я не зміг розкрити їх усіх проте я навів основні і показав їх реалізацію. Через загальність теми не вдалося описати усі можливі шаблони, які можна застосувати в проекті.
В цілому шаблони являють собою якусь архітектурну конструкцію, що допомагає описати і вирішити певну загальну задачу проектування. Вони придбали таку популярність тому, що розробка ПЗ до часу їх формалізації вже була досить розвинена.
Література:
Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к ОО анализу и проектированию. — М.: «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5
Мартин Р.С., Мартин М. Принципы, паттерны и методики гибкой разработки на языке C# (2011)
Мартин Фаулер Шаблоны корпоративных приложений (Signature Series)— М.: «Вильямс», 2012. — 544 с. — ISBN 978-5-8459-1611-2
Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы ООП. Паттерны проектирования — СПб: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1 (также ISBN 5-272-00355-1)
Книга "Дизайн-патерни - просто, як двері"
http://uk.wikipedia.org/wiki/Шаблони_проектування_програмного_забезпечення