Go 言語は GoF 設計パターンを実装します: アダプター パターン

この記事は、Huawei Cloud Community「[Go 実装] GoF を実践するための 23 のデザイン パターン: アダプター パターン」(著者: Yuan Runzi) から共有されたものです。

導入

アダプター モードは、最も一般的に使用される構造モードの 1 つで、実際には、イギリスのプラグを中国のソケットで使用できるようにする電源プラグ コンバーターなど、あらゆるところでアダプター モードが見られます。

GoF は次のように定義しています。

クラスのインターフェースをクライアントが期待する別のインターフェースに変換します。アダプターを使用すると、互換性のないインターフェイスのために他の方法では不可能だったクラスを連携させることができます。

簡単に言うと、アダプター パターンにより、インターフェイスが一致しないために本来は連携できなかった 2 つのクラス/構造が連携できるようになります

アダプター パターンが行うことは、インターフェースを、アダプターを通じてクライアントが予期する別のインターフェース Adaptee Adapter  Target 変換することです。実装原理も非常に単純です。つまり、インターフェースを実装し、対応するメソッドでインターフェースを呼び出すだけです。 Adapter  Target  Adaptee 

UML構造

シーンのコンテキスト

単純な分散アプリケーション システム(サンプル コード プロジェクト)では、サービス登録情報とシステム監視データを保存するために、キーと値のデータベースである db モジュールが使用されます。ビジター モードでは、テーブル列クエリ関数を実装しましたが、同時に、単純な SQL クエリ関数も実装しました (インタプリタモードで導入されます)。クエリの結果は構造体です。結果を に変換するメソッドを提供します      SqlResult  toMap  map 

ユーザーを容易にするために、ターミナル コンソールに人間とコンピューターの対話を提供する機能を実装します。以下に示すように、ユーザーが SQL ステートメントを入力すると、クエリ結果がバックグラウンドで返されます。

ターミナル コンソールの具体的な実装では、スケーラブルなクエリ結果の表示スタイルを提供するためにインターフェイスを設計しましたが、このインターフェイスが実装されていないため、クエリ結果を直接表示できません Console ConsoleRender  SqlResult  Console  SqlResult 

これを行うには、アダプターを実装して、アダプターを通じてクエリ結果をレンダリングできるようにする必要があります。この例では、上に示したように、インターフェースを実装し、クエリ結果をテーブルの形式でレンダリングするアダプターを設計しました。 Console  SqlResult  TableRender ConsoleRender 

コード

// demo/db/sql.go 
package db 

// Adapte SQL ステートメントの実行によって返された結果は、ターゲット インターフェイス タイプを実装していません
SqlResult struct { 
    fields []string 
    vals []interface{} 
} 

func (s *SqlResult) Add(フィールド文字列、レコード インターフェース{}) { 
    s.fields = append(s.fields, field) 
    s.vals = append(s.vals, Record) 
} 

func (s *SqlResult) ToMap() map[string]interface{} { 
    results := make(map[string]interface{}) 
    for i, f := range s.fields { 
        results[f] = s.vals[i] 
    } 
    return results 
} 

// デモ/db/console.go
パッケージdb 

// クライアント端末のコンソール
タイプ Console struct { 
    db Db 
} 

// 出力は ConsoleRender を呼び出し、クエリ結果と出力のレンダリングを完了します
func (c *Console) Output(render ConsoleRender) { 
    fmt.Println(render.Render()) 
} 

// ターゲット インターフェイス、コンソール データベース クエリ結果のレンダリング インターフェイス
タイプ ConsoleRender インターフェイス { 
    Render() string 
} 

// TableRender テーブル形式のクエリ結果のレンダリング アダプター
// キー ポイント 1: アダプターの構造体/クラス タイプの定義
TableRender struct { 
    // キー ポイント 2 : アダプター内の集約 Adapteee、ここでは SqlResult が TableRender 結果のメンバー変数として使用されます
    *SqlResult 
} 

