Adapter
「右と左の端子があっていなくて繋げないから、両端子を繋げられるオブジェクトを間に挟む」
目的
「本来の機能への委譲」
マンガでわかる Adapter
マンガでわかる Adapter #デザインパターン - Qiita
でざぱたんで覚える Adapter
ちびキャラは「アダプターたん」。喋るクラゲ「アダプティー」を従えた魔法使いの姫で、「なんでも結び付けてあげられる」のが得意技。既存実装(Adaptee)には手を加えず、継承や委譲で適合インターフェース分だけを足して呼び出し側と結び付ける変換係、というAdapterの役回りをそのまま擬人化している。
出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.047〜)
登場人物
- インタフェース: ユーザーがそういう名前で呼び出したいメソッドを持つ
- fireとかcreateとか単純な名前
- Adapter: 挟む主体。インタフェースを実装する
- Adaptee: 挟まれて対応される主体
やり方
2つある
1. インタフェースによる実装
- クライアントから呼び出したい名前のシグネチャをインタフェースに置く
- それを実装し、かつAdapteeを継承したAdapterクラスを作成する
- Adapter内でインタフェースのメソッドをOverrideして、Adapteeのメソッドを呼び出す
2. 委譲による実装
- クライアントから呼び出したい名前のシグネチャをインタフェースに置く
- それを継承したAdapterクラスを作成し、プロパティにAdapteeを持たせる
- スーパークラスのメソッドをオーバーライドして、自分が持つAdapteeのメソッドを呼び出す
クラス図
1.インタフェースによる実装
このサイトの実装(Sample1、継承によるクラスアダプタ)での対応関係:
classDiagram
class Print {
<<interface>>
+printWeak()
+printStrong()
}
class Banner {
-string String
+showWithParen()
+showWithAster()
}
class PrintBanner {
+printWeak()
+printStrong()
}
Print <|.. PrintBanner
Banner <|-- PrintBanner
2.クラスによる実装
メリット(用途)
既存クラスを改造する必要がなくなる。本来クラスは書き換えたらテストしなけばいけない。
テストの通った既存クラスをラップすることで追加機能分だけをテストすれば良くなる
既存クラスのソース内容は知る必要はない。仕様がわかればAdapterで新しいクラスを作成できる。
他との関連
Decorator
元のオブジェクトをラップするという点で同一。
ただしDecoratorの目的は「本来の機能の拡張」。意味合いは「右と左の端子があっているところに、同じく端子のあう機能を追加した中間装置をかます」
Java
1.インタフェースによる委譲の例
2.クラスによる委譲の例
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
public void printWeak() {
showWithParen();
}
public void printStrong() {
showWithAster();
}
}
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string) {
this.banner = new Banner(string);
}
public void printWeak() {
banner.showWithParen();
}
public void printStrong() {
banner.showWithAster();
}
}
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
Go
Java版Sample1(継承によるクラスアダプタ)を移植。Goに継承は無いので、構造体埋め込み(embedding)でメソッドを昇格させ見た目を似せるが、実体はcomposition(is-aは保証されない)である点に注意。
実行: go run ./GoF/patterns/Adapter/go
package main
import "fmt"
// Banner は「適合される側(Adaptee)」(Java版Banner.java)。
// ShowWithParen / ShowWithAster という、Print interfaceとは違う名前の
// メソッドしか持たない、既存の(手を入れたくない)部品という想定。
type Banner struct {
str string
}
// NewBanner はコンストラクタ相当(Java版の Banner(String string))。
func NewBanner(str string) *Banner {
return &Banner{str: str}
}
// ShowWithParen は文字列を丸括弧で囲んで表示する。
func (b *Banner) ShowWithParen() {
fmt.Printf("(%s)\n", b.str)
}
// ShowWithAster は文字列をアスタリスクで囲んで表示する。
func (b *Banner) ShowWithAster() {
fmt.Printf("*%s*\n", b.str)
}
package main
// Print は「適合させる側(Target)」のインタフェース(Java版Print.java)。
// 呼び出し側(main.go)はこのインタフェースだけを知っていればよく、
// 中身がBannerを使ったアダプタであることは意識しない。
//
// Goのinterfaceは構造的部分型で「暗黙実装」(implementsを書く必要がない)。
// これはStrategy版strategy.goと同じ、Javaとの一番わかりやすい違い。
type Print interface {
PrintWeak()
PrintStrong()
}
package main
// PrintBanner はAdapter(Java版PrintBanner.java、Sample1=クラスによるアダプタ)。
//
// Java版はこう書かれている:
//
// public class PrintBanner extends Banner implements Print {
// public PrintBanner(String string) { super(string); }
// public void printWeak() { showWithParen(); }
// public void printStrong() { showWithAster(); }
// }
//
// extends(実装の継承)でBannerの機能をそのまま受け継ぎ、implementsでPrintを
// 名乗る、という「継承1本」で完結する書き方=「クラスによるアダプタ」。
//
// Goには extends/implements という言語機能そのものがない:
// - interfaceの実装はimplementsを書かない暗黙実装(→print.go参照)
// - クラス継承(実装を丸ごと受け継ぐ仕組み)はGoに存在しない。あるのは
// 構造体埋め込み(embedding)だけで、これは継承ではなくcomposition
//
// そこでここでは *Banner を無名フィールドとして埋め込む。
// 埋め込むとBannerのメソッド(ShowWithParen/ShowWithAster)がPrintBannerに
// 「昇格(promoted)」し、p.ShowWithParen() のように直接呼べるようになる。
// 見た目はJavaのextendsにかなり近いが、正体は「PrintBannerが*Bannerを
// 1つ持っているだけ」のcomposition。is-a関係を型システムは保証しない
// (PrintBannerをBanner型として渡す、というJavaのアップキャスト相当のことは
// できない=あくまでPrintBannerはPrintを満たすだけの別の型)。
//
// PrintWeak/PrintStrongは、その埋め込まれたBannerのメソッドへ委譲する形で書く。
// この2つを持つことでPrintBannerはPrint interfaceを(暗黙に)満たす。
type PrintBanner struct {
*Banner
}
// NewPrintBanner はコンストラクタ相当(Java版の PrintBanner(String string))。
func NewPrintBanner(str string) *PrintBanner {
return &PrintBanner{Banner: NewBanner(str)}
}
func (p *PrintBanner) PrintWeak() {
p.ShowWithParen()
}
func (p *PrintBanner) PrintStrong() {
p.ShowWithAster()
}
package main
// 実行:
//
// go run ./GoF/patterns/Adapter/go
//
// Java版Sample1(クラスによるアダプタ)の移植。
// 呼び出し側はPrint interfaceしか知らない。中身がBannerを埋め込んだPrintBannerで
// あることは意識しない(Strategy版main.goと同じく、利用側は抽象にしか依存しない)。
func main() {
var p Print = NewPrintBanner("Hello")
p.PrintWeak()
p.PrintStrong()
}
PHP
<?php
/**
* ;継承によるAdapter
*/
/**
* パイ皿
* MakePieを使いたいけれども、
* インタフェースが思惑とずれる
* cookPieというインタフェースでパイを作成したい
*/
class PieDish
{
public function MakePie()
{
print "パイを作る";
}
}
// cookPieメソッドを持つアダプター
interface Adapter
{
public function cookPie();
}
/**
* アダプタを適用させた新しいパイ皿
*/
class PieDishWithAdapter extends PieDish implements Adapter
{
/**PieDishクラスのmakePieを呼ぶ */
public function cookPie()
{
$this->makePie();
}
}
$pieDish = new PieDishWithAdapter();
$pieDish->cookPie();
/**
* 委譲によるアダプター
*/
class PieDish2
{
public function makePie()
{
print "パイを作る";
}
}
/**
* インタフェースでなくabstract class
*/
abstract class Adapter2
{
public abstract function cookPie();
}
/**
* 先ほどの継承パターンと同様に
* 元のクラスを変更せずインタフェースをcookPieに適合させる
*/
class PieDishWithAdapter2 extends Adapter2
{
private $target;
public function __construct($target)
{
$this->target = $target;
}
public function cookPie()
{
$this->target->makePie();
}
}
$pieDish2 = new PieDishWithAdapter2(new PieDish2());
$pieDish2->cookPie();
// これまでのサンプルでもいいのだが、
// adapterパターンの真骨頂はここから
// オーブンはAdapterインタフェースしか受け付けない
// 元のクラスのままだと焼けないところを
// 元のクラスに何も手を入れずオーブンで焼けるようになった
class Oven
{
public static function bake(Adapter $adapter)
{
$adapter->cookPie();
}
}
class Oven2
{
public static function bake(Adapter2 $adapter2)
{
$adapter2->cookPie();
}
}
Oven::bake(new PieDishWithAdapter());
Oven2::bake(new PieDishWithAdapter2(new PieDish2()));
TypeScript
TypeScriptはextendsとimplementsを同時に使えるため、Java版のクラスアダプタをほぼそのまま移植できる。
実行: npx tsx GoF/patterns/Adapter/typescript/main.ts
// Banner: 「適合される側(Adaptee)」のクラス(Java版Banner.java)。
// showWithParen / showWithAster という、Printインタフェースとは違う名前の
// メソッドしか持たない、既存の(手を入れたくない)部品という想定。
export class Banner {
constructor(private readonly str: string) {}
showWithParen(): void {
console.log(`(${this.str})`);
}
showWithAster(): void {
console.log(`*${this.str}*`);
}
}
// Print: 「適合させる側(Target)」のインタフェース(Java版Print.java)。
// 呼び出し側(main.ts)はこのインタフェースだけを知っていればよい。
// (Builder版builder.tsのコメント参照: TypeScriptのinterfaceはJavaのinterfaceと
// ほぼ同じ感覚で書け、ConcreteBuilder同様PrintBanner側でimplementsを明示する)
export interface Print {
printWeak(): void;
printStrong(): void;
}
import { Banner } from "./banner";
import type { Print } from "./print";
// PrintBanner: Adapter(Java版PrintBanner.java、Sample1=クラスによるアダプタ)。
//
// Java版: `class PrintBanner extends Banner implements Print`
// =extends(実装の継承)でBannerの機能を受け継ぎ、implementsでPrintを名乗る
// 「継承1本」で完結する書き方。
//
// TypeScriptはJavaと同じく、単一継承のextendsとinterface実装のimplementsを
// 同時に使える。Go版(print_banner.go)はこの組み合わせを言語として持たず
// 構造体埋め込み(composition)で代用するしかないが、TS版はJava版Sample1の
// 書き方をほぼそのまま移植できる。
export class PrintBanner extends Banner implements Print {
constructor(str: string) {
super(str);
}
printWeak(): void {
this.showWithParen();
}
printStrong(): void {
this.showWithAster();
}
}
// Adapter パターン: 既存クラスBannerをPrintインタフェースに適合させる(Java版Sample1と同じ題材)
//
// 実行:
// npx tsx main.ts
//
// Java版Sample1(クラスによるアダプタ)の移植。呼び出し側はPrintインタフェースしか知らない。
import { PrintBanner } from "./print_banner";
import type { Print } from "./print";
function main(): void {
const p: Print = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
main();
Python
多重継承(class PrintBanner(Banner, Print))でJavaのextends+implementsに一番近い書き方にしている。
実行: python3 GoF/patterns/Adapter/python/main.py
"""Banner: 「適合される側(Adaptee)」のクラス(Java版Banner.java)。
show_with_paren / show_with_aster という、Printとは違う名前のメソッドしか
持たない、既存の(手を入れたくない)部品という想定。
"""
from __future__ import annotations
class Banner:
def __init__(self, str_: str) -> None:
self._str = str_
def show_with_paren(self) -> None:
print(f"({self._str})")
def show_with_aster(self) -> None:
print(f"*{self._str}*")
"""Print: 「適合させる側(Target)」(Java版Print.java)。
PythonにはJavaのinterfaceに相当する言語機能はないため、Builder/Strategy版と
同じく抽象基底クラス(ABC, abcモジュール)で表す。Goのような暗黙実装ではなく、
Java同様に継承(Printを継承)して満たす必要がある。
呼び出し側(main.py)はこの抽象だけを知っていればよい。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
class Print(ABC):
@abstractmethod
def print_weak(self) -> None: ...
@abstractmethod
def print_strong(self) -> None: ...
"""PrintBanner: Adapter(Java版PrintBanner.java、Sample1=クラスによるアダプタ)。
Java版: `class PrintBanner extends Banner implements Print`
=extends(実装の継承)でBannerの機能を受け継ぎ、implementsでPrintを名乗る
「継承1本」で完結する書き方。
Pythonは多重継承が言語として許されるので、Javaのextends+implementsに最も
近い書き方ができる: `class PrintBanner(Banner, Print)` とすれば、Bannerの
実装を継承しつつPrint(ABC)のサブクラスにもなれる(Go版のcomposition/構造体
埋め込みとも、TS版のextends+implementsとも違う第3の道)。
ただしPrint側の抽象メソッドはBannerとは別名(print_weak/print_strong)なので、
継承だけでは自動的には満たされず、ここで委譲の実装を書く必要がある。
"""
from __future__ import annotations
from banner import Banner
from print import Print
class PrintBanner(Banner, Print):
def __init__(self, str_: str) -> None:
super().__init__(str_)
def print_weak(self) -> None:
self.show_with_paren()
def print_strong(self) -> None:
self.show_with_aster()
"""Adapter パターン: 既存クラスBannerをPrintに適合させる(Java版Sample1と同じ題材)
実行:
python3 main.py
Java版Sample1(クラスによるアダプタ)の移植。呼び出し側はPrintの抽象しか知らない。
"""
from __future__ import annotations
from print import Print
from print_banner import PrintBanner
def main() -> None:
p: Print = PrintBanner("Hello")
p.print_weak()
p.print_strong()
if __name__ == "__main__":
main()