Намір
Забезпечує уніфікований інтерфейс для набору інтерфейсів у підсистемі. Фасад визначає високорівневий інтерфейс, який робить систему легкою у використанні.
Мотивація
Структурування систем у підсистеми допомагає зменшити її складність. Загальна ціль дизайну полягає у мінімізуванні комунікацій і залежностей між підсистемами. Одним способом досягнення цієї мети являється впровадження об'єкту фасад, який забезпечує один спрощений інтерфейс до більш загальних можливостей підсистеми.
Розглянемо приклад середовища розробки, яке дає програмам доступ до його підсистеми компілювання. Ця підсистема містить класи на подобі Scanner, Parser, ProgramNode, BytecodeStream i ProgramNodeBuilder, які реалізовують компілятор. Деякі спеціалізовані програми можуть потребувати безпосередній доступ до цих класів. Але більшість клієнтів компілятора в загальному байдуже про такі деталі, як аналіз і генерація коду; вони бажають тільки скомпілювати деякий код. Для них потужній, але низькорівневий інтерфейс у підсистемі компілятора, тільки ускладнюють їхнє завдання.
Для забезпечення високорівневого інтерфейсу, який може захистити клієнтів від цих класів, підсистема компілятора також включає клас Compiler. Цей клас визначає уніфікований інтерфейс до функціональності компілятора. Клас Compiler поводить себе як фасад: він пропонує клієнтам один простий інтерфейс до підсистеми компілятора. Він склеює разом класи, які реалізовують функціональність компілятора без їхнього повного приховування. Фасад компілятора робить життя легшим для більшості програмістів без приховування низькорівневої функціональності від тих деяких, які її потребують.
Застосовуваність
Використовуйте шаблон Фасад коли
ви бажаєте забезпечити простий інтерфейс до складної підсистеми. Підсистеми часто ускладнюються під час розвитку. Більшість шаблонів, під час застосування, результують у великій кількості малих класів. Це робить підсистему більш багаторазовою і легшою у налаштовуванні, але це також робить її важчою у використанні для клієнтів, які не потребують налаштовувати її. Фасад може забезпечити просту точку зору для підсистем, яка є достатньою для більшості клієнтів. Тільки клієнти, які потребують більшу налаштованість, будуть обходити інтерфейс фасаду.
Існує багато залежностей між клієнтами і класами реалізацій абстракції. Впровадження фасаду для відмежовування підсистему від клієнтів і інших підсистем, у зв'язку з чим підтримується незалежність підсистеми і її переносимість.
Ви бажаєте нашарувати ваші підсистеми. Використовуйте фасад для визначення точку входу до кожного рівня підсистем. Якщо підсистеми є залежними, в такому разі ви можете спростити залежності між ними, зв'язуючи їх один-з-одним виключно через їхні фасади.
Структура
Учасники
- Фасад (Compiler)
- знає, які каси підсистеми являються відповідальними за запит.
- делегує запити клієнтів до відповідних об'єктів підсистеми.
- Класи підсистеми (Scanner, Parser, ProgramNode, тощо)
- реалізовують функціональність підсистеми
- виконують роботу назначену об'єктом Фасад.
- Не мають інформації фасаду; тобто, вони не підтримують з ним зв'язків.
Співпрацювання
- Клієнти спілкуються з підсистемою, надсилаючи запити до Фасаду, який передає їх до відповідних об'єктів підсистеми. Хоча об'єкти підсистеми виконують фактичну роботу, фасад може також виконувати роботу по перетворенню його інтерфейсу до інтерфейсу підсистеми.
- Клієнти, які використовують фасад не мають безпосереднього доступу до об'єктів підсистеми.
Наслідки
Шаблон Фасад пропонує наступні вигоди:
- Він відгороджує клієнтів від компонентів підсистеми, у зв'язку з чим зменшує кількість об'єктів з якими необхідно працювати і робить підсистему легкою в користуванні.
- Він пропагує слабке зв'язування між підсистемою і її клієнтами. Часто компоненти у підсистемі являються сильно зв'язаними. Слабке зв'язування дозволяє вам змінювати компоненти підсистеми без впливу на клієнтів. Фасад допомагає відшарувати систему і додавати залежності між об'єктами. Вони можуть усунути складність чи рекурсивні залежності. Це може бути важливим наслідком коли клієнт і підсистма реалізовуються незалежно.
Зменшування залежностей компілювання являється суттєвим для великих програмних систем. Ви бажаєте зберегти час мінімізовуючи перекомпілювання, коли змінюють класи підсистеми. Зменшення залежностей компілювання за допомогою фасадів може обмежити необхідне перекомпілювання через усі малі зміни у важливих системах. Фасад може спростити перенесення системи на інші платформи через те, що він зменшує ймовірність, що перебудування однієї підсистеми вимагає перебудування усіх інших.
- Він не забороняє програмам використовувати класи підсистем, якщо вони цього потребують. Тобто ви можете обрати між легкістю у використанні і повнотою.
Реалізація
Розглянемо наступні проблеми, які можуть виникнути під час реалізовування фасаду:
- Зменшення зв'язування клієнт-підсистема. Зв'язування між клієнтами і підсистемою може бути зменшене навіть більше, виконанням Фасаду як абстрактний клас з конкретними потомками для різних реалізацій підсистем. Тоді клієнти можуть спілкуватися з підсистемою через інтерфейс абстрактного класу Facade. Це абстрактне зв'язування утримує клієнтів від потреби у інформації яка реалізація підсистеми використовується.
Альтернативою до створення потомків являється конфігурування об'єкту Facade за допомогою різних об'єктів підсистеми. Для налаштовування фасаду, просто замініть один чи більше його об'єктів підсистеми.
- Публічні класи підсистеми на противагу приватним. Підсистема є аналоговою до класу у тому, що вони обоє мають інтерфейси, і вони інкапсулюють дещо — клас інкапсулює стан і операції, поки підсистема інкапсулює класи. І так само корисно думати про публічні і приватні інтерфейси класу, ми можемо думати і про приватні і публічні інтерфейси до підсистеми.
Публічні інтерфейси до підсистеми складаються з класів, які можуть використовувати усі клієнти; приватні інтерфейси є тільки для розширень підсистеми. Клас Facade являється частиною публічного інтерфейсу, звичайно, але він не являється єдиною частиною. Інші класи підсистеми являються також публічними. Наприклад, класи Parser i Scanner у підсистемі компілятора являються частиною публічного інтерфейсу.
Роблячи класи підсистеми приватними, являється корисним, але тільки декілька об'єктно-орієнтованих мов підтримують цей механізм. І С++ і Smalltalk традиційно мали глобальні простори імен для класів. Нещодавно однак, комітет стандартизації С++ добавив простори імен до мови [Str94], що дозволить вам надавати тільки публічні класи підсистеми.
Приклад Коду
Давайте ближче переглянемо як впроваджувати фасад у підсистему компілятора.
Підсистема компілятора визначає клас {BytecodeStream}, який реалізовує потік об'єктів Bytecode. Об'єкт Bytecode інкапсулює байткод, який може містити машинні інструкції. Підсистема також визначає клас Token для об'єктів, які інкапсулюють знаки у мові програмування.
Клас Scanner бере потоки символів і продукує потомки знаків, один знак за раз.
class Scanner
{
public:
Scanner(istream&) ;
virtual ~Scanner() ;
virtual Token& Scan() ;
private:
istream& _inputStream ;
} ;
Клас Parser використовує ProgramNodeBuilder для конструювання дерев аналізу з знаків, які продукує Scanner.
class Scanner
{
public:
Scanner(istream&) ;
virtual ~Scanner() ;
virtual Token& Scan() ;
private:
istream& _inputStream ;
} ;
Parser викликається з ProgramNodeBuilder для поступової побудови дерева аналізу. Ці класи співпрацюють відповідно до шаблону
Будівник.
class ProgramNodeBuilder
{
public:
ProgramNodeBuilder () ;
virtual ProgramNode* NewVariable(
const char* variableName
) const ;
virtual ProgramNode* NewAssignment(
ProgramNode* variable,
ProgramNode* expression
) const ;
virtual ProgramNode* NewReturnStatement(
ProgramNode* value
) const ;
virtual ProgramNode* NewCondition(
ProgramNode* condition,
ProgramNode* truePart,
ProgramNode* falsePart
) const ;
// ...
ProgramNode* GetRootNode() ;
private:
ProgramNode* _node ;
} ;
Дерево аналізу зроблене з примірників потомків ProgramNode на подобі StatementNode, ExpressionNode тощо. Ієрархія programNode являється прикладом шаблону
Композитор. ProgramNode визначає інтерфейс для маніпулювання вузлом програми і його потомками, якщо вони існують.
class ProgramNode
{
public:
// program node manipulation
virtual void GetSourcePosition(int& line, int& index) ;
// ...
// child manipulation
virtual void Add(ProgramNode*) ;
virtual void Remove(ProgramNode*) ;
// ...
virtual void Traverse(CodeGenerator&) ;
protected:
ProgramNode() ;
} ;
Операція Traverse бер об'єкт CodeGeneraor. Потомки ProgramNode використовують цей об'єкт для генерування машинного коду у формі об'єкту Bytecode в BytecodeStream. Клас CodeGenerator являється відвідувачем (перегляньте Відвідувач).
class CodeGenerator
{
public:
virtual void Visit(StatementNode*) ;
virtual void Visit(ExpressionNode*) ;
// ...
protected:
CodeGenerator(BytecodeStream&);
protected:
BytecodeStream& _output;
} ;
CodeGenerator має потомки, наприклад, StackMachibeCodeGenerator і RISCCodeGenerator, які генерують машинний код для різних архітектур устаткування.
Кожен потомок класу ProgramNode реалізовує операцію Traverse для її виклику з його дочірніх об'єктів ProgramNode. У свою чергу, кожен потомок виконує те саме для кожного з його потомків, і так далі. Наприклад, ExpressionNode визначає Traverse наступним чином:
void ExpressionNode :: Traverse (CodeGenerator& cg)
{
cg.Visit(this);
ListIterator i(_children);
for (i.First(); !i.IsDone(); i.Next())
{
i.CurrentItem()->Traverse(cg) ;
}
}
Класи, які ми обговорили до цього складають підсистему компілятора. Тепер ми впровадимо клас Compiler - фасад який складає усі ці частини в одне ціле. Compiler забезпечує простий інтерфейс для компілювання вихідного коду і генерування коду для певної машини.
class Compiler
{
public:
Compiler();
virtual void Compile(istream&, BytecodeStream&) ;
} ;
void Compiler :: Compile ( istream& input,
BytecodeStream& output )
{
Scanner scanner(input) ;
ProgramNodeBuilder builder ;
Parser parser ;
parser.Parse(scanner, builder) ;
RISCCodeGenerator generator(output) ;
ProgramNode* parseTree = builder.GetRootNode() ;
parseTree->Traverse(generator) ;
}
Ця реалізація жорстко закодовує використовуваний тип генератора коду, отож програмістам не потрібно вказувати цільову архітектуру. Це може мати сенс, якщо існує реалізація тільки для однієї архітектури. У іншому випадку, ми можемо забажати змінити конструктор Compiler для передавання параметру CodeGenerator. Програмісти можуть вказати, який генератор використовувати, коли вони створюють примірник класу Compiler. Фасад компілятора може параметризувати інших учасників, таких як Scanner і ProgramNodeBuilder, що додає гнучкість до системи, але також знижує основну ціль шаблону Фасад, яка полягає у спрощенні інтерфейсу для загального випадку.
h2Відомі використання
Приклад компілятора з попередньої секції, частково був взятий з системи компілятора ObjectWorks\Smalltalk [Par90].
У програмному фреймворку ET++ [WGM88]. Програма може бути вбудовані інструменти перегляду для перевірки її об'єктів під час виконання програми. Ці інструменти перегляду реалізовуються у окремій підсистемі, яка включає клас Facade названий “ProgrammingEnvirinment”. Цей фасад визначає операції на подобі InspectObject і InspectClass для доступу до переглядачів.
Програма ET++ може також відмовитись від підтримки переглядачів. У цьому випадку, ProgrammingEnvironment реалізовує ці запити як нульові (пусті) операції; тобто, вони нічого не виконують. Тільки потомок ETProgrammingEnvironment реалізовує ці запити за допомогою операцій, які відображають відповідні переглядачі. Програма утримує інформацію чи середовище переглядачів доступне, чи ні; існує абстрактне зв'язування між програмами і системою переглядачів.
Операційна система Choices [CIRM93] використовує фасади для компонування великої кількості фреймворків у одну систему. Ключові абстракції в ОС Choices являються процеси, збереження і адресні простори. Для кожного з цих абстракцій існує відповідна підсистема, реалізована як фреймворк, який підтримує перенесення ОС Choices на декілька різних архітектур устаткування. Дві з цих підсистем мають “представництво” (тобто, фасад). Ці представництва являються FileSystemInterface (збереження) i Domain (адресні простори).
Наприклад, фреймворк віртуальної пам'яті мітить Domain, як його фасад. Тип Domain представляє простір адрес. Він забезпечує перетворення між віртуальними адресами і відгалуженнями у об'єкти пам'яті, файли і резервного збереження. Головні операції у Domain підтримує додавання об'єктів пам'яті з певною адресою, видалення об'єкта пам'яті, і обробка дефектів сторінок пам'яті.
Як показує попередня діаграма, підсистема віртуальної пам'яті використовує всередині наступні компоненти:
- MemoryObject представляє збереження даних.
- MemoryObjectCache кешує дані класу MemoryObjects у фізичній пам'яті. MemoryObjectCache являється Стратегією, яка локалізує політику кешування.
- AddressTranslation інкапсулює устаткування перетворення адрес.
Операція RepairFault викликається кожного разу, коли виникає виключення дефекту пам'яті. Тип Domain знаходить об'єкт пам'яті за адресою, яка викликає дефект і делегує операцію RepairFault до кешу асоційованого з цим об'єктом пам'яті. Тип Domain може бути налаштований зміною його компонентів.
Споріднені Шаблони
Абстрактна Фабрика може використовуватися Фасадом для забезпечення інтерфейсу для створення підсистем об'єктів у системно-незалежний спосіб. Абстрактна Фабрика може також використовуватися як альтернатива до Фасаду для приховування платформенно-залежних класів.
Медіатор є подібним до Фасаду у тому, що він абстрагує функціональність існуючих класів. Однак, ціль Медіатора полягає у абстрагуванні довільних зв'язків між співпрацюючими об'єктами, часто централізовуючи функціональність, яка не належить будь-кому з них.
Співпрацюючі об'єкти медіатора являються незв'язаними і спілкуються з медіатором замість того, щоб спілкуватись один-з-одним безпосередньо. На противагу Медіатору, Фасад просто абстрагує інтерфейс до об'єктів підсистеми щоб полегшити їхнє використання; він не визначає нову функціональність і класи підсистеми не знають про нього.
Зазвичай необхідний тільки один об'єкт Фасад. І об'єкт Фасад часто являється
Синглтоном.