デザインパターン・Proxy(プロキシ)パターンの実装をご紹介します。
Proxyパターンを使用することで、ソースコードの変更工数の低減に貢献できます。
Proxyパターンとは
使用するライブラリの仕様を包括して処理を行うデザインパターンです。Proxyパターンのことをラッパーやラッピングとも呼ばれています。
用途・どんな時に使えるの?
主に以下の目的で使用します。
- ライブラリの仕様を隠蔽し、影響範囲を抑える
例えばあるライブラリを色んなクラスで使用した場合、ライブラリのIF仕様の変更が発生した際はライブラリを使用している箇所全てに影響され、ソースコードの変更規模が大きくなります。
IF仕様の変更は仕方ないことなのですが、変更になった際はできるだけ影響範囲を抑えたいものです。
そこで、ライブラリの仕様を吸収するクラスを作り、IF仕様変更になった際はそのクラスだけが影響を受けるように設計します。
これがProxyパターンの考え方です。
クラス図
My componentの範囲内にあるproxyクラス群はOther componentの範囲内にあるライブラリクラス仕様を吸収し、ライブラリクラスを使用するためのビジネスロジックを実装します。上記の図ではOther componentとして「network」「entity」「os」のライブラリがあり、それぞれにproxyクラスを用意した設計になります。
※上記の設計ですが少し冗長ですね…ここまでProxyクラスを分けなくても良いです。ソフトウェアの規模に合わせてクラス設計してください。
※ExProxyはただのmain関数であり、使用者です。
実装の解説
C++で実装したサンプルコードを解説します。
このサンプルコードはライブラリである「other_network」「other_entity」「other_os」のクラスをproxyクラス経由で使用します。
■ other_network.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include "other_network.h" namespace hukusuke { other_network::other_network(){} other_network::~other_network(){} /* get_token class. */ void other_network::get_token(string& str) { str = "other network get_token."; } /* send_data class. */ void other_network::send_data( const string str ) { LOG_OUTPUT("%s", str.c_str()); } } |
■ other_network.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#pragma once #include "logout.h" #include <string> using namespace std; namespace hukusuke { class other_network { public: other_network(); ~other_network(); /* Public method.*/ void get_token( string& str ); void send_data(const string str ); }; } |
■ other_entity.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include "other_entity.h" namespace hukusuke { other_entity::other_entity(){} other_entity::~other_entity(){} /* write_data class. */ void other_entity::write_data(const string str) { LOG_OUTPUT("%s", str.c_str()); } /* read_data class. */ void other_entity::read_data(string& str) { str = "other entity read_data."; } } |
■ other_entity.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#pragma once #include "logout.h" #include <string> using namespace std; namespace hukusuke { class other_entity { public: other_entity(); ~other_entity(); /* Public method.*/ void write_data( const string str ); void read_data ( string& str ); }; } |
■ other_os.cpp
1 2 3 4 5 6 7 8 9 10 11 |
#include "other_os.h" namespace hukusuke { other_os::other_os(){} other_os::~other_os(){} /* request class. */ void other_os::get_rtc(string& str) { str = "other os get_rtc."; } } |
■ other_os.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#pragma once #include "logout.h" #include <string> using namespace std; namespace hukusuke { class other_os { public: other_os(); ~other_os(); /* Public method.*/ void get_rtc( string& str ); }; } |
「proxy_network」「proxy_entity」「proxy_os」クラスがそれぞれのotherクラスを吸収するために、otherクラスのヘッダーをcppファイル内でインクルードします。こうすることでotherクラスの仕様をproxy_XXXクラス内に隠蔽することができます。
proxy_XXXクラス内にはotherクラスを使用するためのビジネスロジックを実装します。
使用者側へproxy_XXXクラスのIFを提供するためにはInterfaceクラスであるproxy_requestクラスを継承します。
■ proxy_network.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include "proxy_network.h" #include "other_network.h" namespace hukusuke { /*--------------------------------*/ /* proxy_network_get_token. */ /*--------------------------------*/ void proxy_network_get_token::act_request() { string str = ""; other_network other; other.get_token(str); str_ = str; } /*--------------------------------*/ /* proxy_network_send_data. */ /*--------------------------------*/ void proxy_network_send_data::act_request() { other_network other; other.send_data(str_); } } |
■ proxy_network.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#pragma once #include "logout.h" #include "proxy_request.h" #include <string> using namespace std; namespace hukusuke { /*--------------------------------*/ /* proxy_network. */ /*--------------------------------*/ class proxy_network : public proxy_request { public: proxy_network() {}; ~proxy_network() {}; protected: void act_request() {}; }; /*--------------------------------*/ /* proxy_network_get_token. */ /*--------------------------------*/ class proxy_network_get_token : public proxy_network { public: proxy_network_get_token() {}; ~proxy_network_get_token() {}; string get() { return str_; }; private: void act_request(); string str_; }; /*--------------------------------*/ /* proxy_network_send_data. */ /*--------------------------------*/ class proxy_network_send_data : public proxy_network { public: proxy_network_send_data() {}; ~proxy_network_send_data() {}; void set(const string str) { str_ = str; }; private: void act_request(); string str_; }; } |
■ proxy_entity.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "proxy_entity.h" #include "other_entity.h" namespace hukusuke { /*--------------------------------*/ /* proxy_entity_write_data. */ /*--------------------------------*/ void proxy_entity_write_data::act_request() { other_entity other; other.write_data(str_); } /*--------------------------------*/ /* proxy_entity_read_data. */ /*--------------------------------*/ void proxy_entity_read_data::act_request() { string str = ""; other_entity other; other.read_data(str); str_ = str; } } |
■ proxy_entity.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#pragma once #include "logout.h" #include "proxy_request.h" #include <string> using namespace std; namespace hukusuke { /*--------------------------------*/ /* proxy_entity. */ /*--------------------------------*/ class proxy_entity : public proxy_request { public: proxy_entity() {}; ~proxy_entity() {}; }; /*--------------------------------*/ /* proxy_entity_write_data. */ /*--------------------------------*/ class proxy_entity_write_data : public proxy_entity { public: proxy_entity_write_data() {}; ~proxy_entity_write_data() {}; void set(const string str) { str_ = str; }; private: void act_request(); string str_; }; /*--------------------------------*/ /* proxy_entity_read_data. */ /*--------------------------------*/ class proxy_entity_read_data : public proxy_entity { public: proxy_entity_read_data() {}; ~proxy_entity_read_data() {}; string get() { return str_; }; private: void act_request(); string str_; }; } |
■ proxy_os.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "proxy_os.h" #include "other_os.h" namespace hukusuke { /*--------------------------------*/ /* proxy_os_get_rtc. */ /*--------------------------------*/ void proxy_os_get_rtc::act_request() { string str = ""; other_os other; other.get_rtc(str); str_ = str; } } |
■ proxy_os.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#pragma once #include "logout.h" #include "proxy_request.h" #include <string> using namespace std; namespace hukusuke { /*--------------------------------*/ /* proxy_os. */ /*--------------------------------*/ class proxy_os : public proxy_request { public: proxy_os() {}; ~proxy_os(){}; }; /*--------------------------------*/ /* proxy_os_get_rtc. */ /*--------------------------------*/ class proxy_os_get_rtc : public proxy_os { public: proxy_os_get_rtc() {}; ~proxy_os_get_rtc() {}; string get() { return str_; }; private: void act_request(); string str_; }; } |
■ proxy_request.cpp
1 2 3 4 5 |
#include "proxy_request.h" namespace hukusuke { // Nothing. } |
■ proxy_request.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#pragma once #include "logout.h" using namespace std; namespace hukusuke { class proxy_request { public: proxy_request() {}; ~proxy_request(){}; protected: void request_other(proxy_request& request){ request.act_request(); }; private: virtual void act_request() {}; }; } |
なお今回の例は使用者側の窓口としてシングルトンクラスを設けています。シングルトンクラスを設けることでproxy_requestクラスとは別にInterfaceクラスが必要になっても、シングルトンクラスがInterfaceクラスを継承するだけで良いので、使用者側へ影響を与えないようにしています。
1つのクラスを窓口として使用するデザインパターンをFacadeパターンと言います。
■ proxy.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include "proxy.h" namespace hukusuke { proxy* proxy::instance_ = NULL; mutex proxy::mtx_; /* Get singleton instance. */ proxy& proxy::get_instance() { if (!instance_) { lock_guard<std::mutex> grd(mtx_); // Double-Checked Locking if (!instance_) { proxy* obj = new proxy(); instance_ = obj; } else {} } else {} return *instance_; } /* request method. */ void proxy::request(proxy_request& request){ request_other(request); } } |
■ proxy.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#pragma once #include "logout.h" #include "proxy_request.h" #include <mutex> using namespace std; namespace hukusuke { class proxy : public proxy_request { public: // Get singleton instance. // @return not NULL on instance, NULL on error. static proxy& get_instance(); void request(proxy_request& request); private: // Constructor. proxy() {}; proxy(const proxy&) {}; // default. proxy& operator=(const proxy&) {}; // default. // Destructor. ~proxy() {}; private: static proxy* instance_; static mutex mtx_; }; } |
使い方は以下の通りです。
other_XXXクラスの仕様を隠蔽したproxy_XXXクラスのインスタンスを生成して、シングルトンクラスにセットすることで操作できます。
■ ExProxy.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// ExProxy.cpp : コンソール アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include "logout.h" #include "proxy_network.h" #include "proxy_entity.h" #include "proxy_os.h" #include "proxy.h" #include <string> using namespace std; using namespace hukusuke; int main(void) { LOG_OUTPUT("Start."); // proxyインスタンスを生成. proxy_network_get_token network_get_token; proxy_network_send_data network_send_data; proxy_entity_write_data entity_write_data; proxy_entity_read_data entity_read_data; proxy_os_get_rtc os_get_rtc; proxy& tmp = proxy::get_instance(); // network_get_token. tmp.request(network_get_token); LOG_OUTPUT("%s", network_get_token.get().c_str()); // network_send_data. network_send_data.set("other network send_data."); tmp.request(network_send_data); // entity_write_data. entity_write_data.set("other entity write_data."); tmp.request(entity_write_data); // entity_read_data. tmp.request(entity_read_data); LOG_OUTPUT("%s", entity_read_data.get().c_str()); // os_get_rtc. tmp.request(os_get_rtc); LOG_OUTPUT("%s", os_get_rtc.get().c_str()); LOG_OUTPUT("Terminate."); return 0; } |
サンプルコード
Visual C++ 2017で作成したサンプルコードをGitHubで公開しています。
GitHub – ExProxy
実行結果は以下の通りです。
proxy経由でotherクラスを使用していることが確認できます。
[ 16][ main] Start. [ 29][ main] other network get_token. [ 13][ send_data] other network send_data. [ 9][write_data] other entity write_data. [ 41][ main] other entity read_data. [ 45][ main] other os get_rtc. [ 47][ main] Terminate.
参考
以下のサイトを参考にさせていただきました。
TECHSCORE(テックスコア) – 21.Proxyパターン