Ketika Rob Pike mengatakan "Go is about komposisi", apa sebenarnya yang dia maksud? [Tutup]

Jawaban:

13

Maksudnya di mana Anda akan menggunakan sesuatu berdasarkan urutan:

class A : public B {};

di sesuatu seperti Java atau C ++, di Go Anda akan menggunakan (sesuatu yang setara dengan):

class A {
    B b;
};

Ya, ini memberikan kemampuan seperti warisan. Mari kita sedikit memperluas contoh di atas:

struct B {
    int foo() {}
};

struct A { 
    B b;
};

A a;

a.foo();  // not allowed in C++ or Java, but allowed in Go.

Untuk melakukannya, Anda menggunakan sintaks yang tidak diizinkan di C ++ atau Java - Anda meninggalkan objek yang disematkan tanpa nama sendiri, jadi lebih seperti:

struct A {
   B;
};
Jerry Coffin
sumber
1
Saya ingin tahu, saya melakukannya di C ++ (lebih suka komposisi). Apakah pergi menyediakan fitur yang membantu saya menulis ketika di Java / C ++ saya harus mewarisi?
Doug T.
2
@ DougT .: Ya, saya telah mengedit dalam contoh yang menunjukkan gagasan umum (bagian dari) apa yang dibolehkan.
Jerry Coffin
2
Saya pikir ini melenceng: perbedaannya bukan hanya sintaksis yang menyiratkan Anda menggunakan penyematan untuk membangun taksonomi Anda. Faktanya adalah bahwa kurangnya metode OOP menimpa mencegah Anda untuk membangun taksonomi klasik Anda dan Anda harus menggunakan komposisi sebagai gantinya.
Denys Séguret
1
@dystroy: Dibandingkan dengan Java, Anda mungkin ada benarnya. Dibandingkan dengan C ++, tidak terlalu banyak - karena (setidaknya di antara mereka yang memiliki petunjuk) taksonomi raksasa itu terakhir terlihat hampir 20 tahun yang lalu.
Jerry Coffin
1
@dystroy: Anda masih tidak mengerti. Hierarki tiga level dalam C ++ modern ada di sebelah yang belum pernah terjadi sebelumnya. Di C ++ Anda akan melihat orang-orang di perpustakaan iostreams dan hierarki pengecualian - tetapi dekat dengan tempat lain. Jika perpustakaan iostreams sedang dirancang hari ini, saya pikir aman untuk mengatakan itu juga tidak akan seperti itu. Intinya: argumen Anda menunjukkan lebih sedikit tentang C ++ daripada tentang seberapa jauh Anda bersentuhan dengannya. Mengingat bahwa Anda belum menggunakannya dalam beberapa dekade, itu masuk akal. Apa yang tidak masuk akal adalah mencoba mengatakan bagaimana C ++ digunakan berdasarkan pada pengalaman tanggal tersebut.
Jerry Coffin
8

Pertanyaan / masalah ini agak mirip dengan yang ini .

Di Go, Anda tidak benar-benar memiliki OOP.

Jika Anda ingin "mengkhususkan" suatu objek, Anda melakukannya dengan menanamkan, yang merupakan komposisi, tetapi dengan beberapa barang membuatnya sebagian mirip dengan warisan. Anda melakukannya seperti ini:

type ConnexionMysql struct {
    *sql.DB
}

Dalam sampel ini, ConnexionMysql adalah jenis spesialisasi * sql.DB, dan Anda dapat memanggil ConnexionMysql fungsi yang didefinisikan pada * sql.DB:

type BaseMysql struct {
    user     string
    password string
    database string
}

func (store *BaseMysql) DB() (ConnexionMysql, error) {
    db, err := sql.Open("mymysql", store.database+"/"+store.user+"/"+store.password)
    return ConnexionMysql{db}, err
}

