Фасад — это структурный паттерн, который предоставляет простой (но урезанный) интерфейс к сложной системе объектов, библиотеке или фреймворку.
Кроме того, что Фасад позволяет снизить общую сложность программы, он также помогает вынести код, зависимый от внешней системы в единственное место.
Концептуальный пример
Этот пример показывает структуру паттерна Фасад , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest
/// Класс Фасада предоставляет простой интерфейс для сложной логики одной или
/// нескольких подсистем. Фасад делегирует запросы клиентов соответствующим
/// объектам внутри подсистемы. Фасад также отвечает за управление их жизненным
/// циклом. Все это защищает клиента от нежелательной сложности подсистемы.
class Facade {
private var subsystem1: Subsystem1
private var subsystem2: Subsystem2
/// В зависимости от потребностей вашего приложения вы можете предоставить
/// Фасаду существующие объекты подсистемы или заставить Фасад создать их
/// самостоятельно.
init(subsystem1: Subsystem1 = Subsystem1(),
subsystem2: Subsystem2 = Subsystem2()) {
self.subsystem1 = subsystem1
self.subsystem2 = subsystem2
}
/// Методы Фасада удобны для быстрого доступа к сложной функциональности
/// подсистем. Однако клиенты получают только часть возможностей подсистемы.
func operation() -> String {
var result = "Facade initializes subsystems:"
result += " " + subsystem1.operation1()
result += " " + subsystem2.operation1()
result += "\n" + "Facade orders subsystems to perform the action:\n"
result += " " + subsystem1.operationN()
result += " " + subsystem2.operationZ()
return result
}
}
/// Подсистема может принимать запросы либо от фасада, либо от клиента напрямую.
/// В любом случае, для Подсистемы Фасад – это еще один клиент, и он не
/// является частью Подсистемы.
class Subsystem1 {
func operation1() -> String {
return "Sybsystem1: Ready!\n"
}
// ...
func operationN() -> String {
return "Sybsystem1: Go!\n"
}
}
/// Некоторые фасады могут работать с разными подсистемами одновременно.
class Subsystem2 {
func operation1() -> String {
return "Sybsystem2: Get ready!\n"
}
// ...
func operationZ() -> String {
return "Sybsystem2: Fire!\n"
}
}
/// Клиентский код работает со сложными подсистемами через простой интерфейс,
/// предоставляемый Фасадом. Когда фасад управляет жизненным циклом подсистемы,
/// клиент может даже не знать о существовании подсистемы. Такой подход
/// позволяет держать сложность под контролем.
class Client {
// ...
static func clientCode(facade: Facade) {
print(facade.operation())
}
// ...
}
/// Давайте посмотрим как всё это будет работать.
class FacadeConceptual: XCTestCase {
func testFacadeConceptual() {
/// В клиентском коде могут быть уже созданы некоторые объекты
/// подсистемы. В этом случае может оказаться целесообразным
/// инициализировать Фасад с этими объектами вместо того, чтобы
/// позволить Фасаду создавать новые экземпляры.
let subsystem1 = Subsystem1()
let subsystem2 = Subsystem2()
let facade = Facade(subsystem1: subsystem1, subsystem2: subsystem2)
Client.clientCode(facade: facade)
}
}
Output.txt: Результат выполнения
Facade initializes subsystems: Sybsystem1: Ready!
Sybsystem2: Get ready!
Facade orders subsystems to perform the action:
Sybsystem1: Go!
Sybsystem2: Fire!
Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest
/// Facade Design Pattern
///
/// Intent: Provides a simplified interface to a library, a framework, or any
/// other complex set of classes.
class FacadeRealWorld: XCTestCase {
/// In the real project, you probably will use third-party libraries. For
/// instance, to download images.
///
/// Therefore, facade and wrapping it is a good way to use a third party API
/// in the client code. Even if it is your own library that is connected to
/// a project.
///
/// The benefits here are:
///
/// 1) If you need to change a current image downloader it should be done
/// only in the one place of a project. A number of lines of the client code
/// will stay work.
///
/// 2) The facade provides an access to a fraction of a functionality that
/// fits most client needs. Moreover, it can set frequently used or default
/// parameters.
func testFacedeRealWorld() {
let imageView = UIImageView()
print("Let's set an image for the image view")
clientCode(imageView)
print("Image has been set")
XCTAssert(imageView.image != nil)
}
fileprivate func clientCode(_ imageView: UIImageView) {
let url = URL(string: "www.example.com/logo")
imageView.downloadImage(at: url)
}
}
private extension UIImageView {
/// This extension plays a facede role.
func downloadImage(at url: URL?) {
print("Start downloading...")
let placeholder = UIImage(named: "placeholder")
ImageDownloader().loadImage(at: url,
placeholder: placeholder,
completion: { image, error in
print("Handle an image...")
/// Crop, cache, apply filters, whatever...
self.image = image
})
}
}
private class ImageDownloader {
/// Third party library or your own solution (subsystem)
typealias Completion = (UIImage, Error?) -> ()
typealias Progress = (Int, Int) -> ()
func loadImage(at url: URL?,
placeholder: UIImage? = nil,
progress: Progress? = nil,
completion: Completion) {
/// ... Set up a network stack
/// ... Downloading an image
/// ...
completion(UIImage(), nil)
}
}
Output.txt: Результат выполнения
Let's set an image for the image view
Start downloading...
Handle an image...
Image has been set