📱

GoF デザインパターン 入門 ~振る舞いに関するパターン (Behavioral Patterns)編~

に公開

Chain of Responsibility(責任連鎖)パターン

Chain of Responsibilityパターンは責任者を鎖で繋げ、誰がどの段階で処理するかを表現する
例)生徒の質問に対して担任、学年主任、職員会議で判断することを考える
担任、学年主任、職員会議で決められる決裁権が異なる

data class Level(val value: Int) {
    fun lessThan(other: Level): Boolean = this.value < other.value
}

data class Question(val content: String, val level: Level)

抽象的な責任者
setNextが重要でResponsibleを返すことでメソッドチェーンを可能にする

abstract class Responsible(
    val responsiblePerson: String
) {
    private var next: Responsible? = null

    fun setNext(next: Responsible): Responsible {
        this.next = next
        return next
    }

    fun putQuestion(question: Question) {
        if (beAbleToJudge(question)) {
            judge(question)
        } else {
            if (next != null) {
                next!!.putQuestion(question)
            } else {
                println("誰にも判断出来ない")
            }
        }
    }

    protected abstract fun beAbleToJudge(question: Question): Boolean
    protected abstract fun judge(question: Question)
}

具体的な責任者

class ClassTeacher(responsiblePerson: String) : Responsible(responsiblePerson) {
    private val responsibleLevel = Level(2)
    override fun beAbleToJudge(question: Question): Boolean {
        return question.level.lessThan(responsibleLevel)
    }

    override fun judge(question: Question) {
        println("$responsiblePerson が判定: 「${question.content}」はOKです(担任レベル)")
    }
}

class ChiefTeacher(responsiblePerson: String) : Responsible(responsiblePerson) {
    private val responsibleLevel = Level(3)
    override fun beAbleToJudge(question: Question): Boolean =
        question.level.lessThan(responsibleLevel)

    override fun judge(question: Question) {
        println("$responsiblePerson が判定: 「${question.content}」はOKです(学年主任レベル)")
    }
}

class StaffMeeting(responsiblePerson: String) : Responsible(responsiblePerson) {
    private val responsibleLevel = Level(Int.MAX_VALUE) // 一番上
    override fun beAbleToJudge(question: Question): Boolean = true // 何でも最後はここ

    override fun judge(question: Question) {
        println("$responsiblePerson で議論の末、「${question.content}」はOKとなりました(職員会議)")
    }
}

class Student(responsiblePerson: String) : Responsible(responsiblePerson) {
    // 生徒は判断しない
    override fun beAbleToJudge(question: Question) = false
    override fun judge(question: Question) {}
}
fun main() {
    val student = Student("生徒A")
    val rookie = ClassTeacher("担任A")
    val chief = ChiefTeacher("学年主任A")
    val staff = StaffMeeting("職員会議")

    student.setNext(rookie)
        .setNext(chief)
        .setNext(staff)

    val question1 = Question("宿題の提出期限を延ばしてほしい", Level(1))
    val question2 = Question("テストの範囲を狭くしてほしい", Level(2))
    val question3 = Question("体育祭の開催を中止してほしい", Level(3))

    student.putQuestion(question1)
    student.putQuestion(question2)
    student.putQuestion(question3)

    // 担任A が判定: 「宿題の提出期限を延ばしてほしい」はOKです(担任レベル)
    // 学年主任A が判定: 「テストの範囲を狭くしてほしい」はOKです(学年主任レベル
    // 職員会議 で議論の末、「体育祭の開催を中止してほしい」はOKとなりました(職員会議)
}

Command(コマンド)パターン

コマンドパターンは実行したい処理をオブジェクトとして包み込むデザインパターン
例えばリモコンなどがそう、ボタンを押すと動作するがその動作がどういう仕組みということを知る必要がない
コマンド共通のインターフェース

interface Command {
    fun execute()
}

具体的なコマンドの実装
照明クラスを用意してその照明の動作をコマンド化する

