Подтвердить что ты не робот

Вложение вместо наследования в Go

Как вы оцениваете это дизайнерское решение? Какие преимущества у него есть и какие недостатки?

Ссылки:

4b9b3361

Ответ 1

В комментарии вы задавались вопросом, достаточно ли идеи внедрения, чтобы "полностью заменить наследование". Я бы сказал, что ответ на этот вопрос "да". Несколько лет назад я очень кратко сыграл с системой Tcl OO под названием Snit, которая использовала композицию и делегирование за исключением наследования. Снит все еще сильно отличается от подхода "Го", но в этом отношении у них есть общая философская основа. Это механизм для объединения частей функциональности и ответственности, а не иерархии для классов.

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

(Для всех читателей, знакомых с Tcl, я чувствовал, что Snit был немного ближе к "чувству" языка, чем [incr Tcl] был. Tcl - это все о делеции, по крайней мере, на мой взгляд).

Ответ 2

Принцип Gang of 4 - "предпочитает композицию для наследования"; Go заставляет вас следовать за ним, -).

Ответ 3

Единственное реальное использование для наследования:

  • Полиморфизм

    • Интерфейс Go "статическая система утиного ввода" решает эту проблему.
  • Заимствование из другого класса

    • Вот что такое вложение для
Подход

Go точно не отображает 1-к-1, рассмотрим этот классический пример наследования и полиморфизма в Java (на основе этого)

//roughly in Java (omitting lots of irrelevant details)
//WARNING: don't use at all, not even as a test

abstract class BankAccount
{
    int balance; //in cents
    void Deposit(int money)
    {
        balance += money;
    }

    void withdraw(int money)
    {
        if(money > maxAllowedWithdrawl())
            throw new NotEnoughMoneyException();
        balance -= money;
    }

    abstract int maxAllowedWithdrawl();
}

class Account extends BankAccount
{
    int maxAllowedWithdrawl()
    {
        return balance;
    }
}

class OverdraftAccount extends BankAccount
{
    int overdraft; //amount of negative money allowed

    int maxAllowedWithdrawl()
    {
        return balance + overdraft;
    }
}

Здесь наследование и полиморфизм объединены, и вы не можете перевести это на Go без изменения базовой структуры.

Я не глубоко погрузился в Go, но, полагаю, это выглядело бы примерно так:

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it programming Java in Go

type Account interface {
    AddToBalance(int)
    MaxWithdraw() int
}

func Deposit(account Account, amount int) {
    account.AddToBalance(amount)
}

func Withdraw(account Account, amount int) error {
    if account.MaxWithdraw() < amount {
        return errors.New("Overdraft!")
    }
    account.AddToBalance(-amount)
    return nil
}

type BankAccount {
    balance int
}

func (account *BankAccount) AddToBalance(amount int) {
    account.balance += amount;
}

type RegularAccount {
    *BankAccount
}

func (account *RegularAccount) MaxWithdraw() int {
    return account.balance //assuming it allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

func (account *OverdraftAccount) MaxWithdraw() int {
    return account.balance + account.overdraft
}

В соответствии с примечанием, это совершенно неправильный способ кодирования, поскольку в Java используется Java. Если кто-то должен был написать такую ​​вещь в Go, это, вероятно, было бы намного отличаться от этого.

Ответ 4

Встраивание обеспечивает автоматическое делегирование. Это само по себе недостаточно для замены наследования, поскольку вложение не дает никакой формы полиморфизма. Интерфейсы Go обеспечивают полиморфизм, они немного отличаются от интерфейсов, которыми вы можете воспользоваться (некоторые люди сравнивают их с утиным типом или структурным типированием).

В других языках иерархии наследования должны быть тщательно разработаны, потому что изменения широко распространены и поэтому трудно сделать. Go избегает этих ловушек, предоставляя мощную альтернативу.

Здесь статья, которая вникает в ООП с Go немного больше: http://nathany.com/good

Ответ 5

Я только сейчас узнаю о Go, но, поскольку вы просите мнение, я предлагаю его на основе того, что я знаю до сих пор. Вложение, по-видимому, типично для многих других вещей в Go, что является явной поддержкой языка для передовых методов, которые уже выполняются на существующих языках. Например, как заметил Алекс Мартелли, банда 4 говорит "предпочитает композицию для наследования". Go не только удаляет наследование, но и делает композицию проще и мощнее, чем в С++/Java/С#.

Я был озадачен комментариями, такими как "Go не дает ничего нового, что я не могу уже делать в языке X", и "зачем нам нужен другой язык?" Мне кажется, что в каком-то смысле Go не предоставляет ничего нового, чего нельзя было бы сделать до этого с некоторой работой, но в другом смысле, что ново, что Go будет способствовать и поощрять использование лучших техник, которые уже на практике, используя другие языки.

Ответ 6

Пользователи запросили ссылки на информацию о встраивании в Go.

Здесь представлен документ "Эффективный Go", в котором рассматривается внедрение, и где приведены конкретные примеры.

http://golang.org/doc/effective_go.html#embedding

Пример имеет смысл, когда у вас уже есть хорошее понимание интерфейсов и типов Go, но вы можете подделать его, подумав о интерфейсе как имени для набора методов, и если вы думаете о структуре, подобной C struct.

Для получения дополнительной информации о структурах вы можете увидеть спецификацию языка Go, в которой явно упоминаются неназванные члены структур как внедренные типы:

http://golang.org/ref/spec#Struct_types

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

https://github.com/ecashin/go-getting/blob/master/bpaxos.go#L30

Ответ 7

Мне это нравится.

Язык, который вы используете, влияет на ваши шаблоны мысли. (Просто попросите программиста C реализовать "количество слов". Вероятно, они будут использовать связанный список, а затем переключиться на бинарное дерево для производительности. Но каждый программист Java/Ruby/Python будет использовать словарь/хеш. Язык повлиял на их так что они не могут думать об использовании какой-либо другой структуры данных.)

С наследованием вы должны скомпенсировать - начните с абстрактной вещи, а затем подклассифицируйте ее по специфике. Ваш фактический полезный код будет похож на глубину класса N. Это затрудняет использование "части" объекта, потому что вы не можете повторно использовать код, не перетаскивая его в родительские классы.

В Go вы можете "моделировать" ваши классы таким образом (с интерфейсами). Но вы не можете (не можете) кодировать этот путь.

Вместо этого вы можете использовать вложение. Ваш код может быть разбит на небольшие изолированные модули, каждый со своими данными. Это делает повторное использование тривиальным. Эта модульность имеет мало общего с вашими "большими" объектами. (т.е. In Go, вы можете написать метод "quack()", который даже не знает о вашем классе Duck. Но на типичном языке OOP вы не можете объявить, что "моя реализация Duck.quack() не имеет зависимости от любые другие методы Утки." )

В Go это постоянно заставляет программиста думать о модульности. Это приводит к программам, которые имеют низкую связь. Низкое сцепление облегчает техническое обслуживание. ( "О, смотри, Duck.quack() действительно длинный и сложный, но, по крайней мере, я знаю, что он не зависит от остальной части Утки".)