2009-11-13 20 views

Odpowiedz

29

W komentarzu zastanawiałeś się, czy pomysł osadzenia wystarczył do "całkowitego zastąpienia dziedziczenia". Powiedziałbym, że odpowiedź na to pytanie brzmi "tak". Kilka lat temu grałem bardzo krótko z systemem Tcl OO o nazwie Snit, który wykorzystał kompozycję i delegację do wykluczenia dziedziczenia.Snit wciąż różni się znacznie od podejścia Go, ale pod jednym względem mają oni jakąś wspólną filozoficzną podstawę. Jest to mechanizm łączenia ze sobą elementów funkcjonalności i odpowiedzialności, a nie hierarchii dla klas.

Jak stwierdzili inni, chodzi o to, jakiego rodzaju praktyki programistyczne chcą wspierać projektanci języków. Wszystkie takie wybory mają swoje plusy i minusy; Nie uważam, że "najlepsze praktyki" to fraza, która koniecznie ma tu zastosowanie. Prawdopodobnie zobaczymy, że ktoś w końcu opracuje warstwę dziedziczenia dla Go.

(Dla wszystkich czytelników znających Tcl, poczułem, że Snit jest nieco bliższy "odczuciu" języka niż [incr Tcl]. Tcl mówi o delegacji, przynajmniej na mój sposób myślenia.)

36

kluczowe Zasada Gang of 4 „s jest "wolą kompozycję do dziedziczenia"; Przejdź sprawia, że ​​ możesz go śledzić ;-).

+4

Dziedziczenie jest nadużywane i doceniam jak Go upraszcza skład, ale pytanie, które chciałbym wiedzieć, to fakt, że osadzanie może całkowicie zastąpić dziedziczenie. Domyślam się, że jest to trudne pytanie, na które można odpowiedzieć bez wchodzenia i pisania kodu – Casebash

+0

Cóż, nie otrzymujesz (bezpośrednio) pewnych kluczowych wzorców projektowych związanych z dziedziczeniem, takich jak metoda szablonów, ale to nie wydaje się być zabójcze - w najgorszym przypadku wydaje się, że pociąga to za utratę pewnej wygody (wymagającej nieco bardziej jednoznacznego kodowania). –

+0

@Casebash: Ludzie byli w stanie przejść do prototypu JS, który możemy powiedzieć, jest tylko kompozycją. –

12

Jedyne realne zastosowania do dziedziczenia są:

  • Polimorfizm

    • System GO interfejsu, "static kaczka wpisując" rozwiązuje ten problem
  • realizacji pożyczki z innej klasy

    • To co osadzanie jest

podejście Go nie dokładnie map 1-do-1, należy rozważyć to klasyczny przykład dziedziczenia i polimorfizmu w Javie (based on this):

//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; 
    } 
} 

W tym przypadku dziedziczenie i polimorfizm są połączone, a nie można tego przetłumaczyć na Przejdź bez zmiany podstawowej struktury.

Nie sięgnął głęboko do zrobienia, ale przypuszczam, że to będzie wyglądać mniej więcej tak:

//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 
} 

Według notatki, jest to całkowicie niewłaściwy sposób kodowania, ponieważ jeden robi Java Przejdź . Jeśli ktoś miałby napisać coś takiego w Go, prawdopodobnie zorganizowałby się znacznie inaczej.

+0

Wspomniałeś, że nie skompilowałoby to, ale kilka punktów, aby pomóc innym, którzy to czytają: Typy wymagają literału typu w Go. Użyj 'type RegularAccount struct {}' zamiast 'type RegularAccount {}' Nie możesz umieścić prototypów func w definicji typu. Użyj składni odbiornika poza typem: 'func (this * receiverType) funcName (parms) returnType' Musisz podać typy zwracane dla funków, które zwracają wartość, np. 'func (konto * RegularAccount) maxWithdraw() int {}' Wreszcie, w Go wymagane jest, aby zakończyć linię "func" nawiasem otwierającym, zamiast umieszczać ją w osobnej linii. – burfl

+0

Próbowałem napisać to jako ćwiczenie - bardzo wczesne dni dla mnie w Go ... Mam go prawie do pracy, i naprawdę byłbym wdzięczny, gdyby ktoś więcej doświadczenia mógł zadzwonić i poprawić/uzupełnić? https://gist.github.com/mindplay-dk/807179beda57e676b8fb –

3

Właśnie uczę się o Go, ale ponieważ pytasz o opinię, zaproponuję je na podstawie tego, co wiem do tej pory. Osadzanie wydaje się typowe dla wielu innych rzeczy w Go, co jest jawnym wsparciem językowym dla najlepszych praktyk, które są już wykonywane w istniejących językach. Na przykład, jak zauważył Alex Martelli, Gang 4 mówi "preferuj kompozycję do dziedziczenia". Go nie tylko usuwa dziedziczenie, ale sprawia, że ​​kompozycja jest łatwiejsza i bardziej wydajna niż w C++/Java/C#.