// 照明クラス
class Light(private val name: String) {
    fun turnOn() {
        println("${name}の照明をつけました")
    }
    
    fun turnOff() {
        println("${name}の照明を消しました")
    }
}

// 照明をつけるコマンド
class LightOnCommand(private val light: Light) : Command {
    override fun execute() {
        light.turnOn()
    }
}

// 照明を消すコマンド
class LightOffCommand(private val light: Light) : Command {
    override fun execute() {
        light.turnOff()
    }
}

コマンドを設定するメソッドとボタンを押した時のクラスを用意する

class RemoteControl {
    private var command: Command? = null
    
    fun setCommand(cmd: Command) {
        command = cmd
    }
    
    fun pressButton() {
        command?.execute()
    }
}

fun main() {
    // 照明を作成
    val livingRoomLight = Light("リビング")
    
    // コマンドを作成
    val lightOn = LightOnCommand(livingRoomLight)
    val lightOff = LightOffCommand(livingRoomLight)
    
    // リモコン(実行者)
    val remote = RemoteControl()
    
    // コマンドを設定して実行
    remote.setCommand(lightOn)
    remote.pressButton() // リビングの照明をつけました
    
    remote.setCommand(lightOff)
    remote.pressButton() // リビングの照明を消しました
}

Interpreter(インタープリター)パターン

インタープリンターパターンは解析した結果に則って何らかの処理を行いたい場合のデザインパターン

// 1. 基本のクラス - 「何かを実行する」
abstract class Command {
    abstract fun execute(): String
}
// 2. 具体的なコマンド
class HelloCommand : Command() {
    override fun execute(): String = "こんにちは!"
}

class ByeCommand : Command() {
    override fun execute(): String = "さようなら!"
}

class SmileCommand : Command() {
    override fun execute(): String = "😊"
}
// 3. 文字を見てコマンドを作る
class SimpleInterpreter {
    fun interpret(input: String): String {
        val command = when (input) {
            "hello" -> HelloCommand()
            "bye" -> ByeCommand()
            "smile" -> SmileCommand()
            else -> return "わからないコマンドです"
        }
        
        return command.execute()
    }
}

// 4. 使ってみる
fun main() {
    val interpreter = SimpleInterpreter()
    
    println("'hello' → ${interpreter.interpret("hello")}")
    println("'bye' → ${interpreter.interpret("bye")}")
    println("'smile' → ${interpreter.interpret("smile")}")
    println("'xyz' → ${interpreter.interpret("xyz")}")
}

Iterator(イテレーター)パターン

イテレータパターンはコレクションの要素に順次アクセスするためのパターン
内部構造を公開せずに要素を一つずつ取り出せる

data class Book(val title: String)

class BookShelf {
    private val books = mutableListOf<Book>()

    // 本を追加
    fun add(book: Book) {
        books.add(book)
    }

    // 本の総数
    fun size(): Int = books.size

    // 指定位置の本を取得
    fun get(index: Int): Book = books[index]
}

class SimpleBookIterator(private val shelf: BookShelf) {
    private var currentIndex = 0

    // まだ次の本があるか?
    fun hasNext(): Boolean {
        return currentIndex < shelf.size()
    }

    // 次の本を取る
    fun next(): Book {
        val book = shelf.get(currentIndex)
        currentIndex++  // 次の位置に進む
        return book
    }
}

fun main() {
    val shelf = BookShelf()
    shelf.add(Book("Book 1"))
    shelf.add(Book("Book 2"))
    shelf.add(Book("Book 3"))

    val iterator = SimpleBookIterator(shelf)

    while (iterator.hasNext()) {
        val book = iterator.next()
        println(book.title)
    }
}

Kotlinだと標準ですでにあるのでイテレータパターンを実装することはないだろう

val list = listOf(1, 2, 3)
for (item in list) { println(item) }

Mediator(メディエーター)パターン

