Вы увидели, как можно применить замыкания и карринг к функциональным шаблонам, теперь давайте посмотрим, что будет, если мы применим такие же методики к важному шаблону объектно-ориентированного проектирования. В обычном сценарии использования объектно-ориентированная система должна обходить набор объектов и выполнять с каждым из его элементов определенные действия.
В другом сценарии система будет обходить тот же набор, но выполнять другие действия. Обычно это можно сделать с помощью шаблонов разработки Visitor (см. раздел Ресурсы). ИнтерфейсVisitor
предоставляет протокол действия для обработки элемента коллекции. Отдельные подклассы определяют различные необходимые режимы работы. После этого вводится метод, который обходит коллекцию и применяет действие Visitor
к каждому элементу. Если вы еще не догадались, для достижения такого же результата можно использовать замыкания. Одна из привлекательных особенностей такого подхода состоит в том, что при использовании замыканий вам не нужно разрабатывать иерархию класса visitor. Более того, вы можете эффективно использовать композицию и наложение замыканий для определения действий и организации обхода коллекции!
Например, рассмотрим отношение "один ко многим" между классом Library
и классом Book
, которые используются для инвентаризации запасов библиотеки. Для реализации такого отношения можно использовать List
или Map
; однако у Map
есть преимущество в том, что она обеспечивает быстрый поиск, скажем, по заданному в качестве ключа номеру книги по каталогу.
В листинге 9 показано простое отношение "один ко многим", использующее Map
. Обратите внимание на два метода "display" класса Library
. Введение visitor делает оба этих метода кандидатами на реорганизацию с использованием visitor.
Листинг 9. Приложение библиотеки
class Book { |
В класс Library
, который имитирует использование visitor, показанный в листинге 10, включены несколько замыканий. Замыкание action
(чем-то похожее на замыкание map
) применяет замыкание action
к каждому элементу List
. Замыкание displayLoanedBook
отображает книгу в том случае, если она выдана читателю, а замыкание displayAvailableBook
выводит книгу, если она в хранилище. Оба действуют как visitor и связанные действия. Применение карринга к замыканию apply
с результатом displayLoanedBook
в замыкании displayLoanedBooks
, подготовленным для обработки коллекции книг. Похожая схема используется для обработки вывода книг, доступных для выдачи, как показано в листинге 10.
Листинг 10. Исправленный visitor библиотеки
import fp.*
class Book {
@Property title
@Property author
@Property catalogNumber
@Property onLoan = false
String toString() {
return " Title: ${title}; author: ${author}"
}
}
class Library {
@Property name
@Property stock = [ : ]
def addBook(title, author, catalogNumber) {
def bk = new Book(title : title, author :
author, catalogNumber : catalogNumber)
stock[catalogNumber] = bk
}
def lendBook(catalogNumber) {
stock[catalogNumber].onLoan = true
}
def displayBooksOnLoan() {
println "Library: ${name}"
println "Books on loan"
displayLoanedBooks(stock.values())
}
def displayBooksAvailableForLoan() {
println "Library: ${name}"
println "Books available for loan"
displayAvailableBooks(stock.values())
}
private displayLoanedBook = { bk -> if(bk.onLoan == true)
println bk }
private displayAvailableBook = { bk -> if(bk.onLoan == false)
println bk }
private displayLoanedBooks =
Functor.apply.curry(displayLoanedBook)
private displayAvailableBooks =
Functor.apply.curry(displayAvailableBook)
}
def lib = new Library(name : 'Napier')
lib.addBook('Groovy', 'KenB',
'CS123')
lib.addBook('Java', 'JohnS', 'CS456')
lib.addBook('UML', 'Ken and John',
'CS789')
lib.lendBook('CS123')
lib.displayBooksOnLoan() // Title: Groovy; author: KenB
lib.displayBooksAvailableForLoan() // Title: UML; author: Ken and John
// Title: Java; author: JohnS