Swift開発手法をポケモンで理解してみよう①

ポケモンニワカなのでポケモンに関する情報は正確ではない可能性があります、ご容赦ください。

OptionalやDelegate、その他概念等をポケモンに例えて覚えてみようとする試み。

1記事あたり5つ紹介できるようにします、昨今生成AIさんがとても優秀なので知恵を色々お借りしました。

Part1

Optional (オプショナル)

存在するかもしれない、しないかもしれないポケモン

解説

1. ポケモンの捕獲

オプショナルは、ポケモンが捕まっているかどうかを示すものと考えてみましょう。

ポケモンが捕まっている状態は「存在する」、捕まっていない状態は「nil」と考えられますね。   

– 例: var capturedPokemon: Pokemon? は、ポケモンが捕まっているかもしれないし、捕まっていないかもしれない状態を表しています。

2. ポケモンの確認

捕まえたポケモンがいるかどうかを確認するのは、オプショナルのアンラップに似ています。ポケモンがいる場合はそのポケモンを使い、いない場合は別の行動を取ります。

// ポケモンの確認
if let myPokemon = capturedPokemon {
    print("Go, \(myPokemon.name)!")
} else {
    print("No Pokémon captured.")
}

3. 強制的なバトル (強制アンラップ)

強制アンラップは、ポケモンが必ずいると信じてバトルに挑むことに似ています。しかし、もし捕まえていなかった場合、バトルは失敗します。

// 強制的なバトル
let battlePokemon: Pokemon = capturedPokemon! // 捕まえていないとエラー (アプリがクラッシュ)
print("Battling with \(battlePokemon.name)!")

~ 強制アンラップを使っていい時、使ってはいけないとき~

使っていい時

  • 確実に値が存在することが分かっている場合(例えば、ポケモンが出現した後に捕まえた場合)。

使ってはいけないとき

  • 値が存在しない可能性がある場合(例えば、ポケモンを捕まえる前にその値を使おうとする場合)。この場合、強制アンラップを使うとランタイムエラーが発生します。

4. デフォルト値

??演算子を使って、nilの場合にデフォルト値を指定できます。

let unwrappedName = name ?? "Unknown"
print("Hello, \(unwrappedName)!")

構造体 (Struct)とクラス (Class)

構造体は進化しないポケモン、クラスは進化するポケモン

解説

1. 構造体
   – 構造体は、ポケモンのように進化しない存在です。例えば、ピカチュウはそのままの姿で、特性や能力を持っていますが、進化することはありません。Structは値型であり、コピーされると新しいインスタンスが作られます。

struct Pikachu {
    var name: String
    var level: Int
    
    func attack() {
        print("\(name)が攻撃した!")
    }
}

var pikachu1 = Pikachu(name: "ピカチュウ", level: 10)
var pikachu2 = pikachu1 // コピーされる
pikachu2.name = "ライチュウ" // pikachu1には影響しない

2. クラス
   – クラスは、進化するポケモンのような存在です。例えば、リザードンはリザードから進化したポケモンで、特性や能力が変化します。Classは参照型であり、同じインスタンスを参照することができます。

class Charizard {
    var name: String
    var level: Int
    
    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    func attack() {
        print("\(name)が火炎放射を放った!")
    }
}

let charizard1 = Charizard(name: "リザードン", level: 36)
let charizard2 = charizard1 // 同じインスタンスを参照
charizard2.name = "リザード" // charizard1にも影響する

Delegate (デリゲート)

ポケモンバトルの指揮官

解説

デリゲートは、ポケモンバトルにおける指揮官として考えてみましょう。

ポケモンバトルをする際、指揮官(あなた)はポケモンに指示を出します。ポケモンはその指示に従って行動しますが、指揮官がいなければポケモンは何をすればいいのか分かりません。

デリゲートは、あるオブジェクトが別のオブジェクトに対して特定のタスクを委任する仕組みです。例えば、バトル中にポケモンが特定の技を使うタイミングや、バトルの結果を報告する際に、指揮官がその役割を果たします。

// デリゲートプロトコルの定義
protocol BattleDelegate: AnyObject {
    func didWinBattle()
    func didLoseBattle()
}

// ポケモンバトルのクラス
class PokemonBattle {
    weak var delegate: BattleDelegate?
    
