Паттерн Цепочка Обязанностей

Цепочка обязанностей относится к поведенческим шаблонам проектирования.


Ганна Долбієва

Кинокомпания Джа-пикчерс сняла фильм документальный фильм про коммунистов-растаманов из Либерии под названием “Красная заря Марли”. Фильм очень долгий (8 часов), интересный, однако перед отправкой в прокат оказалось что в некоторых странах кадры и фразы из фильмы могут счесть ересью и не дать прокатной лицензии. Продюсеры киноленты решают вырезать моменты содержащие сомнительные фразы из фильма, в ручном и в автоматическом режиме. Двойная проверка нужна для того чтобы представителей прокатчика банально не расстреляли в некоторых странах, в случае ошибки при ручном отсмотре и монтаже.
Страны делятся на четыре группы – страны без цензуры, с умеренной, средней и очень строгой цензурой. Принимается решение использовать нейросети для классификации уровня ереси в отсматриевом фрагменте фильма. Для проекта закупаются очень дорогие state-of-art нейронки обученные на разные уровни цензуры, задача разработчика – разбить фильм на фрагменты и передавать их по цепочке нейросетей, от вольной до строгой, пока одна из них не обнаружит ересь, дальше фрагмент передается на ручной отсмотр для дальнейшего монтажа. Делать проход по всем нейронкам нельзя, т.к. на их работу затрачивается слишком много вычислительных мощностей (нам ведь еще за свет платить) достаточно остановиться на первой сработавшей.
Наивная имлементация псевдокодом:


import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

Вообщем и целом решение с массивом классификаторов не такое уж и плохое, Однако! представим что мы не можем создавать массив, нам доступна возможность создавать только одну сущность классификатора, который уже определяет тип цензуры для фрагмента фильма. Такие ограничения возможны при разработки библиотеки расширяющей функционал приложения (плагин).
Воспользуемся паттерном декоратор – добавим в класс классификатора референс на следующий классификатор в цепочке, будем останавливать процесс проверки на первой успешной классификации.
Таким образом мы реализуем паттерн Цепочка Обязанности:


import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

References

https://refactoring.guru/ru/design-patterns/chain-of-responsibility

Source Code

https://gitlab.com/demensdeum/patterns/