Цепочки вызовов методов
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 81 из 107.
Method chaining — это техника, при которой методы возвращают получатель, что позволяет связывать несколько вызовов методов в одном операторе. Вы уже видели это в шаблоне Builder, но это самостоятельная концепция, полезная во многих контекстах.
key — это возврат указателя на структуру из каждого метода:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Теперь вы можете chain эти вызовы fluently:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)По сравнению с отдельными инструкциями это читается почти как естественный язык. Цепочка вызовов методов лучше всего работает, когда операции настраивают или изменяют объект, не возвращая другие значения. Она часто используется в query builders, API конфигурации и библиотеках утверждений для тестов.
Помни, что each метод должен использовать получатель-указатель и возвращать *StructType, чтобы chain работала правильно.
Задание
ЛегкоДавайте создадим fluent builder HTML-элементов, использующий цепочку методов для конструирования HTML-тегов с атрибутами и содержимым! Это практическое применение цепочки методов: вы создадите API, который при создании HTML-элементов читается почти как естественный язык.
Вы организуете код в двух файлах:
element.go: Define builder HTML-элементов с методами, поддерживающими chaining.Create структуру
Element, которая хранит имя тега, map атрибутов (string в string) и inner content. Build следующие методы, поддерживающие chaining, каждый из которых возвращает*Element:NewElement(tag string)— конструктор, который creates новый Element с заданным именем тега и initialized (empty) map атрибутовAttr(key, value string)— adds атрибут к элементуID(id string)— convenience метод, который устанавливает атрибут "id"Class(class string)— convenience метод, который устанавливает атрибут "class"Content(text string)— устанавливает inner content элемента
Добавьте метод
Render(), который возвращает строку HTML. Атрибуты должны appear в alphabetical порядке по key. Формат:<tag attr1="value1" attr2="value2">content</tag>Если атрибутов нет, просто выведите
<tag>content</tag>. Если content отсутствует, выведите<tag attr="value"></tag>.main.go: Build HTML-элементы с помощью chaining методов.Прочитайте имя тега, затем прочитайте количество операций, которые нужно выполнить. Каждая операция представляет собой один из вариантов:
id, за которым следует значение IDclass, за которым следует значение classattr, за которым следуют имя и значение атрибута (две строки)content, за которым следует текст content
Соедините все операции в цепочку fluently, затем выведите HTML после render.
Будут предоставлены следующие входные данные:
- Строка 1: имя тега
- Строка 2: количество операций
- Следующие строки: тип операции и значение(я)
Например, для входных данных:
div
3
id
container
class
main-content
content
Hello WorldВаш вывод должен быть таким:
<div class="main-content" id="container">Hello World</div>А для входных данных:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click hereВаш вывод должен быть таким:
<a href="https://example.com" target="_blank">Click here</a>А для входных данных:
input
2
attr
type
text
attr
placeholder
Enter nameВаш вывод должен быть таким:
<input placeholder="Enter name" type="text"></input>А для входных данных:
p
1
content
Simple paragraphВаш вывод должен быть таким:
<p>Simple paragraph</p>Обратите внимание, как chaining методов позволяет пошагово создавать элементы в удобном для чтения виде. Каждый метод изменяет элемент и возвращает его, позволяя напрямую присоединить к цепочке следующий вызов метода.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Прочитать имя тега
scanner.Scan()
tagName := scanner.Text()
// Прочитать количество операций
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Создать элемент
element := NewElement(tagName)
// Обработать каждую операцию
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: В зависимости от типа операции вызвать соответствующий метод в цепочке
// Операции могут быть: "id", "class", "attr" или "content"
// - Для "id": прочитать следующую строку как значение ID
// - Для "class": прочитать следующую строку как значение class
// - Для "attr": прочитать следующие ДВЕ строки (имя атрибута, затем значение)
// - Для "content": прочитать следующую строку как текст содержимого
// Использовать цепочку методов для плавного построения элемента
_ = operation // Удалить эту строку при реализации
}
// Вывести отрендеренный HTML
fmt.Println(element.Render())
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool12Продвинутые концепции ООП
Паттерн функциональных опцийПаттерн Builder в GoЦепочки вызовов методовПсевдонимы и определения типовОсновы рефлексииОбзор генерации кодаПотренируйтесь самостоятельно: Онлайн-компилятор Go