メディエータパターンは仲介するクラスを用意してオブジェクト同士のやり取りを仲介するパターン

// ステップ1: メディエーターのインターフェース
interface ChatMediator {
    fun sendMessage(message: String, user: User)
    fun addUser(user: User)
}

// ステップ2: ユーザー(参加者)の基本クラス
abstract class User(
    protected val mediator: ChatMediator,
    protected val name: String
) {
    abstract fun send(message: String)
    abstract fun receive(message: String)
}

// ステップ3: 具体的なメディエーター(チャットルーム)
class ChatRoom : ChatMediator {
    private val users = mutableListOf<User>()
    
    override fun addUser(user: User) {
        users.add(user)
        println("${user.getName()}がチャットルームに参加しました")
    }
    
    override fun sendMessage(message: String, sender: User) {
        println("\n--- ${sender.getName()}からのメッセージ ---")
        // 送信者以外の全員にメッセージを送信
        users.filter { it != sender }.forEach { user ->
            user.receive(message)
        }
    }
}

// ステップ4: 具体的なユーザー
class ConcreteUser(
    mediator: ChatMediator,
    name: String
) : User(mediator, name) {
    
    override fun send(message: String) {
        println("$name: $message を送信")
        mediator.sendMessage(message, this)
    }
    
    override fun receive(message: String) {
        println("$name が受信: $message")
    }
    
    fun getName(): String = name
}

fun main() {
    println("=== シンプルなチャットルーム ===")
    
    // チャットルーム(メディエーター)を作成
    val chatRoom = ChatRoom()
    
    // ユーザーを作成
    val alice = ConcreteUser(chatRoom, "Alice")
    val bob = ConcreteUser(chatRoom, "Bob")
    val charlie = ConcreteUser(chatRoom, "Charlie")
    
    // ユーザーをチャットルームに追加
    chatRoom.addUser(alice)
    chatRoom.addUser(bob)
    chatRoom.addUser(charlie)
    
    // メッセージのやり取り
    alice.send("こんにちは!")
    bob.send("こんにちは、Alice!")
    charlie.send("みなさん、お疲れ様です")
}

Memento(メメント)パターン

Mementoパターンは、オブジェクトの状態を保存・復元するデザインパターンです。
基本構造

  • Originator: 状態を持つオブジェクト
  • Memento: 状態を保存するオブジェクト
  • Caretaker: Mementoを管理するオブジェクト
// Memento: 状態を保存するクラス
data class TextMemento(val content: String, val cursorPosition: Int)

// Originator: 状態を持つクラス
class TextEditor {
    var content: String = ""
    var cursorPosition: Int = 0

    fun write(text: String) {
        content += text
        cursorPosition = content.length
    }

    fun setCursor(position: Int) {
        cursorPosition = position.coerceIn(0, content.length)
    }

    // 現在の状態をMementoとして保存
    fun save(): TextMemento {
        return TextMemento(content, cursorPosition)
    }

    // Mementoから状態を復元
    fun restore(memento: TextMemento) {
        content = memento.content
        cursorPosition = memento.cursorPosition
    }

    override fun toString(): String {
        return "Content: '$content', Cursor: $cursorPosition"
    }
}

// Caretaker: Mementoを管理するオブジェクト
class EditorHistory {
    private val history = mutableListOf<TextMemento>()

    // 状態を保存
    fun save(editor: TextEditor) {
        history.add(editor.save())
    }

    // 最後の状態を復元
    fun restore(editor: TextEditor): Boolean {
        return if (history.isNotEmpty()) {
            // 最後に保存した状態を取り出して復元し、その状態は履歴から削除する
            history.removeAt(history.lastIndex)
            editor.restore(history[history.lastIndex])
            true
        } else {
            false
        }
    }
}

