はじめに
「C言語でトライ! デザインパターン」
今回はProxyパターンです。インターフェイスクラスに中継役を設けて、アクセス制限や負荷分散等色々な用途を持ってもらおう!というデザインです。
ネットワーク系の仕事をされている方は、そのままProxyサーバーを思い浮かべてもらえばいいかと思います。
デザインパターン一覧
作成したライブラリパッケージの説明
公開コードサンプルはこちら, ライブラリパッケージはこちら
その4. Proxy パターン
###このデザインの魅力は、Proxyクラスの自由度
wikipediaの説明は以下となります。
Proxy パターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
そうなんですよね、これというものはないんですよね。
個人的に使いやすそうなのは非同期処理による負荷軽減ですが、Proxyサーバーでもアクセス制限や検閲、負荷分散など色々な用途で用いられています。
Proxyパターンのライブラリ、う~ん何かしっくりこない。
で調べた記事の中でとても参考になるものがありました。
ProxyパターンとProxyクラスと黒魔術
本記事の先輩と後輩の発言を抜粋。
先:「interfaceならなんでも使えるんだがな。ある種のフレームワークでinterfaceのメソッドをProxyすると動きを追うのが捗るぞ。」
そうだ、Proxyサーバーの魅力はどんなものでもラップして好きに内部で処理追加が出来るところなんですよね!
そう考えてみると、よくやっています。関数のラップ、差し替え。
ということで、本パターンについてはライブラリでなく、関数を利用する側が何も変更せずに、関数の中身を差し替える方法について紹介します。
自前ソースに対するラップ
例えばこんなAPIがあったとします。
void publisher_publish(int content_id, int publish_type, void * detail) {
PublishContent content = publisher_get_content(content_id);
if(!content) {
DEBUG_ERRPRINT("invalid content_id\n");
return;
}
publish_content_publish(content, publish_type, detail);
}
これを、以下のようにしてコンパイルすれば使う側は何もせずにちょっと処理を付け足します。
void publisher_publish(int content_id, int publish_type, void * detail) {
///ラップ処理
publisher_publish_(content_id, publish_type, detail);
}
//こちらは元の関数の名称に_を付けただけ
void publisher_publish_(int content_id, int publish_type, void * detail) {
PublishContent content = publisher_get_content(content_id);
if(!content) {
DEBUG_ERRPRINT("invalid content_id\n");
return;
}
publish_content_publish(content, publish_type, detail);
}
自分はコードの見にくいプログラムを触る際、ドキュメントも何もないときは、シーケンスを探るためにこんなログ入れをしたりします。
//defineでダミーに差し替え、ファイル名、ライン名を追加して元関数コール
#define publisher_publish(content_id, publish_type, detail) publisher_publish_(__FILE__, __LINE__, content_id, publish_type, detail)
//元関数内でログ表示。returnがvoidならpublisher_publishのdefineでログを出してもいいです。
void publisher_publish_(char *file, int line, int content_id, int publish_type, void * detail) {
printf("call from [%s:%d]\n", file, line);
PublishContent content = publisher_get_content(content_id);
if(!content) {
DEBUG_ERRPRINT("invalid content_id\n");
return;
}
publish_content_publish(content, publish_type, detail);
}
地道だけど私には結構役に立ってます。
(なんとなくシーケンスが見えてくるので)
ライブラリの差し替え
Cでのインターフェイスといえばライブラリです。ライブラリには2種類静的ライブラリ(.a)と共有ライブラリ(Linuxなら.so)があります。
違いとしては、
静的ライブラリ-プログラム内の中の一部としてコンパイルされるので、バイナリが大きくなる代わりに動作時にライブラリがいらない。
共有ライブラリ-プログラムにはこのライブラリをリンクするという情報だけ持つので、バイナリは小さくなる代わりに動作時にライブラリが必要。
この共有ライブラリの特徴、「動作時にライブラリが必要」をうまく利用すると、どのライブラリに定義されている関数を実行させるかある程度コントロールが出来ます。
そのロードしたライブラリ内でdlopen
/dlsym
して元のライブラリのAPIを読み込み実行すれば、プロキシパターンの完成です。
ライブラリ読み込み順番は正確に覚えていないのですが、とりあえず最近知ったLD_PRELOADが凶悪過ぎるということを知ったので、そのことと一緒に合わせて整理します。
⇒しました
サンプルコード
以下に作成しました。sample1がここでの例、sample2がLD_PRELOADの例になります。
https://github.com/developer-kikikaikai/design_patter_for_c_appendix/tree/master/proxy
2018/08/02 リポジトリ分離によりパスを差し替え
最後に
Proxyパターン、危うく目的と手段が入れ替わるところでした。
このパターンの楽しい所は「使い手の使い方を変えずに色々な用途に代替えすること」。
負荷分散もよし、デバッグ切り替えもよし、アクセス制限もよし。
そしてこういった使い手を変えずに中身を変える手法、C言語はかなりお得意なんですよね。
関数ベースで言語なので、役割分担するので、相手に影響ないよう、今までのようなインターフェイスになるようにってのがまず考えることの一つだったりするので。
このラップした関数内で新しいこと、別のことをしようという考え方が、そのままProxyパターンでやりたいことだったりするんですよね。
##参考
ProxyパターンとProxyクラスと黒魔術
http://d.hatena.ne.jp/Nagise/20111218/1324182111
コンパイル済みのライブラリ差し替え方法 LD_PRELOAD
https://qiita.com/koara-local/items/221508d4691c4502adce