実際のデザイン パターン GoF: プロキシ パターン

概要:   プロキシ パターンは、オブジェクトへのアクセスを制御するためのオブジェクトのプロキシを提供します。

この記事は、HUAWEI CLOUD コミュニティ「[Go 実装] GoF の 23 の設計パターンを実践する: プロキシ パターン」、著者: Yuan Runzi から共有されています。

序章

GoF はプロキシ パターンを次のように定義します。

別のオブジェクトへのアクセスを制御するためのサロゲートまたはプレースホルダーを提供します。

つまり、プロキシ パターンは、オブジェクトがそのオブジェクトへのアクセスを制御するためのプロキシを提供します

これは非常によく使用されるデザイン パターンであり、実生活でも非常に一般的です。たとえば、コンサート チケットのダフ屋。コンサートを見たいと思ったのですが、公式サイトのチケットが売り切れていたので、現場に行ってダフ屋で高額で購入しました。この例では、スキャルパーはコンサートチケットの代理店に相当します.公式ルートでチケットを購入できない場合は、代理店を通じて目的を達成したことになります.

コンサート チケットの例から、プロキシ モードを使用する鍵は、クライアントがオブジェクトに直接アクセスするのが不便な場合に、オブジェクトへのアクセスを制御するプロキシ オブジェクトを提供することであることがわかります。クライアントは実際にプロキシ オブジェクトにアクセスし、プロキシ オブジェクトはクライアントの要求を処理のためにオントロジー オブジェクトに転送します。

UML 構造

シーン コンテキスト

単純な分散アプリケーション システム(サンプル コード プロジェクト) では、db モジュールを使用してサービスの登録および監視情報を格納します。これはキー値データベースです。データベースへのアクセスのパフォーマンスを向上させるために、データベースにキャッシュのレイヤーを追加することにしました。

さらに、クライアントがデータベースを使用するときにキャッシュの存在を認識しないことを願っており、プロキシ モードでこれを行うことができます。

コード

// demo/db/cache.go
package db
// 关键点1: 定义代理对象,实现被代理对象的接口
type CacheProxy struct {
 // 关键点2: 组合被代理对象,这里应该是抽象接口,提升可扩展性
 db    Db
    cache sync.Map // key为tableName,value为sync.Map[key: primaryId, value: interface{}]
    hit   int
 miss  int
}
// 关键点3: 在具体接口实现上,嵌入代理本身的逻辑
func (c *CacheProxy) Query(tableName string, primaryKey interface{}, result interface{}) error {
    cache, ok := c.cache.Load(tableName)
 if ok {
 if record, ok := cache.(*sync.Map).Load(primaryKey); ok {
 c.hit++
            result = record
 return nil
 }
 }
 c.miss++
 if err := c.db.Query(tableName, primaryKey, result); err != nil {
 return err
 }
 cache.(*sync.Map).Store(primaryKey, result)
 return nil
}
func (c *CacheProxy) Insert(tableName string, primaryKey interface{}, record interface{}) error {
 if err := c.db.Insert(tableName, primaryKey, record); err != nil {
 return err
 }
    cache, ok := c.cache.Load(tableName)
 if !ok {
 return nil
 }
 cache.(*sync.Map).Store(primaryKey, record)
 return nil
}
...
// 关键点4: 代理也可以有自己特有方法,提供一些辅助的功能
func (c *CacheProxy) Hit() int {
 return c.hit
}
func (c *CacheProxy) Miss() int {
 return c.miss
}
...

クライアントは次のように使用します。

// 客户端只看到抽象的Db接口
func client(db Db) {
 table := NewTable("region").
 WithType(reflect.TypeOf(new(testRegion))).
 WithTableIteratorFactory(NewRandomTableIteratorFactory())
 db.CreateTable(table)
 table.Insert(1, &testRegion{Id: 1, Name: "region"})
 result := new(testRegion)
 db.Query("region", 1, result)
}
func main() {
 // 关键点5: 在初始化阶段,完成缓存的实例化,并依赖注入到客户端
 cache := NewCacheProxy(&memoryDb{tables: sync.Map{}})
 client(cache)
}

この例では、Subject は Db インターフェイス、Proxy は CacheProxy オブジェクト、SubjectImpl は memoryDb オブジェクトです。

プロキシ パターンを実装するためのいくつかのキー ポイントを要約します。

  1. プロキシされるオブジェクトのインターフェイスを実装するプロキシ オブジェクトを定義します。この例では、前者が CacheProxy オブジェクトで、後者が Db インターフェイスです。
  2. プロキシ オブジェクトはプロキシ オブジェクトを組み合わせます。ここでの組み合わせは、プロキシをよりスケーラブルにする抽象的なインターフェイスにする必要があります。この例では、CacheProxy オブジェクトは Db インターフェイスを結合します。
  3. プロキシ オブジェクトは、プロキシ自体のロジックを特定のインターフェイス実装に埋め込みます。この例では、CacheProxy は、キャッシュ sync.Map の読み取りおよび書き込みロジックを Query や Insert などのメソッドに追加します。
  4. プロキシ オブジェクトは、いくつかの補助機能を提供する独自のメソッドを持つこともできます。この例では、CacheProxy は、Hit や Miss などのメソッドを追加して、キャッシュ ヒット率をカウントします。
  5. 最後に、初期化フェーズで、プロキシのインスタンス化が行われ、依存関係がクライアントに注入されます。これには、クライアントが具体的な実装ではなく抽象的なインターフェイスに依存する必要があります。そうしないと、プロキシは透過的ではありません。

拡大

Go 標準ライブラリのリバース プロキシ

プロキシ モードの最も一般的なアプリケーション シナリオはリモート プロキシであり、その中で最も一般的に使用されるのはリバース プロキシです。

