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

Chain_of_responsibility_proSwift_ru

Давайте разберем шаблон программирования Цепочка ответственности (Chain of responsibility)  на Swift в среде разработки XCode

Chain of responsibility

Представьте себе очередь людей которые пришли за посылками. Выдающий посылки человек, дает первую посылку первому в очереди человеку, он смотрит на имя-фамилию на корбке, видит что посылка не для него, и передает посылку дальше. Второй человек делает собственно тоже самое, и так пока не найдется получатель.

Цепочка ответственности (chain of responsibility) – позволяет вам передавать объект по цепочке объектов-обработчиков, пока не будет найден необходимый объект обработчик.

Когда использовать этот паттерн:

  1. У вас более чем один объект — обработчик.
  2. У вас есть несколько объектов обработчика, при этом вы не хотите специфицировать который объект должен обрабатывать в даный момент времени.

Как всегда – пример:

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

//
// proSwift.ru
//

class BasicItem {
}

class Toy: BasicItem {
}

class Electronics :BasicItem {
}

class Trash: BasicItem {
}

Теперь создадим обработчики:

//
// proSwift.ru
//

protocol BasicHandler {
    var nextHandler: BasicHandler? {get set}
    func handleItem(item: BasicItem)
}

Наш базовый обработчик умеет обрабатывать объекты типа BasicItem. И самое важное – он может иметь ссылку на следующий обработчик (как люди в нашей очереди, которые передают посылку).

Давайте создадим код обработчика игрушки:

//
// proSwift.ru
//

class ToysHandler: BasicHandler {
    
    var nextHandler: BasicHandler?
    
    required init(aHandler: BasicHandler) {
        self.nextHandler = aHandler
    }
    
    func handleItem(item: BasicItem) {
        if item is Toy {
            print("Нашли игрушку. Обрабатываем")
        } else {
            print("Это не игрушка, передаем на обработку следующему")
            
            self.nextHandler!.handleItem(item)
        }
    }
}

Если обработчик получает объект класса Toy – то он его обрабатывает, если нет – то обработчик передает объект следующему обработчику. По аналогии создадим два следующих обработчика: для электроники, и мусора:

//
// proSwift.ru
//

class ElectronicHandler: BasicHandler {
    
    var nextHandler: BasicHandler?
    
    required init(aHandler: BasicHandler) {
        self.nextHandler = aHandler
    }
    
    func handleItem(item: BasicItem) {
        if item is Electronics {
            print("Нашли электонику. Обрабатываем")
        } else {
            print("Это не электроника, передаем на обработку следующему")
            self.nextHandler!.handleItem(item)
        }
    }
}

class OtherItemsHandler: BasicHandler {
    
    var nextHandler: BasicHandler?
    
    func handleItem(item: BasicItem) {
        print("Нашли неопознанный объект. Уничтожаем его")
    }
}

Как видим OtherItemsHandler в случае, когда до него дошло дело – объект уничтожает, и не пробует дергать следующий обработчик (последний человек в очереди не проверяет получателя — его функция просто выбросить посылку, если она попала ему в руки).
Давайте тестировать:

//
// proSwift.ru
//

let otherItemsHandler = OtherItemsHandler()
let electronicItemsHandler = ElectronicHandler(aHandler: otherItemsHandler)
let toysItemsHandler = ToysHandler(aHandler: electronicItemsHandler)

let toy = Toy()
let el = Electronics()
let trash = Trash()

toysItemsHandler.handleItem(toy)
print("- - - - -")
toysItemsHandler.handleItem(el)
print("- - - - -")
toysItemsHandler.handleItem(trash)

Мы в начале создаем обработчик мусора, т.к. все предыдущие обработчики инициализируются с ссылкой на предыдущий обработчик. Таким образом все три обработчика скреплены в цепь и ссылаются один на другой, кроме последнего. Его свойство nexhtHandler = nil. Далее создаем объекты и  пытаемся обработать эти различные созданные элементы. Традиционно лог:

Нашли игрушку. Обрабатываем
- - - - -
Это не игрушка, передаем на обработку следующему
Нашли электонику. Обрабатываем
- - - - -
Это не игрушка, передаем на обработку следующему
Это не электроника, передаем на обработку следующему
Нашли неопознанный объект. Уничтожаем его

Пример с GitHub

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

В примере ниже решается задача для банкомата. В нем есть пачки купюр, определенного достоинства, и нужно решить сможет ли банкомат выдать запрошенную сумму, имея в наличии пачки купюр с определенным количеством и достоинством в этой пачке.

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

class MoneyPile {
    let value: Int // достоинтсво купюр в пачке
    var quantity: Int // количество купюр в пачке
    var nextPile: MoneyPile? // ссылка на следующую пачку
    
    init(value: Int, quantity: Int, nextPile: MoneyPile?){
        self.value = value
        self.quantity = quantity
        self.nextPile = nextPile
    }
    
    // метод проверки - сможем ли мы выдать нужную сумму используя текущщую пачку
    func canWithdraw(v: Int) -> Bool {
        var v = v
        
        func canTakeSomeBill(want: Int) -> Bool {
            return (want / self.value) > 0
        }
        
        var q = self.quantity
        
        while canTakeSomeBill(v) {
            if q == 0 {
                break
            }
            v = v - self.value
            q = q - 1
        }
        if v == 0 {
            return true // справились с выдачей текущей пачкой
        } else if let next = self.nextPile { //а вот если нет, то пробуем выдать купюрами следющей пачки
            return next.canWithdraw(v)
        }
        
        return false // никак недьзя выдать нужную сумму
    }
    
}


class ATM {
    private var hundred: MoneyPile
    private var fifty: MoneyPile
    private var twenty: MoneyPile
    private var ten: MoneyPile
    
    private var startPile: MoneyPile {
        return self.hundred
    }
    
    init(hundred: MoneyPile,
         fifty: MoneyPile,
         twenty: MoneyPile,
         ten: MoneyPile) {
        
        self.hundred = hundred
        self.fifty = fifty
        self.twenty = twenty
        self.ten = ten
    }
    
    func canWithdraw(value: Int) -> String {
        return "Can withdraw: \(self.startPile.canWithdraw(value))"
    }
}

// Создаем пачки денег от меньшей купюры к большей 10 < 20 < 50 < 100.**
let ten = MoneyPile(value: 10, quantity: 6, nextPile: nil)
let twenty = MoneyPile(value: 20, quantity: 2, nextPile: ten)
let fifty = MoneyPile(value: 50, quantity: 2, nextPile: twenty)
let hundred = MoneyPile(value: 100, quantity: 1, nextPile: fifty)

// Создаем банкомат с пачками денег, которые создали ранее
var atm = ATM(hundred: hundred, fifty: fifty, twenty: twenty, ten: ten)
atm.canWithdraw(310) // Не сможет выдать сумму 310, т.к. банкомат имеет только 300
atm.canWithdraw(100) // Может выдать - 1x100
atm.canWithdraw(165) // Не сможет выдать сумму 165, т.к. банкомат не имеет купюр по 5
atm.canWithdraw(30)  // Может выдать - 1x20, 2x10


Метки:
Один комментарий на “Шаблоны программирования на Swift: Chain of responsibility
  1. fix_bot:

    >> atm.canWithdraw(30) // Может выдать — 1×20, 2×10

    30: (1×20 & 1×10) || (3×10)

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*

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