Объектно-ориентированное программирование в Go

Go
Объектно-ориентированное программирование в Go

Серия "Learn Go Language" - 26-я статья, в которой можно поделиться хорошей статьей

Сегодня я поделюсь с вами хорошей статьей об объектно-ориентированном Go. Первоначальный автор — Уильям Кеннеди, автор блога Go Language in Action.www.ardanlabs.com/blog/хранитель . Большинство китайских сусликов познакомились с Go God через этот блог.

Некоторые предложения, которые не имеют ничего общего с очками знаний, немного неудобно переводить, и все понимают их с первого взгляда. Однако те, кто имеет отношение к очкам знаний, будут стараться быть верными исходному тексту. Кроме того, статья сделана в простой верстке для удобства чтения. Уровень перевода ограничен. Если есть какая-либо ошибка, пожалуйста, оставьте сообщение ниже, чтобы исправить ее.

База

Кто-то спросил меня сегодня на форуме, как получить преимущества наследования без встраивания. Важно то, что все должны думать о языке Go, а не о языке, который они оставили позади. Я не могу сказать вам, сколько кода я удалил из более ранних реализаций Go, потому что это было ненужно. Разработчики языков обладают многолетним опытом и знаниями и помогают создавать язык, который является быстрым, компактным и очень интересным для программирования.

Я считаю Go легким объектно-ориентированным языком программирования. Да, у него есть инкапсуляция и функции-члены типов, но ему не хватает наследования и, следовательно, традиционного полиморфизма. Для меня наследование бесполезно, если вы не хотите добиться полиморфизма. Благодаря тому, как интерфейсы реализованы в Go, наследование не требуется. Go берет лучшие части ООП и игнорирует другие, предоставляя нам лучший способ написания полиморфного кода.

Ниже приведен краткий обзор ООП в Go. Начнем с этих трех структур.

type Animal struct {
    Name string
    mean bool
}

type Cat struct {
    Basics Animal
    MeowStrength int
}

type Dog struct {
    Animal
    BarkStrength int
}

В любом примере ООП вы, вероятно, увидите три приведенные выше структуры. Базовая структура Animal и структуры Cat и Dog, объявленные на основе Animal . Структура Animal обладает свойствами, общими для всех животных.

За исключением среднего члена, все остальные члены общедоступны и доступны извне. Средний член структуры Animal начинается со строчной буквы. В Go регистр первой буквы переменных, структур, членов, функций и т. д. определяет доступ. Начало заглавной буквы указывает на то, что оно является общедоступным и может вызываться извне; начало строчной буквы указывает на то, что оно является частным и не может вызываться извне.

Поскольку в Go нет наследования, единственным вариантом является композиция. Одним из членов структуры Cat является Basics, а тип — Animal. Структура Dog анонимно встраивает структуру Animal. Какая реализация лучше решать вам, я покажу обе реализации.

Создайте отдельные методы для структур Cat и Dog:

func (dog *Dog) MakeNoise() {
    barkStrength := dog.BarkStrength

    if dog.mean == true {
        barkStrength = barkStrength * 5
    }

    for bark := 0; bark < barkStrength; bark++ {
        fmt.Printf("BARK ")
    }

    fmt.Println("")
}

func (cat *Cat) MakeNoise() {
    meowStrength := cat.MeowStrength

    if cat.Basics.mean == true {
        meowStrength = meowStrength * 5
    }

    for meow := 0; meow < meowStrength; meow++ {
        fmt.Printf("MEOW ")
    }

    fmt.Println("")
}

Реализуйте соответствующий метод MakeNoise() с приемником указателя. Эти два метода делают одно и то же, если среднее значение верно, каждое животное будет говорить на своем родном языке в зависимости от интенсивности лая или мяуканья. Это скучно, но показывает, как получить доступ к указанному объекту.

Мы можем использовать ссылку Dog для прямого вызова членов структуры Animal, в то время как Cat должен обращаться к членам Animal через элемент Basics.

До сих пор мы обсуждали инкапсуляцию, композицию, спецификации доступа и функции-члены, а остальное — как создать полиморфное поведение.

Полиморфизм достигается через интерфейсы:

type AnimalSounder interface {
    MakeNoise()
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

В приведенном выше коде мы добавляем интерфейс AnimalSounder и публичную функцию MakeSomeNoise(), которая принимает значение типа интерфейса. По сути, функция будет ссылаться на значение типа, реализующего этот интерфейс. Интерфейс — это не тип, который может быть создан, это объявление поведения, и другие типы могут реализовать поведение, объявленное интерфейсом.

В Го,Любой тип, который реализует интерфейс через методы, представляет тип интерфейса.. В нашем примере структуры Dog и Cat реализуют интерфейс AnimalSounder с приемниками указателей, поэтому их можно рассматривать как типы AnimalSounder.

Это означает, что указатели Dog и Cat могут быть переданы в качестве параметров функции MakeSomeNoise(). Функция MakeSomeNoise() реализует полиморфное поведение через интерфейс AnimalSounder.

Посмотреть полный код

Передовой

Если вы хотите уменьшить дублирование кода в методах MakeNoise() Cat и Dog, вы можете создать метод для типа Animal, чтобы справиться с этим:

func (animal *Animal) PerformNoise(strength int, sound string) {
    if animal.mean == true {
        strength = strength * 5
    }

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Println("")
}

func (dog *Dog) MakeNoise() {
    dog.PerformNoise(dog.BarkStrength, "BARK")
}

func (cat *Cat) MakeNoise() {
    cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}

Теперь тип Animal имеет метод обработки шума, который может быть вызван его внешними типами, такими как типы Dog, Cat. В качестве бонуса нам не нужно передавать средний элемент в качестве параметра, потому что он принадлежит структуре Animal. Вот полный код:

package main

import (
    "fmt"
)

type Animal struct {
    Name string
    mean bool
}

type AnimalSounder interface {
    MakeNoise()
}

type Dog struct {
    Animal
    BarkStrength int
}

type Cat struct {
    Basics Animal
    MeowStrength int
}

func main() {
    myDog := &Dog{
        Animal{
           "Rover", // Name
           false,   // mean
        },
        2, // BarkStrength
    }

    myCat := &Cat{
        Basics: Animal{
            Name: "Julius",
            mean: true,
        },
        MeowStrength: 3,
    }

    MakeSomeNoise(myDog)
    MakeSomeNoise(myCat)
}

func (animal *Animal) PerformNoise(strength int, sound string) {
    if animal.mean == true {
        strength = strength * 5
    }

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Println("")
}

func (dog *Dog) MakeNoise() {
    dog.PerformNoise(dog.BarkStrength, "BARK")
}

func (cat *Cat) MakeNoise() {
    cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

вывод:

BARK BARK
MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW

Встраивание интерфейса в структуру

Кто-то на заднем плане привел пример встраивания интерфейса в структуру:

package main

import (
    "fmt"
)

type HornSounder interface {
    SoundHorn()
}

type Vehicle struct {
    List [2]HornSounder
}

type Car struct {
    Sound string
}

type Bike struct {
   Sound string
}

func main() {
    vehicle := new(Vehicle)
    vehicle.List[0] = &Car{"BEEP"}
    vehicle.List[1] = &Bike{"RING"}

    for _, hornSounder := range vehicle.List {
        hornSounder.SoundHorn()
        // PressHorn(hornSounder) 这种方式也可以
    }
}

func (car *Car) SoundHorn() {
    fmt.Println(car.Sound)
}

func (bike *Bike) SoundHorn() {
    fmt.Println(bike.Sound)
}

func PressHorn(hornSounder HornSounder) {
    hornSounder.SoundHorn()
}

В этом примере структура Vehicle поддерживает список значений, реализующих интерфейс HornSounder. Переменная Vehicle создается в основной функции, и сохраняются указатели на переменную типа Car и переменную Bike. Это назначение возможно, потому что и Car, и Bike реализуют интерфейс. Затем выполняется простая циклическая операция, которая вызывает метод SoundHorn() в цикле.

Все, что вам нужно для реализации объектной ориентации в вашем приложении, доступно в Go. Как я уже говорил, Go берет лучшие части ООП и исключает другие, что дает нам лучший способ написания полиморфного кода.

Несколько статей по теме:
1.Methods, Interfaces and Embedded Types in Go
2.How Packages Work in Go
3.Singleton Design Pattern in Go

Надеюсь, что эти несколько простых примеров помогут вам в программировании на Go!

Рекомендуемое чтение:
1.Учи свою девушку писать (продолжение)
2.Объектно-ориентированное программирование в Go