Прототип — это порождающий паттерн, который позволяет копировать объекты любой сложности без привязки к их конкретным классам.
Все классы—Прототипы имеют общий интерфейс. Поэтому вы можете копировать объекты, не обращая внимания на их конкретные типы и всегда быть уверены, что получите точную копию. Клонирование совершается самим объектом-прототипом, что позволяет ему скопировать значения всех полей, даже приватных.
Концептуальный пример
Этот пример показывает структуру паттерна Прототип , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest
/// Swift имеет встроенную поддержку клонирования. Чтобы сделать класс
/// клонируемым, вам нужно реализовать в нём протокол NSCopying, а именно метод
/// `copy`.
class BaseClass: NSCopying, Equatable {
private var intValue = 1
private var stringValue = "Value"
required init(intValue: Int = 1, stringValue: String = "Value") {
self.intValue = intValue
self.stringValue = stringValue
}
/// MARK: - NSCopying
func copy(with zone: NSZone? = nil) -> Any {
let prototype = type(of: self).init()
prototype.intValue = intValue
prototype.stringValue = stringValue
print("Values defined in BaseClass have been cloned!")
return prototype
}
/// MARK: - Equatable
static func == (lhs: BaseClass, rhs: BaseClass) -> Bool {
return lhs.intValue == rhs.intValue && lhs.stringValue == rhs.stringValue
}
}
/// Подклассы могет переопределять базовый метод `copy`, чтобы дополнительно
/// скопировать данные собственного класса. Но в этом случае всегда сперва
/// вызывайте родительскую реализацию метод копирования.
class SubClass: BaseClass {
private var boolValue = true
func copy() -> Any {
return copy(with: nil)
}
override func copy(with zone: NSZone?) -> Any {
guard let prototype = super.copy(with: zone) as? SubClass else {
return SubClass() // oops
}
prototype.boolValue = boolValue
print("Values defined in SubClass have been cloned!")
return prototype
}
}
/// Клиентский код.
class Client {
// ...
static func someClientCode() {
let original = SubClass(intValue: 2, stringValue: "Value2")
guard let copy = original.copy() as? SubClass else {
XCTAssert(false)
return
}
/// См. реализацию протокола `Equatable`.
XCTAssert(copy == original)
print("The original object is equal to the copied object!")
}
// ...
}
/// Давайте посмотрим как всё это будет работать.
class PrototypeConceptual: XCTestCase {
func testPrototype_NSCopying() {
Client.someClientCode()
}
}
Output.txt: Результат выполнения
Values defined in BaseClass have been cloned!
Values defined in SubClass have been cloned!
The original object is equal to the copied object!
Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest
class PrototypeRealWorld: XCTestCase {
func testPrototypeRealWorld() {
let author = Author(id: 10, username: "Ivan_83")
let page = Page(title: "My First Page", contents: "Hello world!", author: author)
page.add(comment: Comment(message: "Keep it up!"))
/// Since NSCopying returns Any, the copied object should be unwrapped.
guard let anotherPage = page.copy() as? Page else {
XCTFail("Page was not copied")
return
}
/// Comments should be empty as it is a new page.
XCTAssert(anotherPage.comments.isEmpty)
/// Note that the author is now referencing two objects.
XCTAssert(author.pagesCount == 2)
print("Original title: " + page.title)
print("Copied title: " + anotherPage.title)
print("Count of pages: " + String(author.pagesCount))
}
}
private class Author {
private var id: Int
private var username: String
private var pages = [Page]()
init(id: Int, username: String) {
self.id = id
self.username = username
}
func add(page: Page) {
pages.append(page)
}
var pagesCount: Int {
return pages.count
}
}
private class Page: NSCopying {
private(set) var title: String
private(set) var contents: String
private weak var author: Author?
private(set) var comments = [Comment]()
init(title: String, contents: String, author: Author?) {
self.title = title
self.contents = contents
self.author = author
author?.add(page: self)
}
func add(comment: Comment) {
comments.append(comment)
}
/// MARK: - NSCopying
func copy(with zone: NSZone? = nil) -> Any {
return Page(title: "Copy of '" + title + "'", contents: contents, author: author)
}
}
private struct Comment {
let date = Date()
let message: String
}
Output.txt: Результат выполнения
Original title: My First Page
Copied title: Copy of 'My First Page'
Count of pages: 2