// 使用例
fun main() {
    val editor = TextEditor()
    val history = EditorHistory()

    // 初期状態を保存
    history.save(editor)

    // テキスト編集
    editor.write("Hello")
    println("1. $editor")
    history.save(editor)

    editor.write(" World")
    println("2. $editor")
    history.save(editor)

    // undo操作
    history.restore(editor)
    println("Undo: $editor")

    history.restore(editor)
    println("Undo: $editor")
}

Observer(オブザーバー)パターン

オブザーバーパターンは状態の変化を検知し観察者にその変更を通知するパターン
登場人物は以下
Observer: 観察する側、通知を受け取って何かの処理をする
Subject: 観察される側、状態が変わった時に通知を送る、Observerの登録と削除する機能を持つ

data class Product(val id: String, val name: String, val price: Int)

Subject

class ShoppingCartManager {
    private val cartItems = mutableListOf<Product>()
    private val observers = mutableListOf<(List<Product>, Int) -> Unit>()
    
    fun addCartObserver(observer: (List<Product>, Int) -> Unit) {
        observers.add(observer)
    }
    
    fun addProduct(product: Product) {
        cartItems.add(product)
        notifyObservers()
    }
    
    fun removeProduct(productId: String) {
        cartItems.removeIf { it.id == productId }
        notifyObservers()
    }
    
    private fun notifyObservers() {
        val total = cartItems.sumOf { it.price }
        observers.forEach { it(cartItems.toList(), total) }
    }
}

fun main() {
    val cartManager = ShoppingCartManager()

    // オブザーバーを追加
    cartManager.addCartObserver { items, total ->
        println("カートの内容: ${items.joinToString { it.name }}, 合計: $total")
    }

    // 商品を追加
    cartManager.addProduct(Product("1", "Apple", 100))
    cartManager.addProduct(Product("2", "Banana", 150))

    // 商品を削除
    cartManager.removeProduct("1")
}

この例は同期的な実装、非同期にするにはFlowを使うのが良い

State(ステート)パターン

Stateパターンは状態をクラスで表現するパターン

interface PlayerAction {
    fun play(): PlayerState
    fun pause(): PlayerState
    fun stop(): PlayerState
    fun getDisplayText(): String
}

sealed class PlayerState : PlayerAction {
    object Stopped : PlayerState() {
        override fun play(): PlayerState {
            println("▶️ 再生開始")
            return Playing
        }
        
        override fun pause(): PlayerState {
            println("❌ 停止中は一時停止できません")
            return this
        }
        
        override fun stop(): PlayerState {
            println("❌ 既に停止中です")
            return this
        }
        
        override fun getDisplayText(): String = "停止中"
    }
    
    object Playing : PlayerState() {
        override fun play(): PlayerState {
            println("❌ 既に再生中です")
            return this
        }
        
        override fun pause(): PlayerState {
            println("⏸️ 一時停止")
            return Paused
        }
        
        override fun stop(): PlayerState {
            println("⏹️ 停止")
            return Stopped
        }
        
        override fun getDisplayText(): String = "再生中"
    }
    
    object Paused : PlayerState() {
        override fun play(): PlayerState {
            println("▶️ 再生再開")
            return Playing
        }
        
        override fun pause(): PlayerState {
            println("❌ 既に一時停止中です")
            return this
        }
        
        override fun stop(): PlayerState {
            println("⏹️ 停止")
            return Stopped
        }
        
        override fun getDisplayText(): String = "一時停止中"
    }
}
class MusicPlayer {
    private var state: PlayerState = PlayerState.Stopped
    
    fun play() {
        state = state.play()
    }
    
    fun pause() {
        state = state.pause()
    }
    
    fun stop() {
        state = state.stop()
    }
    
    fun getStatus(): String = state.getDisplayText()
}

Strategy(ストラテジー)パターン

ストラテジーパターンは同じ目的の処理を複数のアルゴリズムを切り替えるために使用するパターン

