はじめに
この投稿は 増補改訂版 Java言語で学ぶデザインパターン入門 に沿って学んだことを章ごとにまとめるものです.
今回は第2章Adapterパターンについてまとめます.
Adapter とは
Adapter とは,直訳すると「適合させるもの」です.
ここでは,既に用意されているもの・提供されているものが存在しているのだけど,そのままでは使えないものを,必要な形に変換するもののことを指します.
求めているものととても近い実装が用意されているけど,微妙に生じているズレを埋めるために使用します.
AdapterパターンのことをWrapperパターンとも言います.
Adapterパターンには継承を使用するものと,委譲を使用するものがあります.
継承を使用するAdapterパターンの概要
継承を使用するAdapterパターンは下記のように表せます.
Target が今必要としているメソッドを定義したインタフェースです.
Adaptee が今提供されているクラスです.
Adaptee を Target に適用させるのが Adapter の役目です.
継承を使用するAdapterパターンでは,Adapter は既に提供されている Adaptee クラスのサブクラスになります.
継承を使用するAdapterパターンの実装
Adapteeクラス
まずは Adaptee として使用するクラスを実装します.
/// 軍艦クラス
class WarShip {
/// 艦種
private let type: String
/// 艦級
private let `class`: String
private let number: Int
private let name: String
init(_ type: String, _ `class`: String, _ number: Int, _ name: String) {
self.type = type
self.`class` = `class`
self.number = number
self.name = name
}
public func getData() -> String {
return `class` + "型" + type + "\(number)番艦"
}
public func getName() -> String {
return name
}
}
今回は軍艦クラスを作成しました.本人は至って真面目です.軍艦周りの英語は自信がないので間違っていても見逃してください.
艦種,艦級,何番艦か,名前のプライベートな情報を持ち, getData()
で名前以外の情報を, getName()
で名前を返すメソッドを持っています.
Targetクラス
次にTargetクラスを実装します.
今回は,自己紹介をするTargetクラスを作成します.
protocol PrintProfileType {
func sayProfile()
}
シンプルにプロフィールを言うメソッドを持つプロトコルを作成しました.
Adapterクラス
最後に継承を使用してAdapterクラスを作成します.
final class PrintWarShipProfile: WarShip, PrintProfileType {
func sayProfile() {
print(getData() + "の" + getName() + "です!")
}
}
使用例
それでは早速自己紹介をしてもらおうと思います.
let hiei: PrintProfileType = PrintWarShipProfile("戦艦", "金剛", 2, "比叡")
hiei.sayProfile()
出力は
金剛型戦艦2番艦の比叡です!
となります.
委譲を使用するAdapterパターンの概要
先ほどはAdapteeクラスを継承し,Targetインタフェース(実装ではプロトコル)を適用してAdapterパターンを実装しましたが,委譲を使用して実装することもできます.
Target も Adaptee もクラスで実装したい場合に,ひとつのクラスしか継承できない(単一継承)言語を使用する場合に委譲を使えばAdapterパターンを実装することができます.
継承を使用したパターンでは親クラスの実装を上手く使って Target に適合しましたが,委譲を使用するパターンではAdapterクラスがインスタンスとしてTargetクラスを持った状態になります.
委譲を使用するAdapterパターンの実装
Adapteeクラスは同じものを使用します.
Targetクラス
class BasePrintProfile {
public func sayProfile() {
fatalError("サブクラスで実装してください")
}
}
少し名前を変えて空のメソッドを実装しました.
Adapterクラス
final class PrintWarShipProfile: BasePrintProfile {
private let warShip: WarShip
init(_ warShip: WarShip) {
self.warShip = warShip
}
override public func sayProfile() {
print(warShip.getData() + "の" + warShip.getName() + "です!")
}
}
使用例
挨拶させてみました.
let hiei = WarShip("戦艦", "金剛", 2, "比叡")
let printProfile: BasePrintProfile = PrintWarShipProfile(hiei)
printProfile.sayProfile()
使用例もほとんど変わりません.
Adapterパターンのメリット
今回の実装例ではかなり簡単な例になったしまったので,Adapterパターンを使用するメリットはあまり伝わっていないかもしれませんが,もっと複雑な実装に適用すると以下のようなメリットを感じられるはずです.
テストが楽になる
Adapterパターンでは,既存のAdapteeクラスを使用しています.
Adapteeクラスとして使用するクラスが十分にテストされた信頼できる実装なのであれば,不具合が発生した場合にはAdapterクラスの確認をすれば良くなります.
不具合の特定がしやすくなるととっても楽になりますよね.
Adapteeパターンの実装を知らなくても使用できる
中身の詳しい実装を知らないクラスをAdapteeクラスとみなしてAdapterパターンを使用することもできます.
AdapterパターンではAdapterクラスではAdapteeクラスの中身は触らずに必要な形にカスタマイズすることが可能です.
そのため,必要としているものに近い機能を持つフレームワークやライブラリを,自分が実装したクラスのサブクラスになるようにラップしたりすることができます.
十分にテストされた機能を自分が欲しい形にして使用できるなんて素敵だと思います.
思ったこと
継承と委譲を使用した実装がありましたが,それぞれ両方とも使用例ではTargetの型で宣言し, sayProfile()
メソッドを持っているインスタンス,という情報だけを持たせました.
それでも継承を使用したAdapterパターンでは,キャストしてしまえば親クラスのメソッドを直接使用することができてしまうため,Targetクラスの利用者にAdapteeクラスのメソッドを使用されたくない場合にはあまり良くないのかなぁ,と思いました.
その点委譲を使用したAdapterパターンでは,初期化の仕方に気を使えばAdapteeクラスを完全に隠すことができるので,委譲を使った方がいいのかなぁと思いました.
また,Swift だったら extension
で適合させるプロトコルを増やせるので,普段から継承を使用したパターンと同じような感覚で実装していそうです.
Swiftを触っているとAdapterパターンは自然と使っているのかもしれないです.
Adapterパターンはあまりにも当たり前な考え方かもしれませんが,新しいクラスを実装しようと思った時に少し意識してみようかなぁと思っています.
まとめ
- Adapterパターンは既存の実装 Adaptee を,自分が使用したい形(Target)に Adapt させるために使用する
- 実装を減すことができたり,既存の実装を使用することで不具合の特定がしやすくなることがある