func (con ConnexionMysql) EtatBraldun(idBraldun uint) (*EtatBraldun, error) {
    row := con.QueryRow("select pv, pvmax, pa, tour, dla, faim from compte where id=?", idBraldun)
    // stuff
    return nil, err
}

// somewhere else:
con, err := ms.bd.DB()
defer con.Close()
// ...
somethings, err = con.EtatBraldun(id)

Jadi pada pandangan pertama Anda mungkin berpikir bahwa komposisi ini adalah alat untuk membuat taksonomi Anda yang biasa.

Tapi

jika suatu fungsi yang didefinisikan pada * sql.DB memanggil fungsi-fungsi lain yang didefinisikan pada * sql.DB, itu tidak akan memanggil fungsi-fungsi yang didefinisikan ulang pada ConnexionMysql bahkan jika mereka ada.

Dengan warisan klasik, Anda sering melakukan sesuatu seperti ini:

func (db *sql.DB) doComplexThing() {
   db.doSimpleThing()
   db.doAnotherSimpleThing()
}

func (db *sql.DB) doSimpleThing() {
   // standard implementation, that we expect to override
}

Artinya, Anda mendefinisikan doComplexThingkelas super sebagai organisasi berdasarkan panggilan spesialisasi.

Tetapi di Go, ini tidak akan memanggil fungsi khusus tetapi fungsi "superclass".

Jadi, jika Anda ingin memiliki algoritma yang perlu memanggil beberapa fungsi yang didefinisikan pada * sql.DB tetapi didefinisikan ulang pada ConnexionMySQL (atau spesialisasi lainnya), Anda tidak dapat mendefinisikan algoritma ini sebagai fungsi * sql.DB tetapi harus mendefinisikannya di tempat lain dan fungsi ini hanya akan menyusun panggilan ke spesialisasi yang disediakan.

Anda bisa melakukannya seperti ini menggunakan antarmuka:

type interface SimpleThingDoer {
   doSimpleThing()
   doAnotherSimpleThing()
}

func doComplexThing(db SimpleThingDoer) {
   db.doSimpleThing()
   db.doAnotherSimpleThing()
}

func (db *sql.DB) doSimpleThing() {
   // standard implementation, that we expect to override
}

func (db ConnexionMySQL) doSimpleThing() {
   // other implemenation
}

Ini sangat berbeda dari pengesampingan klasik hierarki kelas.

Terutama, Anda jelas tidak dapat secara langsung memiliki tingkat ketiga yang mewarisi implementasi fungsi dari yang kedua.

Dalam praktiknya, Anda akan menggunakan sebagian besar antarmuka (ortogonal) dan membiarkan fungsi membuat panggilan pada implementasi yang disediakan alih-alih meminta "superclass" implementasi mengatur panggilan-panggilan tersebut.

Dalam pengalaman saya, ini mengarah pada tidak adanya hierarki praktis yang lebih dalam dari satu level.

Terlalu sering, dalam bahasa lain, Anda memiliki refleks, ketika Anda melihat bahwa konsep A adalah spesialisasi dari konsep B, untuk memverifikasi fakta ini dengan membuat kelas B dan kelas A sebagai subkelas B. Bukan membuat Anda program di sekitar data Anda, Anda menghabiskan waktu mereproduksi taksonomi objek dalam kode Anda, dengan prinsip bahwa ini adalah kenyataan.

Di Go, Anda tidak dapat menentukan algoritma umum dan mengkhususkannya. Anda harus menentukan algoritma umum dan memastikannya umum dan berfungsi dengan implementasi antarmuka yang disediakan.

Setelah merasa ngeri dengan semakin kompleksnya beberapa pohon hierarki di mana para pembuat kode membuat peretasan yang rumit untuk mencoba mengakomodasi suatu algoritma yang logikanya akhirnya menyiratkan semua tingkatan, saya akan mengatakan saya senang dengan logika Go yang lebih sederhana, bahkan jika itu memaksa Anda berpikir alih-alih hanya memahami konsep-konsep model aplikasi Anda.

Denys Séguret
sumber