Шаблон фасад (англ. Facade) — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.
Фасад | |
---|---|
Facade | |
Тип | структурный |
Описан в Design Patterns | Да |
Описание
правитьПроблема
правитьКак обеспечить унифицированный интерфейс с набором разрозненных реализаций или интерфейсов, например, с подсистемой, если нежелательно сильное связывание с этой подсистемой или реализация подсистемы может измениться?
Решение
правитьОпределить одну точку взаимодействия с подсистемой — фасадный объект, обеспечивающий общий интерфейс с подсистемой, и возложить на него обязанность по взаимодействию с её компонентами. Фасад — это внешний объект, обеспечивающий единственную точку входа для служб подсистемы. Реализация других компонентов подсистемы закрыта и не видна внешним компонентам. Фасадный объект обеспечивает реализацию GRASP паттерна Устойчивый к изменениям (Protected Variations) с точки зрения защиты от изменений в реализации подсистемы.
Особенности применения
правитьШаблон применяется для установки некоторого рода политики по отношению к другой группе объектов. Если политика должна быть яркой и заметной, следует воспользоваться услугами шаблона Фасад. Если же необходимо обеспечить скрытность и аккуратность (прозрачность), более подходящим выбором является шаблон Заместитель (Proxy).
Примеры
правитьC++
править#include <iostream>
#include <string>
#include <memory>
#include <string_view>
/** Абстрактный музыкант - не является обязательной составляющей паттерна, введен для упрощения кода */
class Musician {
const char* name;
public:
Musician(std::string_view name) {
this->name = name.data();
}
virtual ~Musician() =default;
protected:
void output(std::string_view text) {
std::cout << this->name << " " << text << "." << std::endl;
}
};
/** Конкретные музыканты */
class Vocalist: public Musician {
public:
Vocalist(std::string_view name): Musician(name) {}
void singCouplet(int coupletNumber) {
std::string text = "спел куплет №";
text += std::to_string(coupletNumber);
output(text);
}
void singChorus() {
output("спел припев");
}
};
class Guitarist: public Musician {
public:
Guitarist(std::string_view name): Musician(name) {}
void playCoolOpening() {
output("начинает с крутого вступления");
}
void playCoolRiffs() {
output("играет крутые риффы");
}
void playAnotherCoolRiffs() {
output("играет другие крутые риффы");
}
void playIncrediblyCoolSolo() {
output("выдает невероятно крутое соло");
}
void playFinalAccord() {
output("заканчивает песню мощным аккордом");
}
};
class Bassist: public Musician {
public:
Bassist(std::string_view name): Musician(name) {}
void followTheDrums() {
output("следует за барабанами");
}
void changeRhythm(std::string_view type) {
std::string text = ("перешел на ритм ");
text += type;
text += "a";
output(text);
}
void stopPlaying() {
output("заканчивает играть");
}
};
class Drummer: public Musician {
public:
Drummer(std::string_view name): Musician(name) {}
void startPlaying() {
output("начинает играть");
}
void stopPlaying() {
output("заканчивает играть");
}
};
/** Фасад, в данном случае - знаменитая рок-группа */
class BlackSabbath {
std::unique_ptr<Vocalist> vocalist;
std::unique_ptr<Guitarist> guitarist;
std::unique_ptr<Bassist> bassist;
std::unique_ptr<Drummer> drummer;
public:
BlackSabbath() {
vocalist = std::make_unique<Vocalist>("Оззи Осборн");
guitarist = std::make_unique<Guitarist>("Тони Айомми");
bassist = std::make_unique<Bassist>("Гизер Батлер");
drummer = std::make_unique<Drummer>("Билл Уорд");
}
void playCoolSong() {
guitarist->playCoolOpening();
drummer->startPlaying();
bassist->followTheDrums();
guitarist->playCoolRiffs();
vocalist->singCouplet(1);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");
guitarist->playCoolRiffs();
vocalist->singCouplet(2);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");
guitarist->playIncrediblyCoolSolo();
guitarist->playCoolRiffs();
vocalist->singCouplet(3);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");
guitarist->playCoolRiffs();
bassist->stopPlaying();
drummer->stopPlaying();
guitarist->playFinalAccord();
}
};
int main() {
std::cout << "OUTPUT:" << std::endl;
BlackSabbath band;
band.playCoolSong();
return 0;
}
/**
* OUTPUT:
* Тони Айомми начинает с крутого вступления.
* Билл Уорд начинает играть.
* Гизер Батлер следует за барабанами.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №1.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №2.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми выдает невероятно крутое соло.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №3.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми играет крутые риффы.
* Гизер Батлер заканчивает играть.
* Билл Уорд заканчивает играть.
* Тони Айомми заканчивает песню мощным аккордом.
*/
JavaScript
править/* Complex parts */
function SubSystem1() {
this.method1 = function() {
console.log("вызван SubSystem1.method1");
};
}
function SubSystem2() {
this.method2 = function() {
console.log("вызван SubSystem2.method2");
};
this.methodB = function() {
console.log("вызван SubSystem2.methodB");
};
}
/* Facade */
function Facade() {
var s1 = new SubSystem1(),
s2 = new SubSystem2();
this.m1 = function() {
console.log("вызван Facade.m1");
s1.method1();
s2.method2();
};
this.m2 = function() {
console.log("вызван Facade.m2");
s2.methodB();
};
}
/* Client */
function test() {
var facade = new Facade();
facade.m1();
facade.m2();
}
test();
/*
Выведет:
"вызван Facade.m1"
"вызван SubSystem1.method1"
"вызван SubSystem2.method2"
"вызван Facade.m2"
"вызван SubSystem2.methodB"
*/
CoffeeScript
править# Загрузчик изображений
class ImageLoader
loadImage = (src) ->
# ...
constructor : (hash = {}) ->
@images = {}
@images[name] = loadImage(src) for name, src of hash
# Загрузчик аудио
class SoundLoader
loadSound = (src) ->
# ...
constructor : (hash = {}) ->
@sounds = {}
@sounds[name] = loadSound(src) for name, src of hash
# Фасад
class Loader
constructor : ({images, sounds}) ->
@images = new ImageLoader(images).images
@sounds = new SoundLoader(sounds).sounds
sound : (name) ->
@sounds[name]
image : (name) ->
@images[name]
PHP
править/**
* Реализации отдельных частей компьютера.
* У каждого метода классов имеется какая-то реализация, в данном примере она опущена.
*/
/**
* Class CPU, отвечает за работу процессора
*/
class CPU
{
public function freeze() {}
public function jump($position) {}
public function execute() {}
}
/**
* Class Memory, отвечает за работу памяти
*/
class Memory
{
const BOOT_ADDRESS = 0x0005;
public function load($position, $data) {}
}
/**
* Class HardDrive, отвечает за работу жёсткого диска
*/
class HardDrive
{
const BOOT_SECTOR = 0x001;
const SECTOR_SIZE = 64;
public function read($lba, $size) {}
}
/**
* Пример шаблона "Фасад"
* В качестве унифицированного объекта выступает Компьютер.
* За этим объектом будут скрыты все детали работы его внутренних частей.
*/
class Computer
{
protected $cpu;
protected $memory;
protected $hardDrive;
/**
* Computer constructor.
* Инициализируем части
*/
public function __construct()
{
$this->cpu = new CPU();
$this->memory = new Memory();
$this->hardDrive = new HardDrive();
}
/**
* Упрощённая обработка поведения "запуск компьютера"
*/
public function startComputer()
{
$cpu = $this->cpu;
$memory = $this->memory;
$hardDrive = $this->hardDrive;
$cpu->freeze();
$memory->load(
$memory::BOOT_ADDRESS,
$hardDrive->read($hardDrive::BOOT_SECTOR, $hardDrive::SECTOR_SIZE)
);
$cpu->jump($memory::BOOT_ADDRESS);
$cpu->execute();
}
}
/**
* Пользователям компьютера предоставляется Фасад (компьютер),
* который скрывает все сложность работы с отдельными компонентами.
*/
$computer = new Computer();
$computer->startComputer();
Python
править# Сложные части системы
class CPU(object):
def __init__(self):
# ...
pass
def freeze(self):
# ...
pass
def jump(self, address):
# ...
pass
def execute(self):
# ...
pass
class Memory(object):
def __init__(self):
# ...
pass
def load(self, position, data):
# ...
pass
class HardDrive(object):
def __init__(self):
# ...
pass
def read(self, lba, size):
# ...
pass
# Фасад
class Computer(object):
def __init__(self):
self._cpu = CPU()
self._memory = Memory()
self._hardDrive = HardDrive()
def startComputer(self):
self._cpu.freeze()
self._memory.load(BOOT_ADDRESS, self._hardDrive.read(BOOT_SECTOR, SECTOR_SIZE))
self._cpu.jump(BOOT_ADDRESS)
self._cpu.execute()
# Клиентская часть
if __name__ == "__main__":
facade = Computer()
facade.startComputer()
C#
правитьusing System;
namespace Library
{
/// <summary>
/// Класс подсистемы
/// </summary>
/// <remarks>
/// <li>
/// <lu>реализует функциональность подсистемы;</lu>
/// <lu>выполняет работу, порученную объектом <see cref="Facade"/>;</lu>
/// <lu>ничего не "знает" о существовании фасада, то есть не хранит ссылок на него;</lu>
/// </li>
/// </remarks>
internal class SubsystemA
{
internal string A1()
{
return "Subsystem A, Method A1\n";
}
internal string A2()
{
return "Subsystem A, Method A2\n";
}
}
internal class SubsystemB
{
internal string B1()
{
return "Subsystem B, Method B1\n";
}
}
internal class SubsystemC
{
internal string C1()
{
return "Subsystem C, Method C1\n";
}
}
}
/// <summary>
/// Facade - фасад
/// </summary>
/// <remarks>
/// <li>
/// <lu>"знает", каким классами подсистемы адресовать запрос;</lu>
/// <lu>делегирует запросы клиентов подходящим объектам внутри подсистемы;</lu>
/// </li>
/// </remarks>
public class Facade
{
Library.SubsystemA a = new Library.SubsystemA();
Library.SubsystemB b = new Library.SubsystemB();
Library.SubsystemC c = new Library.SubsystemC();
public void Operation1()
{
Console.WriteLine("Operation 1\n" +
a.A1() +
a.A2() +
b.B1());
}
public void Operation2()
{
Console.WriteLine("Operation 2\n" +
b.B1() +
c.C1());
}
}
class Program
{
static void Main(string[] args)
{
Facade facade = new Facade();
facade.Operation1();
facade.Operation2();
// Wait for user
Console.Read();
}
}
Ruby
правитьmodule Library
# <summary>
# Класс подсистемы
# </summary>
# <remarks>
# <li>
# <lu>реализует функциональность подсистемы;</lu>
# <lu>выполняет работу, порученную объектом <see cref="Facade"/>;</lu>
# <lu>ничего не "знает" о существовании фасада, то есть не хранит ссылок на него;</lu>
# </li>
# </remarks>
class SubsystemA
def a1; "Subsystem A, Method a1\n"; end
def a2; "Subsystem A, Method a2\n"; end
end
class SubsystemB
def b1; "Subsystem B, Method b1\n"; end
end
class SubsystemC
def c1; "Subsystem C, Method c1\n"; end
end
end
# <summary>
# Facade - фасад
# </summary>
# <remarks>
# <li>
# <lu>"знает", каким классами подсистемы адресовать запрос;</lu>
# <lu>делегирует запросы клиентам подходящим объектам внутри подсистемы;</lu>
# </li>
# </remarks>
class Facade
def initialize
@a = Library::SubsystemA.new;
@b = Library::SubsystemB.new;
@c = Library::SubsystemC.new;
end
def operation1
puts "Operation 1\n" +
@a.a1 +
@a.a2 +
@b.b1
end
def operation2
puts "Operation 2\n" +
@b.b1() +
@c.c1()
end
end
facade = Facade.new
facade.operation1
facade.operation2
# Wait for user
gets
VB.NET
правитьNamespace Library
'Класс подсистемы
' . реализует функциональность подсистемы
' . выполняет работу, порученную объектом Facade
' . ничего не "знает" о существовании фасада, то есть не хранит ссылок на него
Friend Class SubsystemA
Friend Function A1() As String
Return "Subsystem A, Method A1" & vbCrLf
End Function
Friend Function A2() As String
Return "Subsystem A, Method A2" & vbCrLf
End Function
End Class
Friend Class SubsystemB
Friend Function B1() As String
Return "Subsystem B, Method B1" & vbCrLf
End Function
End Class
Friend Class SubsystemC
Friend Function C1() As String
Return "Subsystem C, Method C1" & vbCrLf
End Function
End Class
End Namespace
'Facade - фасад
' . "знает", каким классами подсистемы адресовать запрос
' . делегирует запросы клиентов подходящим объектам внутри подсистемы
Public NotInheritable Class Facade
Private Sub New()
End Sub
Shared a As New Library.SubsystemA()
Shared b As New Library.SubsystemB()
Shared c As New Library.SubsystemC()
Public Shared Sub Operation1()
Console.WriteLine("Operation 1" & vbCrLf & a.A1() & a.A2() & b.B1())
End Sub
Public Shared Sub Operation2()
Console.WriteLine("Operation 2" & vbCrLf & b.B1() & c.C1())
End Sub
End Class
Class Program
Shared Sub Main()
Facade.Operation1()
Facade.Operation2()
'Ожидаем действия пользователя
Console.Read()
End Sub
End Class
Delphi
правитьprogram FacadePattern;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TComputer = class
public
procedure PlugIn;
procedure PowerMonitor;
procedure Power;
end;
procedure TComputer.PlugIn;
begin
WriteLn('Included in the network');
end;
procedure TComputer.PowerMonitor;
begin
WriteLn('Turn on the monitor');
end;
procedure TComputer.Power;
begin
WriteLn('Turn the system unit');
end;
type
TNotebook = class
procedure Power;
end;
procedure TNotebook.Power;
begin
WriteLn('Press the power button');
end;
type
TKettle = class
procedure PlugIn;
procedure Power;
end;
procedure TKettle.Power;
begin
WriteLn('Press the power button');
end;
procedure TKettle.PlugIn;
begin
WriteLn('Included in the network');
end;
type
TFacade = class
public
procedure PowerOn(aDevice: TObject);
end;
procedure TFacade.PowerOn(aDevice: TObject);
begin
if aDevice is TComputer then
with TComputer(aDevice) do
begin
PlugIn;
PowerMonitor;
Power;
end;
if aDevice is TNotebook then
with TNotebook(aDevice) do
Power;
if aDevice is TKettle then
with TKettle(aDevice) do
begin
PlugIn;
Power;
end;
WriteLn
end;
begin
with TFacade.Create do
try
PowerOn(TComputer.Create);
PowerOn(TNotebook.Create);
PowerOn(TKettle.Create);
finally
Free;
end;
ReadLn;
end.
Java
править/* Complex parts */
class CPU {
public void freeze() {
System.out.println("freeze");
}
public void jump(long position) {
System.out.println("jump position = " + position);
}
public void execute() {
System.out.println("execute");
}
}
class Memory {
public void load(long position, byte[] data) {
System.out.println("load position = " + position + ", data = " + data);
}
}
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("read lba = " + lba + ", size = " + size);
return new byte[size];
}
}
/* Facade */
class Computer {
private final static long BOOT_ADDRESS = 1L;
private final static long BOOT_SECTOR = 2L;
private final static int SECTOR_SIZE = 3;
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public Computer() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void startComputer() {
cpu.freeze();
memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
cpu.jump(BOOT_ADDRESS);
cpu.execute();
}
}
/* Client */
class Application {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startComputer();
}
}
Haxe
править/**
* Реализации отдельных частей компьютера.
* У каждого метода классов имеется какая-то реализация, в данном примере она опущена.
*/
/**
* Class CPU, отвечает за работу процессора
*/
class CPU {
public function new() {
}
public function freeze():Void {
//...
}
public function jump(position:Int):Void {
//...
}
public function execute():Void {
//...
}
}
/**
* Class Memory, отвечает за работу памяти
*/
class Memory {
public static inline var BOOT_ADDRESS:Int = 0x0005;
public function new() {
}
public function load(position:Int, data:haxe.io.Bytes):Void {
//...
}
}
/**
* Class HardDrive, отвечает за работу жёсткого диска
*/
class HardDrive {
public static inline var BOOT_SECTOR:Int = 0x001;
public static inline var SECTOR_SIZE:Int = 64;
public function new() {
}
public function read(lba:Int, size:Int):haxe.io.Bytes {
//...
return null;
}
}
/**
* Пример шаблона "Фасад"
* В качестве унифицированного объекта выступает Компьютер.
* За этим объектом будут скрыты, все детали работы его внутренних частей.
*/
class Computer {
private var cpu:CPU;
private var memory:Memory;
private var hardDrive:HardDrive;
/**
* Computer constructor.
* Инициализируем части
*/
public function new() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
/**
* Упрощённая обработка поведения "запуск компьютера"
*/
public function startComputer():Void {
cpu.freeze();
memory.load(
Memory.BOOT_ADDRESS,
hardDrive.read(HardDrive.BOOT_SECTOR, HardDrive.SECTOR_SIZE)
);
cpu.jump(Memory.BOOT_ADDRESS);
cpu.execute();
}
}
/**
* Пользователям компьютера предоставляется Фасад (компьютер),
* который скрывает все сложность работы с отдельными компонентами.
*/
class Application {
public static function main():Void {
var computer:Computer = new Computer();
computer.startComputer();
}
}
Swift
править// Logic
class CPU {
public func freeze() -> String {
return "Freezing processor."
}
public func jump(position: String) -> String {
return "Jumping to: \(position)"
}
public func execute() -> String {
return "Executing."
}
}
class Memory {
public func load(position: String, data: String) -> String {
return "Loading from \(position) data: \(data)"
}
}
class HardDrive {
public func read(lba: String, size: String) -> String {
return "Some data from sector \(lba) with size \(size)"
}
}
//Facade
class ComputerFacade {
private let cpu = CPU()
private let memory = Memory()
private let hardDrive = HardDrive()
public func start() {
cpu.freeze()
let ssd = hardDrive.read(lba: "100", size: "1024")
memory.load(position: "0x00", data: ssd)
cpu.jump(position: "0x00")
cpu.execute()
}
}
// Client
let pc = ComputerFacade()
pc.start()
Литература
править- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб.: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1. (также ISBN 5-272-00355-1)
Источники и ссылки
править- Паттерн Facade (фасад) — назначение, описание, особенности и реализация на C++.
- Паттерны для масштабируемых JavaScript-приложений. Глава 9. Паттерн «Фасад» — описание, пример реализации.