Menu
Coddy logo textTech

Неявная реализация

Часть раздела Объектно-ориентированное программирование путешествия по 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 слабосвязанным и гибким. Типам не нужно знать об интерфейсах заранее, что упрощает построение систем из независимых частей.

challenge icon

Задание

Легко

Давайте создадим систему доставки сообщений, которая демонстрирует, как типы неявно удовлетворяют интерфейсам в 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 берет все на себя.

Шпаргалка

В 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)
}

Здесь нет объявления 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
}

Такой подход позволяет определять интерфейсы уже после создания типов, что делает код слабосвязанным и гибким.

Попробуйте сами

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 для отправки сообщения через каждый мессенджер
	// и выведите результаты
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование