5. Интерфейсы и полиморфизм #
5.1 Что такое интерфейсы? #
В Go интерфейсы — это набор сигнатур методов, которые должен реализовать тип, чтобы соответствовать этому интерфейсу. Основное преимущество интерфейсов в Go — неявная реализация. Тип автоматически удовлетворяет интерфейсу, если реализует все его методы.
Основные свойства: #
- Интерфейсы описывают поведение, а не структуру.
- Любой тип, который реализует методы интерфейса, может быть использован там, где требуется этот интерфейс.
- Неявная реализация избавляет от необходимости явно указывать, что тип реализует интерфейс.
Пример интерфейса:
type Shape interface {
Area() float64
}
5.2 Реализация интерфейсов #
Для реализации интерфейса тип должен предоставить все его методы.
Пример:
type Circle struct {
Radius float64
}
// Метод для вычисления площади круга
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
type Rectangle struct {
Width, Height float64
}
// Метод для вычисления площади прямоугольника
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
Использование интерфейса:
func printArea(s Shape) {
fmt.Println("Площадь:", s.Area())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 4, Height: 6}
printArea(c) // Площадь: 78.5
printArea(r) // Площадь: 24
}
5.3 Полиморфизм через интерфейсы #
Полиморфизм позволяет работать с объектами разных типов через общий интерфейс.
Пример:
type Printer interface {
Print()
}
type Text struct {
Content string
}
func (t Text) Print() {
fmt.Println("Text:", t.Content)
}
type Image struct {
FileName string
}
func (i Image) Print() {
fmt.Println("Image:", i.FileName)
}
func printAll(items []Printer) {
for _, item := range items {
item.Print()
}
}
func main() {
text := Text{Content: "Hello, Go!"}
image := Image{FileName: "picture.jpg"}
items := []Printer{text, image}
printAll(items)
}
5.4 Интерфейсы в стандартной библиотеке #
Go активно использует интерфейсы в стандартной библиотеке.
Примеры интерфейсов: #
io.Reader
иio.Writer
: Для чтения и записи данных.func copyContent(src io.Reader, dst io.Writer) { io.Copy(dst, src) }
error
: Для представления ошибок.type error interface { Error() string }
Реализация интерфейса error
:
#
type MyError struct {
Message string
}
func (e MyError) Error() string {
return e.Message
}
func main() {
err := MyError{Message: "Something went wrong"}
fmt.Println(err.Error()) // Something went wrong
}
5.5 Пустой интерфейс (interface{}
)
#
Пустой интерфейс может содержать значение любого типа, так как все типы реализуют интерфейс без методов.
Использование пустого интерфейса: #
Хранение любых значений:
var data interface{} data = 42 fmt.Println(data) // 42 data = "Hello" fmt.Println(data) // Hello
Type assertion: Для извлечения значения из пустого интерфейса.
value, ok := data.(int) if ok { fmt.Println("Целое число:", value) } else { fmt.Println("Не целое число") }
Type switch: Для определения типа значения.
switch v := data.(type) { case int: fmt.Println("int:", v) case string: fmt.Println("string:", v) default: fmt.Println("Unknown type") }
5.6 Комбинирование интерфейсов #
Интерфейсы могут быть составными. Это позволяет определять поведение, комбинируя методы нескольких интерфейсов.
Пример:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter объединяет Reader и Writer
type ReadWriter interface {
Reader
Writer
}
5.7 Советы по работе с интерфейсами #
Используйте интерфейсы для абстракции: Разрабатывайте код, опираясь на интерфейсы, а не на конкретные типы.
Избегайте избыточности: Определяйте интерфейсы только с необходимыми методами.
Интерфейсы и тестирование: Интерфейсы упрощают создание тестов, позволяя подменять реальные реализации mock-объектами.
Пример с тестированием:
type Database interface {
Query(query string) string
}
// Mock для тестирования
type MockDB struct{}
func (db MockDB) Query(query string) string {
return "Mocked result"
}
func fetchData(db Database) string {
return db.Query("SELECT * FROM table")
}
func main() {
mock := MockDB{}
fmt.Println(fetchData(mock)) // Mocked result
}
5.8 Интерфейсы и дженерики #
С появлением дженериков (с версии Go 1.18) использование интерфейсов стало ещё мощнее.
Пример с ограничением типов через интерфейсы:
type Number interface {
int | float64
}
func Sum[T Number](a, b T) T {
return a + b
}
func main() {
fmt.Println(Sum(3, 5)) // 8
fmt.Println(Sum(2.5, 3.1)) // 5.6
}
Интерфейсы в Go обеспечивают гибкость и мощные возможности для построения архитектуры, не теряя при этом статической типизации.