Web アプリケーションを例にとると、リバース プロキシは Web サーバーの前に配置され、クライアント (Web ブラウザーなど) の要求をバックエンド Web サーバーに転送します。リバース プロキシは、ロード バランシング、SSL セキュア リンクなど、セキュリティ、パフォーマンス、信頼性を向上させるためによく使用されます。

Go 標準ライブラリの net パッケージは、http.Handler インターフェイスを実装する net/http/httputil/reverseproxy.go の下にあるリバース プロキシ、ReverseProxy も提供します。http.Handler は、Http サーバーと同等の Http 要求を処理する機能を提供します。次に、UML 構造図に対応して、http.Handler が Subject で、ReverseProxy が Proxy です。

以下に、ReverseProxy のコア コードをいくつか示します。

// net/http/httputil/reverseproxy.go
package httputil
type ReverseProxy struct {
 // 修改前端请求,然后通过Transport将修改后的请求转发给后端
    Director func(*http.Request)
 // 可理解为Subject,通过Transport来调用被代理对象的ServeHTTP方法处理请求
    Transport http.RoundTripper
 // 修改后端响应,并将修改后的响应返回给前端
 ModifyResponse func(*http.Response) error
 // 错误处理
 ErrorHandler func(http.ResponseWriter, *http.Request, error)
 ...
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 // 初始化transport
 transport := p.Transport
 if transport == nil {
        transport = http.DefaultTransport
 }
 ...
 // 修改前端请求
 p.Director(outreq)
 ...
 // 将请求转发给后端
    res, err := transport.RoundTrip(outreq)
 ...
 // 修改后端响应
 if !p.modifyResponse(rw, res, outreq) {
 return
 }
 ...
 // 给前端返回响应
    err = p.copyResponse(rw, res.Body, p.flushInterval(res))
 ...
}

ReverseProxy は、リモート プロキシがバックエンドのオブジェクト参照を直接参照できない典型的なプロキシ モードの実装であるため、Transport を導入してバックエンド サービスにリモート アクセスすることにより、Transport をサブジェクトとして理解することができます。

ReverseProxy は次のように使用できます。

func proxy(c *gin.Context) {
    remote, err := url.Parse("https://yrunz.com")
 if err != nil {
 panic(err)
 }
 proxy := httputil.NewSingleHostReverseProxy(remote)
 proxy.Director = func(req *http.Request) {
 req.Header = c.Request.Header
 req.Host = remote.Host
 req.URL.Scheme = remote.Scheme
 req.URL.Host = remote.Host
 req.URL.Path = c.Param("proxyPath")
 }
 proxy.ServeHTTP(c.Writer, c.Request)
}
func main() {
 r := gin.Default()
 r.Any("/*proxyPath", proxy)
 r.Run(":8080")
}

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

  • リモート プロキシ(リモート プロキシ), リモート プロキシは、リモート マシン上でサービスを提供するオブジェクトに適しています. サービスは通常の関数呼び出しでは使用できず、リモート プロキシを介して行う必要があります. オントロジー オブジェクトには直接アクセスできないため、すべてのリモート プロキシ オブジェクトは、通常、オントロジー オブジェクトの参照を直接保持するのではなく、ネットワーク プロトコルを介してオントロジー オブジェクトにアクセスするためのリモート マシンのアドレスを保持します
  • 仮想プロキシ(仮想プロキシ), プログラムの設計には重量のサービス オブジェクトが存在することがよくあります. オブジェクト インスタンスが常に保持されている場合, 多くのシステム リソースを消費します. このとき, 仮想プロキシを使用して初期化を遅らせることができます.オブジェクトの。
  • オントロジー オブジェクトへのアクセスを制御するために使用される保護プロキシ。クライアント アクセスにアクセス許可の検証が必要なシナリオでよく使用されます。
  • キャッシュ プロキシ(キャッシュ プロキシ)。キャッシュ プロキシは、主にクライアントとオントロジー オブジェクトの間にキャッシュのレイヤーを追加して、オントロジー オブジェクトのアクセスを高速化します。これは、データベースに接続するシナリオで一般的です。
  • スマート参照(スマート参照), スマート参照はオントロジー オブジェクトのアクセスに追加のアクションを提供します. 一般的な実装は C++ のスマート ポインターであり, これはオブジェクトのアクセスにカウント関数を提供します. アクセスされたオブジェクトのカウントが0、オブジェクトは破棄されます。

長所と短所

アドバンテージ

  • クライアントの認識なしに、リモート アクセス、キャッシュの増加、セキュリティなどのアクセス オブジェクトを制御することが可能です。
  • 開閉原則に従ってクライアントとプロキシ オブジェクトを変更せずに新しいプロキシを追加できます。また、クライアントとプロキシを変更せずにプロキシ オブジェクトを置き換えることもできます。

欠点

  • リモート プロキシとして機能する場合、もう 1 つの転送のために、要求の遅延が影響を受けます。

他のスキーマへのリンク

装飾パターンとプロキシパターンは構造的に 類似性が高いが、両者が強調する点は異なる。前者はオントロジー オブジェクトに新しい機能を追加することを強調し、後者はオントロジー オブジェクトへのアクセス制御を強調します

写真付き記事

 記事の描き方はKeynoteで手描き風にご覧いただけます。 

参照する

[1] [Go実装] GoFの23のデザインパターンを実践する: SOLID Principle , Yuan Runzi 

[2] [Go 実装] GoF の 23 のデザイン パターンを実践する: 装飾パターン、元 Runzi 

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

[4] プロキシモード、 refactoringguru.cn

[5] リバースプロキシとは? 、クラウドフレア

 

フォローをクリックして、HUAWEI CLOUDの新技術について初めて学びましょう〜

{{o.name}}
{{m.name}}

おすすめ

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