    func startBattle() {
        // バトルのロジック
        let battleOutcome = Bool.random() // ランダムに勝敗を決定
        
        if battleOutcome {
            delegate?.didWinBattle()
        } else {
            delegate?.didLoseBattle()
        }
    }
}

// 指揮官クラス
class Trainer: BattleDelegate {
    func didWinBattle() {
        print("勝った!ポケモンが強い!")
    }
    
    func didLoseBattle() {
        print("負けた…もっと鍛えなきゃ。")
    }
}

// 使用例
let trainer = Trainer()
let battle = PokemonBattle()
battle.delegate = trainer
battle.startBattle()

デリゲートは、ポケモンバトルにおける指揮官のように、特定のイベントやアクションに対して反応するための仕組みです。

指揮官がいなければ、ポケモンは自分の行動を決めることができません。同様に、デリゲートを使うことで、オブジェクト間のコミュニケーションを円滑に行うことができます。


Generics (ジェネリクス)

どんなポケモンでも使えるバトル道具

解説

1. 汎用性

ジェネリクスは、特定の型に依存せず、さまざまな型のデータを扱える道具のようなものです。

ポケモンバトルで使う道具は、どんなポケモンにも使えるものがあれば便利ですよね。

2. 型の柔軟性

例えば、ポケモンの「回復アイテム」は、どのポケモンにも使えるため、特定のポケモンにしか使えないアイテムよりも便利です。

ジェネリクスも同様に、型を指定せずに関数やクラスを定義できるため、柔軟に使えます。

3. 再利用性

ジェネリクスを使うことで、同じロジックを異なる型に対して再利用できます。

ポケモンのバトルで、同じ戦略を使って異なるポケモンに対応できるようなものです。

// ポケモンの構造体
struct Pokemon {
    var name: String
    var level: Int
}

// ジェネリクスを使った関数の定義
func printPokemonInfo<T>(pokemon: T) {
    if let pokemon = pokemon as? Pokemon {
        print("捕まえたポケモン: \(pokemon.name), レベル: \(pokemon.level)")
    } else if let name = pokemon as? String {
        print("捕まえたポケモン: \(name)")
    } else {
        print("未知のポケモン")
    }
}

// 使用例
let pikachu = Pokemon(name: "ピカチュウ", level: 25)
let charizard = Pokemon(name: "リザードン", level: 36)
let unknownPokemon = "ミュウツー"

printPokemonInfo(pokemon: pikachu)
printPokemonInfo(pokemon: charizard)
printPokemonInfo(pokemon: unknownPokemon)

4. ジェネリクスを使うと便利なケース

コレクションの操作

→  配列や辞書など、異なる型のデータを扱う際に、ジェネリクスを使うことで同じ操作を適用できます。

アルゴリズムの実装

→   ソートやフィルタリングなど、特定の型に依存しないアルゴリズムを実装する際に、ジェネリクスが役立ちます。

APIの設計

→ 異なる型のデータを返すAPIを設計する際に、ジェネリクスを使うことで、より柔軟で再利用可能なコードを書くことができます。


Protocol (プロトコル)

ポケモンの能力を定義するルールブック

解説

1. 能力の定義

プロトコルは、ポケモンが持つべき能力や技を定義するルールブックのようなものです。

例えば、「水タイプのポケモンは水技を使える」というルールを定めることができます。

2. 共通のインターフェース

すべてのポケモンが「バトルをする」という共通のインターフェースを持つように、プロトコルは異なるクラスや構造体が共通のメソッドやプロパティを持つことを保証します。

3. 多様性の受容

プロトコルを使うことで、異なるポケモンが同じ能力を持つことができ、バトルスタイルや技の使い方はそれぞれ異なるという多様性を受け入れることができます。

// プロトコルの定義
protocol Battleable {
    var name: String { get }
    func attack()
}

// ポケモンのクラス
class Pikachu: Battleable {
    var name: String {
        return "ピカチュウ"
    }
    
    func attack() {
        print("\(name)が10万ボルトを放った!")
    }
}

class Charizard: Battleable {
    var name: String {
        return "リザードン"
    }
    
    func attack() {
        print("\(name)がかえんほうしゃを放った!")
    }
}

// 使用例
let pikachu = Pikachu()
let charizard = Charizard()

pikachu.attack() // ピカチュウが10万ボルトを放った!
charizard.attack() // リザードンがかえんほうしゃを放った!