Шаблоны программирования на Swift: Prototype

Хотел бы представить на суд читателей новый раздел сайта — Паттерны проектирования в iOS программировании.

Wikipedia нам говорит:

Шаблон проектирования или паттерн (англ. design pattern) в разработке программного обеспечения — повторимая архитектурная конструкция, представляющая собой решение проблемы проектирования в рамках некоторого часто возникающего контекста.

Для того чтобы разобраться в шаблонах программирования или шаблонах проектирования (Design pattern) я взял книгу Дмитрия Малеева
«Хрестоматия iOS паттернов. На всякий…» и читая ее, переписывал код на Swift, пытался вникнуть во все тонкости и решить все проблемы, которые мне встречались.
Материал данного раздела не претендует на уникальность, да и что может быть уникального в распространенных шаблонах объектно ориентированного программирования. Этот материал я выкладываю сюда для собственного понимания, закрепления и для обсуждения читателями. Я — не истина в последней инстанции и всегда готов выслушать критику и исправить ошибки.

Prototypeprototype

Прототип – один из самых простых паттернов, который позволяет нам получить точную копию необходимого объекта. Тоесть использовать как прототип для нового объекта.

Когда использовать:
1. У нас есть семейство схожих объектов, разница между которыми только в состоянии их полей.
2. Чтобы создать объект вам надо пройти через огонь, воду и медные трубы. Особенно если этот объект состоит из еще одной кучи объектов, многие из которых для заполнения требуют подгрузку даных из базы, веб сервисов и тому подобных источников. Часто, легче скопировать объект и поменять несколько полей
3. Нам не важно как создается объект.
4. Нам страшно лень писать иерархию фабрик (читай дальше), которые будут инкапсулировать всю противную работу создания объекта

Иными словами если для создания объекта нужно затратить много ресурсов или выполнить кучу условий и требований, а объектов нам требуется много, то проще создать один прототип, затем копировать его и заменять значения свойств на необходимые.

И вот тут нужно разобрать понятия «поверхностное копирование» и «глубокое копирование». В переменной, которая содержит экземпляр класса на самом деле содержится указатель на блок памяти в куче, где расположен этот экземпляр. Поверхностное копирование – это просто создание нового указателя на те же самые байты в куче. То есть, в результате мы можем получить два объекта, которые указывают на одно и тоже значение.

Перейдем к примеру

//
// proSwift.ru
//

import Foundation

class Person: NSObject {
    var name = ""
    var surname = ""
}

 А теперь создадим два объекта нашего класса, поменяем значения свойств, выведем лог и посмотрим что же получится:

//
// proSwift.ru
//

let firstPerson = Person()
firstPerson.name = "Pavel"
firstPerson.surname = "Davidoff"
let secondPerson = firstPerson

print("First person name: \(firstPerson.name) and surname: \(firstPerson.surname)")

secondPerson.name = "Alex"
secondPerson.surname = "Black"

print("Second person name: \(secondPerson.name) and surname: \(secondPerson.surname)")
print("First person name: \(firstPerson.name) and surname: \(firstPerson.surname)")

Вывод лога в консоль:

First person name: Pavel and surname: Davidoff
Second person name: Alex and surname: Black
First person name: Alex and surname: Black

Думаю, понятно, что при создании второго указателя secondPerson, он сослался на тот же экземпляр что и firstPerson, и поэтому изменения свойств через один указатель повлияют и на другой.

Именно для создания копии объектов в памяти следует использовать глубокое копирование, которое в Swift реализовано протоколом NSCopying, и методом этого протокола

//
public func copyWithZone(zone: NSZone) -> AnyObject

Изменим реализация нашего класса, чтобы он соответствовал протоколу, и добавим инициализатор от объекта собственно типа.

//
// proSwift.ru
//

import Foundation

class Person: NSObject, NSCopying {
    var name = ""
    var surname = ""
    
    // это чтобы реализовать NSCopyng на Swift
    required override init() {
    }
    
    required init(_ person: Person) {
        self.name = person.name
        self.surname = person.surname
    }
    
    func copyWithZone(zone: NSZone) -> AnyObject {
        return self.dynamicType.init(self)
    }
}

Создаем третий и четвертый объекты, только для создания четвертого используем написанный ранее инициализатор.

//
// proSwift.ru
//

let thirdPerson = Person()
thirdPerson.name = "Pavel"
thirdPerson.surname = "Davidoff"

print("Third person name: \(thirdPerson.name) and surname: \(thirdPerson.surname)")

let fourthPerson = Person(thirdPerson)
fourthPerson.name = "Alex"
fourthPerson.surname = "Black"

print("Fourth person name: \(fourthPerson.name) and surname: \(fourthPerson.surname)")
print("Third person name: \(thirdPerson.name) and surname: \(thirdPerson.surname)")

Вполне ожидаемый лог в консоле:

Third person name: Pavel and surname: Davidoff
Fourth person name: Alex and surname: Black
Third person name: Pavel and surname: Davidoff

И последнее, что нужно сказать про NSCopyng. Ведь мы нигде не использовали метод этого протокола. Это потому, что NSZone больше не используется в Swift  да и в Objective-C в течение длительного времени. И передающийся в этот метод аргумент игнорируется. Этот метод существует по историческим причинам.

Ну и напоследок приведу пример использования шаблона программирования Prototype, реализованного без использования протокола NSCopyng, а лишь преимущества языка программирования Swift. Как было сказано выше — шаблон прототип используется для создания экземпляра нового объекта путем копирования всех свойств существующего объекта,  создание независимого клона. Эта практика особенно полезна , когда создание нового объекта является неэффективным.

//
// proSwift.ru
//
// https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/creational/prototype.swift

class ChungasRevengeDisplay {
    var name: String?
    let font: String

    init(font: String) {
        self.font = font
    }

    func clone() -> ChungasRevengeDisplay {
        return ChungasRevengeDisplay(font:self.font)
    }
}

let Prototype = ChungasRevengeDisplay(font:"GotanProject")

let Philippe = Prototype.clone()
Philippe.name = "Philippe"

let Christoph = Prototype.clone()
Christoph.name = "Christoph"

let Eduardo = Prototype.clone()
Eduardo.name = "Eduardo"


В следующей стате мы рассмотрим шаблоны программирования Factory:Шаблоны программирования на Swift: Factory

Добавить комментарий

Ваш адрес email не будет опубликован.

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.