5

私は Go を初めて使用します。最初にやりたいことの 1 つは、小さなマークアップ ページ生成ライブラリを Go に移植することです。主な実装は Ruby であり、その設計は非常に「古典的なオブジェクト指向」です (少なくとも、アマチュア プログラマーの観点から OO を理解している限り)。これは、マークアップされたドキュメント タイプ間の関係をどのように見るかをモデル化したものです。

                                      Page
                                   /        \
                          HTML Page          Wiki Page
                         /         \
              HTML 5 Page           XHTML Page

小さなプロジェクトの場合、次のようなことをするかもしれません (現在必要な Go に変換されます)。

p := dsts.NewHtml5Page()
p.Title = "A Great Title"
p.AddStyle("default.css")
p.AddScript("site_wide.js")
p.Add("<p>A paragraph</p>")
fmt.Println(p) // Output a valid HTML 5 page corresponding to the above

大規模なプロジェクトの場合、たとえば「Egg Sample」という Web サイトの場合、既存のページ タイプの 1 つをサブクラス化し、より深い階層を作成します。

                                 HTML 5 Page
                                      |
                               Egg Sample Page
                             /        |        \
               ES Store Page    ES Blog Page     ES Forum Page

これは、従来のオブジェクト指向設計にうまく適合します。サブクラスは無料で多くのものを取得し、親クラスとは異なるいくつかの部分にのみ焦点を当てています。たとえば、EggSamplePage は、すべての Egg Sample ページに共通するいくつかのメニューとフッターを追加できます。

ただし、Go には型の階層という概念がありません。クラスはなく、型の継承もありません。メソッドの動的なディスパッチもありません(これは上記から続くようです; Go 型HtmlPageは Go 型の「種類」ではありませんPage)。

Go は以下を提供します。

  • 埋め込み
  • インターフェース

これらの 2 つのツールは、私が望むものを得るのに十分なはずですが、何度か間違ったスタートを切った後、私は困惑し、イライラしています。私の推測では、私はそれについて間違って考えていると思います.誰かがこれを行う方法について正しい方向に私を向けることができることを望んでいます.

これは私が抱えている特定の実際の問題です。そのため、より広範な質問に対処せずに特定の問題を解決するための提案を歓迎します。しかし、それを回避するものではなく、「構造体、埋め込み、インターフェイスをこのように組み合わせることで、簡単に思い通りの動作を実現できる」という形で答えてくれることを願っています。古典的なオブジェクト指向言語から Go に移行する多くの新参者は、同様の混乱の時期を経験する可能性が高いと思います。

通常、壊れたコードをここに表示しますが、いくつかのバージョンがあり、それぞれに問題があります。それらを含めることで、すでにかなり長くなった私の質問が実際に明確になるとは思いません。もちろん、有用であることが判明した場合は、コードを追加します。

私がやったこと:

  • The Go FAQ の多くを読む(特に関連があると思われる部分)
  • Effective Goの多くを読む(特に関連があると思われる部分)
  • 多くの検索語の組み合わせで Google を検索した
  • golang-nutに関するさまざまな投稿を読む
  • 非常に不適切な Go コードが書かれている
  • Go標準ライブラリのソースコードを調べて、似ていると思われる例を探しました

