Врезать вместо наследования в


каково Ваше мнение об этом проектном решении? Какие преимущества она имеет и какие минусы?

ссылки:

7   51   2009-11-13 08:13:04

7 ответов:

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

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

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

The банда 4критический принцип "предпочитает композицию наследованию"; Go делает вы следуете за ним;-).

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

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

    • система интерфейса Go "static duck typing" решает эту проблему
  • заимствование реализации из другого класса

    • это то, что вложение для

подход 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, но я полагаю, что это будет выглядеть примерно так:

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it's 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's allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

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

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

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

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

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

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

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

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

вот документ" эффективный Go", где обсуждается внедрение и где приводятся конкретные примеры.

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

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

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

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

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

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

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

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

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

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

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

в Go это постоянно заставляет программиста думать о модульности. Это приводит к программам, которые имеют низкую связь. Низкое соединение делает обслуживание гораздо легче. ("о, Смотри, утка.шарлатан() это действительно долго и сложный, но, по крайней мере, я знаю, что это не зависит от остальной части утки.")