1
1

More than 3 years have passed since last update.

Golangで、デザインパターン「Proxy」を学ぶ

Last updated at Posted at 2020-03-27

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Proxy」を学ぶ"

今回は、Pythonで実装した”Proxy”のサンプルアプリをGolangで実装し直してみました。

■ Proxy(プロキシ・パターン)

Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。

UML class and sequence diagram

W3sDesign_Proxy_Design_Pattern_UML.jpg

UML class diagram

proxy.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "Proxy"のサンプルプログラム

実際に、Proxyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

  • まず、プリンタの代理人として、"Alice"を受け付ける
  • プリンタ本体が使用できるよう準備する(所要時間は、約10秒)
  • 実際に、プリンタを使って、以下のプリントアウト処理"Nice to meet you"を実施する。
  • つぎに、プリンタの代理人として、"Bob"を受け付ける
  • 実際に、プリンタを使って、以下のプリントアウト処理"Hello, World"を実施する。

<プリントアウト処理>
(1) 現在のプリンタの代理人を、プリンタ使用者として扱う
(2) 前後に"==="で囲って、プリンタ使用者の名前を表示する
(3) 文字列を表示する。

$ go run Main.go 
Printer代理人の名前は現在(Alice)です
Printerのインスタンス(Alice)を作成中..........完了
=== Printer使用者(Alice) ===
Nice to meet you

Printer代理人の名前は現在(Bob)です
=== Printer使用者(Bob) ===
Hello, World

ここでの確認ポイントは、Printerのインスタンス(xxx)を作成中..........完了"の箇所が、一度しか表示されていない点です。
実際のサンプルプログラムでは、複雑なオブジェクトのインスタンスを一度だけ生成できるよう、Singletonパターンを活用しています。

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Proxy

  • ディレクトリ構成
.
├── Main.go
└── proxy
    └── printer_proxy.go

(1) Subject(主体)の役

Proxy役とRealSubject役を同一視するためのインタフェースを定めます。
Subject役があるおかげで、Client役は、Proxy役とRealProxy役の違いを意識する必要がありません。
サンプルプログラムでは、Printableインタフェースが、この役を努めます。

proxy/printer_proxy.go
package proxy

import (
    "fmt"
    "time"
)

// Printable is inteface
type Printable interface {
    SetPrinterName(name string)
    GetPrinterName() string
    MyPrint(str string)
}

(2) Proxy(代理人)の役

Proxy役はClient役からの要求をできるだけ処理します。
もしも、自分だけで処理できなかったら、Proxy役はRealSubject役に仕事をお任せします。
Proxy役は、本当にRealSubject役が必要になってからRealSubject役を生成します。
Proxy役は、Subject役で定められているインタフェースを実装しています。
サンプルプログラムでは、PrinterProxy構造体が、この役を努めます。

proxy/printer_proxy.go
// PrinterProxy is struct
type PrinterProxy struct {
    name string
    real printer
}

// NewPrinterProxy func for initializing PrinterProxy
func NewPrinterProxy(name string) *PrinterProxy {
    return &PrinterProxy{
        name: name,
    }
}

// SetPrinterName func for setting name in printer
func (p *PrinterProxy) SetPrinterName(name string) {
    p.name = name
}

// GetPrinterName func for fetching name
func (p *PrinterProxy) GetPrinterName() string {
    return p.name
}

// MyPrint func for printing something
func (p *PrinterProxy) MyPrint(str string) {
    real := getPrinter(p.name)
    real.MyPrint(str)
}

(3) RealSubject(実際の主体)の役

「代理人」のProxy役では手が負えなくなったときに登場するのが、「本人」のRealSubject役です。
この役も、Proxy役と同じくSubject役で定められているインタフェースを実装しています。
サンプルプログラムでは、printer構造体が、この役を努めます。

proxy/printer_proxy.go
type printer struct {
    name string
}

var instance *printer

func getPrinter(name string) *printer {
    if instance == nil {
        instance = newPrinter(name)
    } else {
        instance.name = name
    }
    return instance
}

func newPrinter(name string) *printer {
    prt := &printer{name: name}
    prt.heavyJob(fmt.Sprintf("Printerのインスタンス(%s)を作成中", name))
    return prt
}

func (p *printer) SetPrinterName(name string) {
    p.name = name
}

func (p *printer) GetPrinterName() string {
    return p.name
}

func (p *printer) MyPrint(str string) {
    fmt.Printf("=== Printer使用者(%s) ===\n", p.name)
    fmt.Println(str)
    fmt.Println("")
}

func (p *printer) heavyJob(msg string) {
    fmt.Printf(msg)
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second * 1)
        fmt.Printf(".")
    }
    fmt.Println("完了")
}

(4) Client(依頼人)の役

Proxy役を利用する役です。
サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
package main

import (
    "fmt"

    "./proxy"
)

func startMain(p proxy.Printable) {
    fmt.Printf("Printer代理人の名前は現在(%s)です\n", p.GetPrinterName())
    p.MyPrint("Nice to meet you")
    p.SetPrinterName("Bob")
    fmt.Printf("Printer代理人の名前は現在(%s)です\n", p.GetPrinterName())
    p.MyPrint("Hello, World")
}

func main() {
    startMain(proxy.NewPrinterProxy("Alice"))
}

■ 参考URL

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1