Неявная реализация
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 26 из 107.
Во многих объектно-ориентированных языках, таких как Java или C#, необходимо явно объявить, что тип реализует интерфейс, используя такие ключевые слова, как implements. Go использует другой подход: реализация интерфейса является неявной.
Тип удовлетворяет интерфейсу, просто реализуя все его method. Нет необходимости где-либо объявлять эту связь. Компилятор выясняет это automatically.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter неявно реализует Writer
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Обратите внимание: в FileWriter нет объявления implements Writer. Поскольку в FileWriter есть метод Write(data string) int, соответствующий сигнатуре интерфейса, он автоматически удовлетворяет интерфейсу Writer.
У этого проектного решения есть важное следствие: вы можете определять интерфейсы уже после того, как типы существуют. Если в сторонней библиотеке есть тип с нужными вам методами, вы можете создать интерфейс, которому этот тип уже соответствует, не изменяя исходный код.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Работает, потому что FileWriter удовлетворяет Writer
}
Неявная реализация помогает сохранять код Go слабо связанным и гибким. Типам не нужно заранее знать об интерфейсах, благодаря чему проще составлять системы из независимых частей.
Задание
ЛегкоДавайте создадим систему доставки сообщений, которая демонстрирует, как типы неявно удовлетворяют интерфейсам в Go. Вы создадите разные типы messenger, которые все удовлетворяют одному и тому же интерфейсу, не объявляя явно об этой связи.
Вы организуете свой код в двух файлах:
messengers.go: Define интерфейсMessengerс одним методомSend(message string) string. Затем создайте две структуры, которые будут неявно удовлетворять этому интерфейсу:EmailMessengerс полемAddress(строка)SMSMessengerс полемPhoneNumber(строка)
Send, возвращающий строку с описанием доставки. Ни одна из структур не должна явно объявлять, что она реализуетMessenger: Go автоматически определит это на основе сигнатур методов.main.go: Создайте function с именемDeliverMessage, которая принимает любойMessengerи строку message, а затем возвращает результат вызоваSend. Считайте контактную информацию из ввода, создайте оба типа messenger и используйте свою function для доставки сообщений через каждый из них.
Будут предоставлены следующие входные данные:
- Строка 1: Email address
- Строка 2: Phone number
- Строка 3: Message to send
Ваши методы Send должны возвращать строки в следующих форматах:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Например, если заданы alice@example.com, 555-1234 и Hello!, результатом должен быть:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!Главная идея заключается в том, что ваша DeliverMessage function принимает любой Messenger, а и EmailMessenger, и SMSMessenger удовлетворяют этому интерфейсу просто благодаря наличию соответствующего метода Send. Ключевого слова implements нет нигде: неявная реализация в Go делает всё необходимое.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Создайте функцию DeliverMessage, которая принимает любой Messenger
// и строку сообщения, затем возвращает результат вызова Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Read email address
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Прочитать номер телефона
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Прочитать сообщение
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Создайте EmailMessenger с адресом электронной почты
// TODO: Создайте SMSMessenger с номером телефона
// TODO: Используйте DeliverMessage для отправки сообщения через каждый messenger
// и выведите результаты
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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 PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go