Неявная реализация
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 26 из 107.
Во многих объектно-ориентированных языках, таких как Java или C#, вы должны явно объявить, что тип реализует интерфейс, используя такие ключевые слова, как implements. Go использует другой подход: реализация интерфейса является неявной.
Тип удовлетворяет интерфейсу, просто реализуя все его методы. Нет необходимости объявлять эту связь где-либо. Компилятор определяет это автоматически.
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. Вы создадите различные типы мессенджеров, которые удовлетворяют одному и тому же интерфейсу — без явного объявления этой связи.
Вы организуете свой код в двух файлах:
messengers.go: Определите интерфейсMessengerс единственным методомSend(message string) string. Затем создайте две структуры, которые будут неявно удовлетворять этому интерфейсу:EmailMessengerс полемAddress(string)SMSMessengerс полемPhoneNumber(string)
Send, который возвращает строку, описывающую доставку. Ни одна из структур не должна явно объявлять, что она реализуетMessenger— Go определит это автоматически на основе сигнатур методов.main.go: Создайте функцию с именемDeliverMessage, которая принимает любойMessengerи строку сообщения, а затем возвращает результат вызоваSend. Считайте контактную информацию из входных данных, создайте оба типа мессенджеров и используйте свою функцию для доставки сообщений через каждый из них.
Будут предоставлены следующие входные данные:
- Строка 1: Email адрес
- Строка 2: Номер телефона
- Строка 3: Сообщение для отправки
Ваши методы 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 принимает любой Messenger, и как EmailMessenger, так и SMSMessenger удовлетворяют этому интерфейсу просто за счет наличия соответствующего метода Send. Здесь нигде нет ключевого слова implements — неявная реализация в Go берет все на себя.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Создайте функцию DeliverMessage, которая принимает любой Messenger
// и строку сообщения, затем возвращает результат вызова Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Чтение адреса электронной почты
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 для отправки сообщения через каждый мессенджер
// и выведите результаты
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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