// 決済戦略インターフェース
interface PaymentStrategy {
    fun pay(amount: Int): String
}
// 具体的な決済戦略
class CreditCardStrategy(private val cardNumber: String) : PaymentStrategy {
    override fun pay(amount: Int): String {
        return "💳 クレジットカード(${cardNumber})で¥${amount}を決済しました"
    }
}

class PayPalStrategy(private val email: String) : PaymentStrategy {
    override fun pay(amount: Int): String {
        return "🅿️ PayPal(${email})で¥${amount}を決済しました"
    }
}

class BankTransferStrategy(private val bankAccount: String) : PaymentStrategy {
    override fun pay(amount: Int): String {
        return "🏦 銀行振込(${bankAccount})で¥${amount}を決済しました"
    }
}
// Context(戦略を使用するクラス)
class PaymentProcessor {
    private var strategy: PaymentStrategy? = null
    
    fun setPaymentStrategy(strategy: PaymentStrategy) {
        this.strategy = strategy
    }
    
    fun processPayment(amount: Int): String {
        return strategy?.pay(amount) ?: "決済方法が設定されていません"
    }
}

Template Method(テンプレートメソッド)パターン

テンプレートメソッドは処理の骨組みを親クラスで定義して具体的な実装を子クラスに委ねるパターン

// 抽象クラス(テンプレートを定義)
abstract class DataProcessor {
    
    // Template Method(処理の骨組み)
    fun processData() {
        println("=== データ処理開始 ===")
        
        val data = readData()        // 1. データ読み込み
        val processed = process(data) // 2. データ処理
        writeData(processed)         // 3. データ書き込み
        cleanup()                    // 4. 後処理
        
        println("=== データ処理完了 ===")
    }
    
    // 抽象メソッド(子クラスで実装必須)
    protected abstract fun readData(): String
    protected abstract fun process(data: String): String
    protected abstract fun writeData(data: String)
    
    // フック(オプション実装)
    protected open fun cleanup() {
        println("🧹 デフォルトのクリーンアップ処理")
    }
}
// CSVファイル処理の具体実装
class CsvDataProcessor : DataProcessor() {
    override fun readData(): String {
        println("📄 CSVファイルからデータを読み込み")
        return "name,age\nTaro,25\nHanako,30"
    }
    
    override fun process(data: String): String {
        println("🔄 CSVデータを処理中...")
        return data.replace(",", " | ")
    }
    
    override fun writeData(data: String) {
        println("💾 処理済みCSVデータを保存:")
        println(data)
    }
    
    override fun cleanup() {
        println("🧹 CSVファイルのテンポラリを削除")
    }
}
fun main() {
    val csvProcessor = CsvDataProcessor()
    csvProcessor.processData()
}

Visitor(ビジター)パターン

受け入れる側は変わらないけど訪問者の種類や対応が増える場合で使えるパターン

// 1. Visitor(訪問者)- 先生のインターフェース
interface Teacher {
    fun visit(home: Home)
    fun visit(tanakaHome: TanakaHome)
    fun visit(suzukiHome: SuzukiHome)
}
// 2. Element(要素)- 家庭の基底クラス
abstract class Home {
    abstract fun praiseChild(): Any?
    abstract fun reproveChild(): Any?
}
// 3. Acceptor(受け入れ側)インターフェース
interface TeacherAcceptor {
    fun accept(teacher: Teacher)
}
// 4. 具体的な家庭たち

class TanakaHome : Home(), TeacherAcceptor {
    override fun praiseChild(): Any {
        println("👩 田中さん: ありがとうございます!嬉しいです")
        return "お茶とお菓子"
    }
    
    override fun reproveChild(): Any? {
        println("👩 田中さん: 申し訳ございません...気をつけます")
        return null
    }
    
    override fun accept(teacher: Teacher) {
        println("🏠 田中家: いらっしゃいませ、先生")
        teacher.visit(this) // 自分(田中家)を先生に渡す
    }
}