私が探しているものについてもう少し明確にするために:

  • このような階層を扱う慣用的なGoの方法を学びたいです。私のより効果的な試みの 1 つは、Go らしくないようです。

    type page struct {
        Title     string
        content   bytes.Buffer
        openPage  func() string
        closePage func() string
        openBody  func() string
        closeBody func() string
    }
    

    これは私を近づけましたが、完全ではありませんでした。現時点で私が言いたいのは、このような状況で Go プログラマーが使用するイディオムを学ぶのは失敗した機会のように思えるということです。

  • 私は合理的である限りDRY(「Don't Repeat Yourself」)になりたいです。text/template各テンプレートの多くが他のテンプレートと同一である場合、ページの種類ごとに個別に作成する必要はありません。私の破棄された実装の 1 つはこのように動作しますが、上で概説したようにページ タイプのより複雑な階層を取得すると、管理できなくなるようです。

  • html5Pageサポートするタイプ (や などxhtmlPage) にそのまま使用でき、ライブラリを直接コピーして編集することなく、上記のように拡張できるコア ライブラリ パッケージを用意したいと考えています。(従来の OO では、たとえば、Html5Page を拡張/サブクラス化し、いくつかの微調整を行います。) 現在の試みは、これにはあまり適していないようです。

これについてのGoの考え方を説明するために、正しい答えは多くのコードを必要としないと思います。

更新:これまでのコメントと回答に基づいて、私はそれほど遠くないようです。私の問題は、私が思っていたよりも一般的なデザイン指向ではなく、私が物事をどのように行っているかについてもう少し正確に書かれているに違いありません。だからここに私が取り組んでいるものがあります:

type page struct {
    Title    string

    content  bytes.Buffer
}

type HtmlPage struct {
    page

    Encoding   string
    HeaderMisc string

    styles   []string
    scripts  []string
}

type Html5Page struct {
    HtmlPage
}

type XhtmlPage struct {
    HtmlPage

    Doctype string
}

type pageStringer interface {
    openPage()   string
    openBody()   string
    contentStr() string
    closeBody()  string
    closePage()  string
}

type htmlStringer interface {
    pageStringer

    openHead()   string
    titleStr()   string
    stylesStr()  string
    scriptsStr() string
    contentTypeStr() string
}

func PageString(p pageStringer) string {
    return headerString(p) + p.contentStr() + footerString(p)
}

func headerString(p pageStringer) string {
    return p.openPage() + p.openBody()
}

func HtmlPageString(p htmlStringer) string {
    return htmlHeaderString(p) + p.contentStr() + footerString(p)
}

func htmlHeaderString(p htmlStringer) string {
    return p.openPage() +
        p.openHead() + p.titleStr() + p.stylesStr() + p.scriptsStr() + p.con    tentTypeStr() +
        p.openBody()
}

これは機能しますが、いくつかの問題があります。

  1. それは本当に厄介な感じです
  2. 私は自分自身を繰り返しています
  3. それは不可能かもしれませんが、理想的には、すべての Page タイプにString()、関数を使用するのではなく、正しいことを行うメソッドが必要です。

私は何か間違ったことをしていて、これを改善できるGoのイディオムがあるのではないかと強く疑っています。

正しいことをするString()メソッドが欲しいのですが、

func (p *page) String( string {
    return p.headerString() + p.contentStr() + p.footerString()
}

は、 を介して使用された場合でも、常にpageメソッドを使用HtmlPageします。これは、インターフェイス以外では動的ディスパッチが不足しているためです。

私の現在のインターフェースベースのページ生成では、fmt.Println(p)(どこpにある種のページがあるのか​​) を行うだけでなく、 と の間で具体的に選択する必要がfmt.Println(dsts.PageString(p))ありfmt.Println(dsts.HtmlPageString(p))ます。それは非常に間違っていると感じます。

PageString()そして、 /HtmlPageString()headerString()/の間でぎこちなくコードを複製していますhtmlHeaderString()

ですから、Go ではなく Ruby や Java で考え続けている結果として、まだ設計上の問題に苦しんでいるように感じます。私が説明したクライアント インターフェイスのようなものを備えたライブラリを構築するための、簡単で慣用的な Go の方法があることを願っています。

4

5 に答える 5

5

継承は 2 つの概念を組み合わせたものです。ポリモーフィズムとコード共有。Go はこれらの概念を分離します。

  • Go のポリモーフィズム ('is a') は、インターフェイスを使用して実現されます。
  • Go でのコード共有は、インターフェイス上で動作する埋め込みと関数によって実現されます

OOP 言語から来た多くの人々は、関数を忘れて、メソッドだけを使用して道に迷っています。

Go はこれらの概念を分離しているため、個別に考える必要があります。「ページ」と「たまごサンプルページ」の関係は?それは「is a」関係ですか、それともコード共有関係ですか?

于 2012-07-13T14:21:23.097 に答える
3

最初に、警告: 深い階層はすべての言語で適応するのが困難です。深い階層構造モデリングは、多くの場合、落とし穴です。最初は知的に満足できますが、最後は悪夢に終わります。

次に、Go には埋め込みがあります。これは実際には合成ですが、(潜在的に複数の) 継承で一般的に必要とされるもののほとんどを提供します。

たとえば、これを見てみましょう:

type ConnexionMysql struct {
    *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)

ご覧のとおり、埋め込むだけで次のことができます。

  • 「サブクラス」のインスタンスを簡単に生成するConnexionMysql
  • 次のような独自の関数を定義して使用しますEtatBraldun
  • *sql.DBのようCloseに で定義された関数を引き続き使用するQueryRow
  • 必要に応じて(ここにはありません)フィールドをサブクラスに追加して使用します

そして、複数のタイプを埋め込むことができました。または、私のタイプを「サブタイプ」しConnexionMysqlます。

私の意見では、これは良い妥協点であり、深い継承階層の罠や硬直性を回避するのに役立ちます。

Go が OOP 言語ではないことは明らかなので、これは妥協だと言います。ご覧のとおり、関数のオーバーライドがないため、「サブクラス」メソッドの呼び出しを構成する「スーパークラス」にメソッドを持つという通常の解決策が妨げられます。

これが気になるかもしれないことは理解していますが、通常の階層ベースのソリューションの重みと冗長性を本当に見逃しているかどうかはわかりません。先ほども言いましたが、最初はいいのですが、複雑になるとつらいです。そのため、Go の方法を試すことをお勧めします。

Go インターフェイスを使用すると、継承の必要性を減らすことができます。実際、あなたのページはインターフェース(またはより慣用的にいくつかのインターフェース)と struct である可能性があります:

type pageOpenerCloser interface {
    openPage  func() string
    closePage func() string
    openPage  func() string
    closePage func() string
}

type page struct {
    Title     string
    content   bytes.Buffer
}

String()の実装で定義されたメソッドに依存して、同じ実装で定義されpageOpenerCloserたメソッドを単に呼び出すことはできないため、closeBodyメソッドではなく関数を使用して作業の一部を実行する必要があります。のpageOpenerCloser適切な実装を呼び出す合成関数へ。

これの意味は

  • アトミックおよび直交インターフェース定義を持つことをお勧めします
  • インターフェースは(単純な)アクションによって定義されます
  • インターフェイス関数は、同じインスタンスで他の関数を呼び出すことは想定されていません
  • 実装/アルゴリズムによって定義された中間レベルを持つ深い階層を持つことはお勧めできません
  • 大きな「構成」アルゴリズムをオーバーライドしたり、一般的に複数回記述したりしないでください

これにより、煩雑さが軽減され、Go プログラムが小さくてわかりやすくなるのに役立つと思います。

于 2012-07-13T09:47:41.567 に答える
1

たぶん、このようにあなたの問題を解決しようとします:

  • ページを記述し、htmlを出力するための最小限のインターフェースを受け入れる関数を作成します。
  • 「持っている」関係について考えて、すべてのページをモデル化します。たとえば、GetTitle()メソッドを使用してページに埋め込んだTitle構造体があり、assertと入力して確認します(すべてにタイトルがあるわけではないため、「」の一部にしたくない場合があります。関数が受け入れるインターフェースの「必須」機能。)ページの階層について考えるのではなく、ページがどのように一緒に構成されているかを考えてください。

私が通常役立つと思うことの1つは、インターフェースを機能のキャプチャーと見なし、構造体をデータのキャプチャーと考えることです。

于 2012-07-13T15:28:51.997 に答える
1

ご質問に満足してお答えすることはできないと思います。しかし、プログラミングについて一般的に受け入れられている考えを避けるために、別の文脈からの短い類推から始めましょう (たとえば、多くのプログラマーは、OOP がプログラミングの「正しい方法」であると信じています。

Bridge Builder という古典的なゲームをプレイしているとします。このゲームの目的は、柱の上に橋を架け、列車が片側から反対側に通過できるようにすることです。ある日、何年もゲームをマスターした後、何か新しいことに挑戦したいと決心しました。ポータル 2 としましょう :)

最初のレベルは簡単に管理できますが、2 番目のレベルで反対側のプラットフォームに到達する方法がわかりません。それであなたは友人に「ポータル 2 に柱を配置するにはどうすればよいですか」と尋ねますか? あなたの友人は一見混乱しているように見えるかもしれませんが、それらの箱を手に取って互いに重ねることができると言うかもしれません. そのため、部屋の反対側への橋を架けるために、見つけられるすべてのボックスをすぐに集め始めます。素晴らしい!

とにかく、数時間後、Portal 2 が本当にイライラすることに気付きます (ブロックを集めるのに時間がかかり、レベルが非常に難しい)。だからあなたは遊ぶのをやめます。

それで、ここで何がうまくいかなかったのですか?まず、あるゲームの 1 つのテクニックが別のゲームでうまく機能する可能性があると想定しました。第二に、あなたは正しい質問をしていません。あなたの問題について友人に話す代わりに (「あそこのプラットフォームにアクセスするにはどうすればよいですか?」)、他のゲームで慣れ親しんだものをアーカイブする方法を尋ねました。他の質問をした場合、友人は、ポータルガンを使用して赤と青のポータルを作成し、通り抜けることができると言うことができたかもしれません.

よくできた Ruby / Java などのプログラムを Go に移植しようとするのは、本当にもどかしいものです。ある言語でうまく機能することは、別の言語ではうまく機能しない可能性があります。どのような問題を解決しようとしているのか、私たちに尋ねたことさえありません。いくつかのクラス階層を示す役に立たないボイラープレート コードを投稿しました。Go のインターフェースはより柔軟であるため、Go ではそれは必要ありません。(Javascript のプロトタイピングと、Javascript で OOP 方式でプログラミングしようとする人々との間には、同様の類似性が引き出されます)。

特にOOPに慣れている場合、最初はGoで良いデザインを思いつくのは難しいです。しかし、Go のソリューションは通常、より小さく、より柔軟で、理解しやすいものです。Go 標準ライブラリとその他の外部パッケージに含まれるすべてのパッケージを詳しく見てください。たとえば、両方の言語をよく知っている場合でも、leveldb-go は leveldb よりもはるかに簡単で理解しやすいと思います。

于 2012-07-13T12:09:32.283 に答える
0

少なくとも現在のタスクでは、実行可能な解決策を考え出したようです。ここですべてのアドバイスを読み、友人(Goを知らないが、型継承の言語サポートなしで明らかに階層関係をモデル化しようとした他の経験がある)と話した後、「私は自分自身に尋ねます」他に何がありますか?はい、それは階層ですが、他に何があり、それをどのようにモデル化できますか?'"、私は座って要件を書き直しました:

次のようなフローを持つクライアントインターフェイスを備えたライブラリが必要です。

  1. ページ作成オブジェクトをインスタンス化し、生成する形式を指定する可能性があります。例えば:

    p := NewHtml5Page()
    
  2. オプションで、プロパティを設定し、コンテンツを追加します。例えば:

    p.Title = "FAQ"
    p.AddScript("default.css")
    p.Add("<h1>FAQ</h1>\n")
    
  3. ページを生成します。例えば:

    p.String()
    
  4. そしてトリッキーな部分:Egg Sampleという名前のWebサイトがライブラリを簡単に活用して、既存のフォーマットに基づいて新しいフォーマットを作成し、それ自体がさらなるサブフォーマットの基礎を形成できるように、拡張可能にします。例えば:

    p  := NewEggSamplePage()
    p2 := NewEggSampleForumPage()
    

Goでそれをモデル化する方法を考えて、クライアントは実際には型階層を必要としないと判断しました。クライアントは、をまたはEggSampleForumPageとして扱う必要はありません。むしろ、私の「サブクラス」がそれぞれコンテンツを追加したり、時には「スーパークラス」とは異なるコンテンツを持ったりするページ内の特定のポイントを持っていることを望んでいるように見えました。つまり、それは行動の問題ではなく、データの問題です。EggSamplePageEggSamplePageHtml5Page

それは私のために何かがクリックされたときです:Goにはメソッドの動的ディスパッチがありませんが、「サブタイプ」(「スーパータイプ」を埋め込むタイプ)がデータフィールドを変更した場合、「スーパータイプ」のメソッドはその変更を認識します。(これは、メソッドではなく関数ポインターを使用して、私の質問で示した非常に非Goのような試みで作業していたものです。)これが最終的な結果の抜粋であり、新しい設計を示しています。

type Page struct {
    preContent  string
    content     bytes.Buffer
    postContent string
}

type HtmlPage struct {
    Page

    Title      string
    Encoding   string
    HeadExtras string

    // Exported, but meant as "protected" fields, to be optionally modified by
    //  "subclasses" outside of this package
    DocTop     string
    HeadTop    string
    HeadBottom string
    BodyTop    string
    BodyAttrs  string
    BodyBottom string
    DocBottom  string

    styles  []string
    scripts []string
}

type Html5Page struct {
    *HtmlPage
}

type XhtmlPage struct {
    *HtmlPage

    Doctype string
}

func (p *Page) String() string {
    return p.preContent + p.content.String() + p.postContent
}

func (p *HtmlPage) String() string {
    p.preContent = p.DocTop + p.HeadTop +
        p.titleStr() + p.stylesStr() + p.scriptsStr() + p.contentTypeStr() +
        p.HeadExtras + p.HeadBottom + p.BodyTop
    p.postContent = p.BodyBottom + p.DocBottom

    return p.Page.String()
}

func NewHtmlPage() *HtmlPage {
    p := new(HtmlPage)

    p.DocTop     = "<html>\n"
    p.HeadTop    = "  <head>\n"
    p.HeadBottom = "  </head>\n"
    p.BodyTop    = "<body>\n"
    p.BodyBottom = "</body>\n"
    p.DocBottom  = "</html>\n"

    p.Encoding = "utf-8"

    return p
}

func NewHtml5Page() *Html5Page {
    p := new(Html5Page)

    p.HtmlPage = NewHtmlPage()

    p.DocTop = "<!DOCTYPE html>\n<html>\n"

    return p
}

クリーンアップを使用することもできますが、アイデアが浮かんだら書くのは非常に簡単で、(私が知る限り)完璧に機能します。言語構造と戦っているような気分にはなりません。 、そして私fmt.Stringerは私が望んでいたように実装することさえできます。目的のインターフェイスでHTML5ページとXHTMLページの両方を正常に生成し、Html5Pageクライアントコードから「サブクラス化」して、新しいタイプを使用しました。

Goで階層をモデル化するという質問に対して明確で普遍的な答えが得られない場合でも、これは成功だと思います。

于 2012-07-14T11:38:01.470 に答える