// キー ポイント 3: ターゲット インターフェイスの実装、ここに ConsoleRender インターフェイスがあります
func (t *TableRender) Render() string { 
    // Key point ポイント 4: Target インターフェース実装で、Adaptee の独自メソッドを呼び出して特定のビジネス ロジックを実装する
    vals := t.result.ToMap() 
    var header []string 
    var data []string 
    for key, val := range vals { 
        header = append(header, key) 
        data = append(data, fmt.Sprintf("%v", val)) 
    } 
    builder := &strings.Builder{} 
    table := tablewriter.NewWriter(builder) 
    table.SetHeader(header) 
    table .Append(data) 
    table.Render() 
    return builder.String() 
} 

// これはエラーをレンダリングする関数を実装する別のアダプタです
type ErrorRender struct { 
    err error 
} 

func (e *ErrorRender) Render() string { 
    return e.err.Error() 
}

クライアントはこれを使用します。

func (c *Console) Start() { 
    fmt.Println("デモ DB へようこそ。終了するには終了を入力してください!") 
    fmt.Println("> SQL 式を入力してください:") 
    fmt.Print("> ")
    スキャナ:= bufio.NewScanner(os.Stdin) 
    for scanner.Scan() { 
        sql := Scanner.Text() 
        if sql == "exit" { 
            Break 
        }
        結果、err := c.db.ExecSql(sql) 
        if err == nil { 
            // キー ポイント 5: Target インターフェイスが必要な場合は、アダプターのアダプター インスタンスを渡します。アダプター インスタンスを作成するときは、Adaptee インスタンスを渡す必要があります。
            c.Output(NewTableRender(result)) 
        } else { 
            c.Output(NewErrorRender (err)) 
        } 
        fmt.Println("> SQL 式を入力してください:") 
        fmt.Print("> ") 
    } 
}

ConsoleRenderTarget インターフェイス ( ) と Adaptee ( )がすでにあるという前提でSqlResult、アダプター パターンを実装する際のいくつかの重要なポイントをまとめてみましょう。

  1. アダプターの構造/クラスを定義します。構造は次のとおりです。 TableRender 
  2. アダプタ内の集計 Adapte です。これは のメンバ変数です SqlResult  TableRender 
  3. アダプターは Target インターフェースを実装します。ここではインターフェースが実装されています TableRender  ConsoleRender 
  4. Target インターフェイスの実装では、特定のビジネス ロジックを実装するために Adaptee の独自メソッドが呼び出され、メソッドが呼び出されクエリ結果が取得され、その結果が表示されます。 TableRender.Render()  SqlResult.ToMap() 
  5. クライアントが Target インターフェイスを必要とする場合、Adapter インスタンスが渡され、Adaptee インスタンスは、Adapter インスタンスの作成時に渡されます。ここでは、インスタンスを作成するときに、それを入力パラメーターとして渡し、そのインスタンスをメソッドに渡します。 NewTableRender()  TableRender  SqlResult  TableRender  Console.Output() 

拡大する

Jin でのアダプター パターンの適用

Gin は高パフォーマンスの Web フレームワークであり、一般的な使用法は次のとおりです。

// ユーザー定義のリクエスト処理関数、タイプ gin.HandlerFunc 
func myGinHandler(c *gin.Context) { 
    ... // リクエストを処理するための特定のロジック
} 

func main() { 
    // デフォルトのルート エンジンを作成します、タイプ For gin .Engine 
    r := gin.Default() 
    // ルート定義
    r.GET("/my-route", myGinHandler) 
    // ルート エンジン開始
    r.Run() 
}

実際のアプリケーション シナリオでは、この状況が存在する可能性があります。ユーザーの最初の Web フレームワークは Go ネイティブ を使用し、使用シナリオは次のとおりです。 net/http

// ユーザー定義のリクエスト処理関数、タイプ http.Handler 
func myHttpHandler(w http.ResponseWriter, r *http.Request) { 
    ... // リクエストを処理するための特定のロジック
} 

func main() { 
    // ルート定義
    http. HandleFunc("/my-route", myHttpHandler) 
    // ルートの開始
    http.ListenAndServe(":8080", nil) 
}

パフォーマンスの問題により、現在の顧客は、Gin フレームワークに切り替えることを計画していますが、myHttpHandler インターフェイスに互換性がないため、直接登録できないことは明らかですユーザーの便宜のために、Gin フレームワークは型を型に変換できるアダプターを提供しており、次のように定義されています。 gin.Default()  gin.WrapH http.Handler  gin.HandlerFunc 

// WrapH は http.Handler をラップするためのヘルパー関数であり、Gin ミドルウェアを返します。
func WrapH(h http.Handler) HandlerFunc { 
	  return func(c *Context) { 
		  h.ServeHTTP(c.Writer, c.Request) 
	  } 
}

それの使い方:

// ユーザー定義のリクエスト処理関数、タイプは http.Handler 
func myHttpHandler(w http.ResponseWriter, r *http.Request) { 
    ... // リクエストを処理するための特定のロジック
} 

func main() { 
    // デフォルト ルートを作成エンジン
    r := gin.Default() 
    // ルート定義
    r.GET("/my-route", gin.WrapH(myHttpHandler)) 
    // ルート エンジンが開始
    r.Run() 
}

この例では、gin.Engine クライアント、gin.HandlerFunc つまりターゲット インターフェイス、http.Handler アダプティ、gin.WrapH つまりアダプタです。これは、Adapter パターンの Go スタイルの実装であり、より簡潔な置き換えが行われます func  struct

典型的なアプリケーションシナリオ

  • インターフェース A をユーザーが望む別のインターフェース B に変換し、互換性のない元のインターフェース A とインターフェース B が相互に連携できるようにします。
  • 古いシステムの再構築。元のインターフェースを変更せずに、古いインターフェースを新しいインターフェースに適応させます。

長所と短所

アドバンテージ

  1. Adapte と Target を分離できます。ターゲットに適応する新しいアダプタを導入することにより、アダプティを変更する必要がなく、開始および終了の原則に準拠します。
  2. 優れた柔軟性があり、さまざまなアダプターを介してさまざまなインターフェイスに簡単に適応できます。

欠点がある

  1. コードが複雑になります。アダプター モードには新しいアダプターが必要ですが、悪用するとシステムのコードが複雑になります。

他のパターンとの関連付け

アダプター パターンは、UML 構造のデコレーター パターンおよびエージェント パターンと特定の類似点があります。ただし、アダプター モードは元のオブジェクトのインターフェイスを変更しますが、元の機能は変更しませんが、デコレーター モードとプロキシ モードはインターフェイスを変更せずに元のオブジェクトの機能を強化します。  

記事の写真

Keynoteを使った手描き風イラストで記事の描き方を確認できます  

参考

[1] [Go Implementation] GoF を実践するための 23 のデザイン パターン: SOLID 原則、Yuan Runzi 

[2] デザイン パターン、第 4 章 構造パターン、GoF

[3] アダプターモード、 refactoringguru.cn

[4]ジン Web フレームワーク、ジン 

 

クリックしてフォローし、できるだけ早くHuawei Cloudの新しいテクノロジーについて学びましょう~

 

IntelliJ IDEA 2023.3 と JetBrains Family Bucket の年次メジャー バージョン アップデート 新しいコンセプト「防御型プログラミング」: 安定した仕事に就く GitHub.com では 1,200 を超える MySQL ホストが稼働していますが、8.0 にシームレスにアップグレードするにはどうすればよいですか? Stephen Chow の Web3 チームは来月、独立したアプリをリリースする予定ですが、 Firefox は廃止されるのでしょうか? Visual Studio Code 1.85 がリリース、フローティング ウィンドウ 米国 CISA、メモリ セキュリティの脆弱性を排除するために C/C++ の放棄を推奨 余成東 : ファーウェイは来年破壊的製品を発売し、業界の歴史を書き換える TIOBE 12 月: C# は今年のプログラミング言語になると期待される 論文30年前にLei Junが書いた「コンピュータウイルス判定エキスパートシステムの原理と設計」
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4526289/blog/10320801