class SuzukiHome : Home(), TeacherAcceptor {
    override fun praiseChild(): Any {
        println("👨 鈴木さん: あら、先生ったらご冗談を")
        return "お茶"
    }
    
    override fun reproveChild(): Any? {
        println("👨 鈴木さん: うちの子に限ってそんなことは...")
        return null
    }
    
    override fun accept(teacher: Teacher) {
        println("🏠 鈴木家: こんにちは、先生")
        teacher.visit(this) // 自分(鈴木家)を先生に渡す
    }
}

class YamadaHome : Home(), TeacherAcceptor {
    override fun praiseChild(): Any {
        println("👩 山田さん: 本当ですか?嬉しいです!")
        return "手作りケーキ"
    }
    
    override fun reproveChild(): Any? {
        println("👩 山田さん: しっかり叱っておきます")
        return null
    }
    
    override fun accept(teacher: Teacher) {
        println("🏠 山田家: お疲れさまです、先生")
        teacher.visit(this)
    }
}
// 5. 具体的な先生たち

class RookieTeacher(private val students: List<String>) : Teacher {
    
    override fun visit(home: Home) {
        println("👨‍🏫 新人先生: こ、こ、こんにちは...")
    }
    
    override fun visit(tanakaHome: TanakaHome) {
        println("👨‍🏫 新人先生: 田中くんは頑張ってますね")
        tanakaHome.praiseChild()
    }
    
    override fun visit(suzukiHome: SuzukiHome) {
        println("👨‍🏫 新人先生: 鈴木くんは少し注意が必要で...")
        suzukiHome.reproveChild()
    }
}

class VeteranTeacher(private val students: List<String>) : Teacher {
    
    override fun visit(home: Home) {
        println("👨‍🏫 ベテラン先生: こんにちは、お邪魔します")
    }
    
    override fun visit(tanakaHome: TanakaHome) {
        println("👨‍🏫 ベテラン先生: 田中さんのお子さんは素晴らしいですね")
        tanakaHome.praiseChild()
    }
    
    override fun visit(suzukiHome: SuzukiHome) {
        println("👨‍🏫 ベテラン先生: 鈴木くんについてお話があります")
        suzukiHome.reproveChild()
    }
}

class SubstituteTeacher(private val students: List<String>) : Teacher {
    
    override fun visit(home: Home) {
        println("👩‍🏫 代理先生: こんにちは、代理でお伺いしました")
    }
    
    override fun visit(tanakaHome: TanakaHome) {
        println("👩‍🏫 代理先生: 田中くんの様子をお伝えに")
        tanakaHome.praiseChild()
    }
    
    override fun visit(suzukiHome: SuzukiHome) {
        println("👩‍🏫 代理先生: 鈴木くんのことでご相談が")
        suzukiHome.reproveChild()
    }
}
// 使用例
fun main() {
    println("=== 家庭訪問のVisitorパターン ===\n")
    
    // 家庭リスト
    val homes: List<TeacherAcceptor> = listOf(
        TanakaHome(),
        SuzukiHome(),
        YamadaHome()
    )
    
    val students = listOf("田中くん", "鈴木くん", "山田くん")
    
    // 1. 新人先生の家庭訪問
    println("--- 新人先生の家庭訪問 ---")
    val rookieTeacher = RookieTeacher(students)
    homes.forEach { home ->
        home.accept(rookieTeacher)
        println()
    }
    
    // 2. ベテラン先生の家庭訪問
    println("--- ベテラン先生の家庭訪問 ---")
    val veteranTeacher = VeteranTeacher(students)
    homes.forEach { home ->
        home.accept(veteranTeacher)
        println()
    }
    
    // 3. 代理先生の家庭訪問
    println("--- 代理先生の家庭訪問 ---")
    val substituteTeacher = SubstituteTeacher(students)
    homes.forEach { home ->
        home.accept(substituteTeacher)
        println()
    }
    
    println("=== 家庭訪問終了 ===")
}

Discussion