Zastanawiałem się nad komentarzami typu: "Go nie dostarcza niczego nowego, czego już nie umiem zrobić w języku X" i "dlaczego potrzebujemy innego języka?" Wydaje mi się, że w pewnym sensie Go nie dostarcza niczego nowego, czego wcześniej nie można by zrobić z jakąś pracą, ale w innym sensie, co jest nowe, to Go ułatwi i zachęci do stosowania najlepszych technik, które są już w praktyce używają innych języków.

+3

Pod pewnymi względami nowością w Go jest to, co zostało zabrane - to jest kluczowy powód nowego języka. Jeśli dodawały tylko funkcje, mogło to być C+++;), ale aby zabrać funkcje (dziedziczenie, arytmetyka wskaźnikowa, ręczna alokacja pamięci), wymagany jest nowy język. –

3

Ludzie zażądali linków do informacji o umieszczaniu w Go.

Oto dokument "Efektywne działanie", w którym omówiono osadzanie i gdzie podano konkretne przykłady.

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

Przykład większy sens, gdy masz już dobrą znajomość interfejsów Idź i typów, ale można udawać, myśląc o interfejsie jako nazwa dla zestawu metod i jeśli myślisz struktura podobna do struktury C.

Więcej informacji o elemencie, można zobaczyć specyfikację języka Go, który wyraźnie wspomina bezimiennych członków kodowanym jako wbudowanych typów:

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

Dotychczas używałem go tylko jako wygodny sposób wstawić jedną strukturę do drugiej bez konieczności używania nazwy pola dla wewnętrznej struktury, gdy nazwa pola nie doda żadnej wartości do kodu źródłowego. W poniższym ćwiczeniu programowym łączę typ oferty w typ, który ma propozycję i kanał odpowiedzi.

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

7

Osadzanie zapewnia automatyczne delegację. Samo w sobie nie wystarczy, aby zastąpić dziedziczenie, ponieważ osadzanie nie zapewnia żadnej formy polimorfizmu. Interfejsy Go zapewniają polimorfizm, są nieco inne niż interfejsy, z których możesz korzystać (niektórzy ludzie porównują je do typowania lub pisania strukturalnego).

W innych językach hierarchie dziedziczenia muszą być starannie zaprojektowane, ponieważ zmiany są szeroko rozrzedzone, a zatem trudne do zrobienia. Idź, unikaj tych pułapek, zapewniając jednocześnie potężną alternatywę.

Oto artykuł, który zagłębia się OOP z idź trochę więcej: http://nathany.com/good

3

mi się podoba.

Język, którego używasz, wpływa na twoje schematy myślowe. (Po prostu poproś programistę C o zaimplementowanie "liczby słów", prawdopodobnie wykorzystają listę połączoną, a następnie przełączą się na drzewo binarne dla wydajności, ale każdy programista Java/Ruby/Python użyje Dictionary/Hash. mózg tak bardzo, że nie mogą myśleć o używaniu jakiejkolwiek innej struktury danych.)

Z dziedziczeniem, musisz zbudować - zacznij od abstrakcyjnej rzeczy, a następnie podklasuj ją do specyfiki. Twój rzeczywisty użyteczny kod zostanie pochowany na poziomie N klasy. To sprawia, że ​​trudno jest użyć "części" obiektu, ponieważ nie można ponownie użyć kodu bez przeciągania w klasach nadrzędnych.

W programie Go można "modelować" swoje klasy w ten sposób (za pomocą interfejsów). Ale nie możesz (nie możesz) kodować w ten sposób.

Zamiast tego możesz używać osadzania. Twój kod może być podzielony na małe, odizolowane moduły, z których każdy ma własne dane. To sprawia, że ​​ponowne użycie jest banalne. Ta modułowość ma niewiele wspólnego z twoimi "dużymi" obiektami. (W In Go możesz napisać metodę "quack()", która nawet nie wie o twojej klasie Duck, ale w typowym języku OOP nie możesz zadeklarować "moja implementacja Duck.quack() nie ma zależności dowolne inne metody Kaczki. ")

W Go, to stale zmusza programistę do myślenia o modularności. Prowadzi to do programów o niskim sprzężeniu. Niskie sprzęganie znacznie ułatwia konserwację. ("oh, zobacz, Duck.quack() jest naprawdę długi i skomplikowany, ale przynajmniej wiem, że to nie zależy od reszty Kaczki.")