本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にアダプターパターン(Adapter)をC++で実装してみました。
【目次】
〇アダプターパターンとは
・クラスによるAdapterパターン(継承を使ったもの)
・インスタンスによるAdapterパターン(委譲を使ったもの)
〇C++で継承をつかったアダプターパターンを実装
・Bannerクラス
・PrintBannerクラス
・main関数
〇C++で委譲をつかったアダプターパターンを実装
・Bannerクラス
・Printクラス
・PrintBannerクラス
・main関数
〇まとめ
アダプターパターン(Adapter)とは
アダプターパターンとは、「すでに提供されているもの」と「必要なもの」の間にある「ずれ」を埋めて再利用するためのデザインパターンです。
提供されたAPIなどを手直しするために使われます。
アダプターパターンには、以下の2種類の実装方法があります。
- クラスによるAdapterパターン(継承を使ったもの)
- インスタンスによるAdapterパターン(委譲を使ったもの)
クラスによるAdapterパターン(継承を使ったもの)
継承を使ったアダプターパターンは以下のようなクラス図になります。
継承のAdapterパターンは、Adaptee役のメソッドがほぼそのまま使えそうなときに使われます。
Target(対象)
今必要となっているメソッドを定める役で、インタフェースとなっています。
C++では、二重継承を非推奨としているため、継承を使ったアダプターパターンでは、Targetは省略されることが多いです。
Client(依頼者)
Target役(省略する際はAdapter役)のメソッドを使って仕事をする役です。
main関数のようなAPIを呼び出すやつだと思ってもらえば大丈夫です。
Adaptee(適合される側)
すでに用意しているメソッドをもっている役です。要するに使いたいAPIを持っているクラスのことですね。
Target役が使いたいAPIがAdaptee役ですでに実装していたら、Adapter役は必要ないんですけどね…。
Adapter
Adapterパターンの主役です。Adaptee役のメソッドを使用して、Targetが求めているメソッドを作り出すやくですね。
インスタンスによるAdapterパターン(委譲を使ったもの)
委譲を使ったアダプターパターンは以下のようなクラス図になります。
委譲を使ったAdapterパターンは、Adaptee役のメソッドを呼び出す前に手を加えたいことが多い場合に有効です。
Adaptee役のメソッドをそのまま呼び出したい場合は、メソッドを再定義する必要があり、コード量が増えるというデメリットもあります。
C++では多重継承(インタフェースと親クラスの同時継承)が推奨されていないこともあり、委譲を使ったAdapterパターンのほうがよくつかわれるような気がします。
C++で継承をつかったアダプターパターンを実装
今回は、与えられた文字列を
(Hello)
*Hello*
のように表示する簡単なものをAdapterパターンを使用して実装していきます。
クラス構成は以下の通りです。
二重継承をしないようにTargetクラスを省略した実装にしています。
Bannerクラス
Bannerクラスは、事前に用意されていたAPIクラスだと思ってください。
■ヘッダファイル
#ifndef BANNER_H
#define BANNER_H
#include <iostream>
class Banner{
public:
Banner(std::string string);
virtual ~Banner();
void showWithParen();
void showWithAster();
private:
std::string m_string;
};
#endif // BANNER_H
■ソースファイル
#include "Banner.h"
Banner::Banner(std::string string)
: m_string(string)
{
}
Banner::~Banner(){}
void Banner::showWithParen(){
std::cout << "(" << m_string << ")" << std::endl;
}
void Banner::showWithAster(){
std::cout << "*" << m_string << "*" << std::endl;
}
PrintBannerクラス
PrintBannerクラスがアダプターの役目を果たします。
今回はm単純な実装のためBannerクラスのAPIをそのまま呼び出すだけになります。
■ヘッダファイル
#ifndef PRINTBANNER_H
#define PRINTBANNER_H
#include "Banner.h"
class PrintBanner : public Banner{
public:
PrintBanner(std::string string);
~PrintBanner();
void printWeak();
void printStrong();
};
#endif // PRINTBANNER_H
■ソースファイル
#include "PrintBanner.h"
PrintBanner::PrintBanner(std::string string)
: Banner(string)
{
}
PrintBanner::~PrintBanner(){}
void PrintBanner::printWeak(){
showWithParen();
}
void PrintBanner::printStrong(){
showWithAster();
}
main関数
ここまで作ってきたAdapter役のPrintBannerクラスを使って、"Adapter Sample"という文字列を括弧付きと*ではさんで表示します。
■ソースファイル
#include "PrintBanner.h"
int main(void){
PrintBanner printBanner("Adapter Sample");
printBanner.printWeak();
printBanner.printStrong();
return 0;
}
■実行結果
(Adapter Sample)
*Adapter Sample*
C++で委譲をつかったアダプターパターンを実装
委譲を使ったアダプターパターンのサンプルプログラムのクラス構成は以下の通りです。
Bannerクラス
■ヘッダファイル
#ifndef BANNER_H
#define BANNER_H
#include <iostream>
class Banner{
public:
Banner(std::string string);
virtual ~Banner();
void showWithParen();
void showWithAster();
private:
std::string m_string;
};
#endif // BANNER_H
■ソースファイル
#include "Banner.h"
Banner::Banner(std::string string)
: m_string(string)
{
}
Banner::~Banner(){}
void Banner::showWithParen(){
std::cout << "(" << m_string << ")" << std::endl;
}
void Banner::showWithAster(){
std::cout << "*" << m_string << "*" << std::endl;
}
Printクラス
JavaでいうインタフェースはC++では抽象クラスとして実装していきます。
■ヘッダファイル
#ifndef PRINT_H
#define PRINT_H
class Print{
protected:
virtual ~Print(){};
virtual void printWeak()=0;
virtual void printStrong()=0;
};
#endif // PRINT_H
PrintBannerクラス
■ヘッダファイル
#ifndef PRINTBANNER_H
#define PRINTBANNER_H
#include <iostream>
#include "Print.h"
class Banner;
class PrintBanner : public Print{
public:
PrintBanner(std::string string);
virtual ~PrintBanner();
void printWeak() override;
void printStrong() override;
private:
Banner *m_Banner;
};
#endif // PRINTBANNER_H
■ソースファイル
#include "PrintBanner.h"
#include "Banner.h"
#include "Print.h"
PrintBanner::PrintBanner(std::string string)
{
m_Banner = new Banner(string);
}
PrintBanner::~PrintBanner(){
delete m_Banner;
}
void PrintBanner::printWeak(){
m_Banner->showWithParen();
}
void PrintBanner::printStrong(){
m_Banner->showWithAster();
}
main関数
■ソースファイル
#include "PrintBanner.h"
int main(void){
PrintBanner printBanner("Adapter Sample");
printBanner.printWeak();
printBanner.printStrong();
return 0;
}
■実行結果
(Adapter Sample)
*Adapter Sample*
まとめ
個人開発ではなく、会社などで集団で開発する場合は、いきなりAPIの使用が変更することが多々あります。
いきなりの変更でも困らないようにアダプターパターンを使って、修正箇所ができる限り少なくなるようなプログラムを作っていきましょう。