![퍼사드](/images/patterns/cards/facade-mini.png?id=71ad6fa98b168c11cb3a1a9517dedf78)
스위프트로 작성된 퍼사드
퍼사드는 구조 디자인 패턴이며 간단한 (그러나 제한된 인터페이스를 클래스, 라이브러리 또는 프레임워크로 구성된 복잡한 시스템에 제공합니다.
퍼사드는 앱의 전반적인 복잡성을 줄이는 동시에 원치 않는 의존성들을 한 곳으로 옮기는 것을 돕습니다.
복잡도:
인기도:
사용 사례들: 퍼사드 패턴은 일반적으로 스위프트 코드로 작성된 앱에서 사용되며 또 복잡한 라이브러리 및 API와 작업할 때 특히 편리합니다.
식별: 퍼사드는 인터페이스가 간단하나 대부분의 작업을 다른 클래스들에 위임하는 클래스의 존재여부로 식별할 수 있으며 일반적으로 퍼사드들은 사용하는 객체들의 전체 수명 주기를 관리합니다.
개념적인 예시
이 예시는 퍼사드 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest
/// The Facade class provides a simple interface to the complex logic of one or
/// several subsystems. The Facade delegates the client requests to the
/// appropriate objects within the subsystem. The Facade is also responsible for
/// managing their lifecycle. All of this shields the client from the undesired
/// complexity of the subsystem.
class Facade {
private var subsystem1: Subsystem1
private var subsystem2: Subsystem2
/// Depending on your application's needs, you can provide the Facade with
/// existing subsystem objects or force the Facade to create them on its
/// own.
init(subsystem1: Subsystem1 = Subsystem1(),
subsystem2: Subsystem2 = Subsystem2()) {
self.subsystem1 = subsystem1
self.subsystem2 = subsystem2
}
/// The Facade's methods are convenient shortcuts to the sophisticated
/// functionality of the subsystems. However, clients get only to a fraction
/// of a subsystem's capabilities.
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
}
}
/// The Subsystem can accept requests either from the facade or client directly.
/// In any case, to the Subsystem, the Facade is yet another client, and it's
/// not a part of the Subsystem.
class Subsystem1 {
func operation1() -> String {
return "Sybsystem1: Ready!\n"
}
// ...
func operationN() -> String {
return "Sybsystem1: Go!\n"
}
}
/// Some facades can work with multiple subsystems at the same time.
class Subsystem2 {
func operation1() -> String {
return "Sybsystem2: Get ready!\n"
}
// ...
func operationZ() -> String {
return "Sybsystem2: Fire!\n"
}
}
/// The client code works with complex subsystems through a simple interface
/// provided by the Facade. When a facade manages the lifecycle of the
/// subsystem, the client might not even know about the existence of the
/// subsystem. This approach lets you keep the complexity under control.
class Client {
// ...
static func clientCode(facade: Facade) {
print(facade.operation())
}
// ...
}
/// Let's see how it all works together.
class FacadeConceptual: XCTestCase {
func testFacadeConceptual() {
/// The client code may have some of the subsystem's objects already
/// created. In this case, it might be worthwhile to initialize the
/// Facade with these objects instead of letting the Facade create new
/// instances.
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