この設計上の決定についてどう思いますか? それにはどのような利点があり、どのような欠点がありますか?
リンク:
Gang of 4の重要な原則は、「継承よりも構成を優先する」ことです。Goはあなたをフォローさせます;-)。
コメントで、埋め込みのアイデアが「継承を完全に置き換える」のに十分かどうか疑問に思いました。その質問に対する答えは「はい」です。数年前、私はSnitと呼ばれる Tcl OO システムで非常に簡単に遊んでいました。これは、継承を除外して構成と委譲を使用していました。Snit は依然として Go のアプローチとは大きく異なりますが、その 1 つの点で、両者には共通の哲学的基盤があります。これは、クラスの階層ではなく、機能と責任を結合するためのメカニズムです。
他の人が述べているように、言語設計者がどのようなプログラミング手法をサポートしたいかが重要です。そのような選択肢にはすべて、それぞれ長所と短所があります。ここで「ベスト プラクティス」という言葉が必ずしも当てはまるとは思いません。最終的に誰かが Go の継承レイヤーを開発するのを見るでしょう。
(Tcl に精通している読者にとっては、Snit は以前よりも言語の「感触」にわずかに近いと感じました[incr Tcl]
。少なくとも私の考え方では、Tcl は委任に関するものです。)
継承の実際の用途は次のとおりです。
ポリモーフィズム
別のクラスから実装を借用する
Go のアプローチは 1 対 1 で正確にマッピングされません。Java での継承とポリモーフィズムのこの古典的な例を考えてみましょう ( 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;
}
}
ここでは、継承とポリモーフィズムが組み合わされており、基礎となる構造を変更せずにこれを Go に変換することはできません。
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
}
注記によると、Go で Java を実行しているため、これはまったく間違ったコーディング方法です。Go でそのようなことを書くとしたら、おそらくこれとはかなり異なる編成になるでしょう。
埋め込みは自動委任を提供します。埋め込みはポリモーフィズムの形式を提供しないため、これ自体は継承を置き換えるのに十分ではありません。Goインターフェースはポリモーフィズムを提供しますが、使用するインターフェースとは少し異なります(ダックタイピングや構造型タイピングに例える人もいます)。
他の言語では、変更は広範囲にわたって行われるため、継承階層を慎重に設計する必要があります。Goは、強力な代替手段を提供しながら、これらの落とし穴を回避します。
Goを使用したOOPについてもう少し詳しく説明した記事は次のとおりです:http://nathany.com/good
Go への埋め込みに関する情報へのリンクを求める人々がいます。
これは、埋め込みが議論され、具体的な例が提供されている「Effective Go」ドキュメントです。
http://golang.org/doc/effective_go.html#embedding
この例は、Go のインターフェイスと型を既によく理解している場合にはより意味がありますが、インターフェイスを一連のメソッドの名前と考えたり、構造体を C 構造体に似ていると考えたりすると、それを偽造することができます。
構造体の詳細については、構造体の名前のないメンバーを埋め込み型として明示的に言及している Go 言語仕様を参照してください。
http://golang.org/ref/spec#Struct_types
これまでのところ、フィールド名がソース コードに値を追加しない場合に、内部構造体にフィールド名を使用せずに、ある構造体を別の構造体に配置する便利な方法としてのみ使用してきました。以下のプログラミング演習では、提案と応答チャネルを持つ型の中に提案型をまとめています。
https://github.com/ecasin/go-getting/blob/master/bpaxos.go#L30
私は今、囲碁について学んでいますが、意見を求められているので、これまでの知識に基づいて意見を述べます。埋め込みは、既存の言語で既に行われているベスト プラクティスの明示的な言語サポートである、Go の他の多くの機能の典型のようです。たとえば、Alex Martelli が指摘したように、Gang of 4 は「継承よりも構成を好む」と言います。Go は継承を取り除くだけでなく、構成を C++/Java/C# よりも簡単かつ強力にします。
「Go は言語 X でまだ実現できない新しいことを何も提供しない」、「なぜ別の言語が必要なのですか?」などのコメントに戸惑いました。ある意味では、Go は以前にはできなかった新しいことを何も提供していないように見えますが、別の意味では、新しいのは、Go が最も優れた技術の使用を容易にし、奨励することです。すでに他の言語を使用して実践しています。
それはいいですね。
使用する言語は、思考パターンに影響を与えます。(C プログラマーに「ワード カウント」を実装するように依頼してください。彼らはおそらくリンク リストを使用し、パフォーマンスのためにバイナリ ツリーに切り替えます。しかし、すべての Java/Ruby/Python プログラマーは辞書/ハッシュを使用します。他のデータ構造を使用することを考えられないほどの頭脳です。)
継承では、構築する必要があります。抽象的なものから始めて、それを詳細にサブクラス化します。実際に役立つコードは、クラス N レベルの深さに埋もれてしまいます。これにより、親クラスにドラッグしないとコードを再利用できないため、オブジェクトの「一部」を使用することが難しくなります。
Go では、この方法で (インターフェースを使用して) クラスを「モデル化」できます。しかし、この方法でコーディングすることはできません。
代わりに、埋め込みを使用できます。コードは、それぞれが独自のデータを持つ小さな分離されたモジュールに分割できます。これにより、再利用が簡単になります。このモジュール性は、「大きな」オブジェクトとはほとんど関係ありません。(つまり、Go では、Duck クラスについても知らない "quack()" メソッドを書くことができます。しかし、典型的な OOP 言語では、"my Duck.quack() 実装には依存関係がない" と宣言することはできません。ダックの他の方法。」)
Go では、これにより、プログラマーは常にモジュール性について考える必要があります。これにより、カップリングの低いプログラムが作成されます。低カップリングにより、メンテナンスがはるかに簡単になります。(「ほら、Duck.quack() は本当に長くて複雑ですが、少なくとも Duck の残りの部分に依存していないことはわかっています。」)