GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Proxy」を学ぶ"
今回は、Pythonで実装した”Proxy”のサンプルアプリをGolangで実装し直してみました。
■ Proxy(プロキシ・パターン)
Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。
UML class and sequence diagram
UML class diagram
■ "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
インタフェースが、この役を努めます。
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
構造体が、この役を努めます。
// 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
構造体が、この役を努めます。
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
関数が、この役を努めます。
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"))
}