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

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

Swift開発手法をポケモンに例えて覚えてみようとする試み。

Part3です。

Part3

Type Alias (エイリアス)

ポケモンのニックネーム

解説

1. ニックネームを使用する

Type Aliasは、型に別名を付けることができる機能です。ポケモンにニックネームを付けるように、型にわかりやすい名前を与えることで、コードの可読性が向上します。

2. 整数型のニックネーム

ここでは、Int型にAgeというニックネームを付けています。これにより、年齢を表す変数がより明確になります。

typealias Age = Int
let myAge: Age = 25

3. 複雑な型のニックネーム

複雑なタプル型にUserInfoというニックネームを付けることで、コードがすっきりし、何を表しているのかが一目でわかります。

typealias UserInfo = (name: String, age: Int)
let user: UserInfo = (name: "Pikachu", age: 5)

4. 関数型のニックネーム

関数の型にニックネームを付けることで、引数の意味が明確になり、関数の使い方がわかりやすくなります。

typealias CompletionHandler = (Bool) -> Void
func performAction(completion: CompletionHandler) {
    // 処理
    completion(true)
}

5. どういう時に使えばよいか

Type Aliasは、特に複雑な型や長い型名を使う際に、コードの可読性を向上させるために使用します。また、特定の意味を持つ型にわかりやすい名前を付けることで、コードの意図を明確にすることができます。


定数・変数宣言 (let, var, weak var, lazy var)

ポケモンの育成スタイル

解説

1. let(固定のポケモン)

letは、一度決めたら変わらないポケモンのようなものです。例えば、伝説のポケモンは一度捕まえたらそのままです。

let legendaryPokemon = "Mewtwo"

2. var(成長するポケモン)

varは、成長や進化をするポケモンのように、変化することができる変数です。レベルアップして進化するポケモンをイメージしてください。

var evolvingPokemon = "Charmander"
evolvingPokemon = "Charmeleon" // 進化

3. weak var(頼りないポケモン)

weak varは、他のポケモンに依存しているが、いつでも消えてしまう可能性があるポケモンのようです。例えば、仲間のポケモンがいなくなると、頼りにしていたポケモンも消えてしまいます。

class Trainer {
    weak var partner: Pokemon?
}

4. lazy var(のんびり屋のポケモン)

lazy varは、必要になるまで動かないのんびり屋のポケモンのようです。例えば、バトルが始まるまで技を使わないポケモンをイメージしてください。

class Pokemon {
    lazy var specialMove: String = {
        return "Thunderbolt"
    }()
}

5. どういう時に使うのか

– letは不変の値を保持したいときに使用します。 
– varは値が変わる可能性がある場合に使用します。 
– weak varは循環参照を避けるために、他のオブジェクトに依存する場合に使用します。 
– lazy varは、初期化が重い処理を遅延させたいときに使用します。これにより、必要なときだけリソースを消費します。


DI (Dependency Injection)

“依存性の注入:ポケモンのタイプを選んで、バトルを有利に進める戦略”

解説

1. タイプの選択

依存性の注入とは、適切なポケモンのタイプを選んで、特定の敵に有利な戦闘を行うことです。ポケモントレーナーは、戦闘に応じて最適なポケモンを選びます。

2. 柔軟なチーム構成

トレーナーは、バトルに入る前にポケモンを選択し、状況に応じて強みを活かすことができます。同様に、DIはクラスが依存するオブジェクトを外部から注入し、状況に応じて変更可能にします。

3. 容易な交代

水タイプのポケモンを使って火タイプの敵を倒すように、依存するオブジェクトを簡単に差し替えることができ、テストやモックの作成が容易になります。

4. 強みの活用

トレーナーがバトルで最大限の効果を発揮するためにポケモンの強みを活かすように、DIはクラスの柔軟性と再利用性を高め、異なるシナリオで最大の効果を発揮させます。

// ポケモンの攻撃を表現するためのプロトコル
protocol Pokemon {
    func attack()
}

// PikachuクラスはPokemonプロトコルに準拠し、攻撃方法を実装
class Pikachu: Pokemon {
    func attack() {
        print("Pikachu uses Thunderbolt!")
    }
}

// CharizardクラスもPokemonプロトコルに準拠し、別の攻撃方法を実装
class Charizard: Pokemon {
    func attack() {
        print("Charizard uses Flamethrower!")
    }
}

// PokemonTrainerクラスは依存するPokemonオブジェクトを外部から注入
class PokemonTrainer {
    let pokemon: Pokemon
    
    // 依存性注入:コンストラクタでPokemonオブジェクトを受け取る
    init(pokemon: Pokemon) {
        self.pokemon = pokemon
    }
    
    // バトルを開始し、ポケモンの攻撃を実行
    func startBattle() {
        pokemon.attack()
    }
}

// Pikachuオブジェクトを作成
let pikachu = Pikachu()
// Charizardオブジェクトを作成
let charizard = Charizard()

// Pikachuを持つトレーナーを作成し、バトルを開始
let trainerWithPikachu = PokemonTrainer(pokemon: pikachu)
trainerWithPikachu.startBattle()  // Pikachu uses Thunderbolt!

// Charizardを持つトレーナーを作成し、バトルを開始
let trainerWithCharizard = PokemonTrainer(pokemon: charizard)
trainerWithCharizard.startBattle()  // Charizard uses Flamethrower!

5. どういう時に使うのか

  • テストのしやすさを向上させたいとき
    • テスト用のモックオブジェクトを注入することで、依存関係を簡単に置き換えることができます。
  • クラスの柔軟性と再利用性を高めたいとき
    • 異なる実装を注入することで、クラスの利用シーンに応じた振る舞いを簡単に変更できます。
  • 複雑な依存関係の管理をシンプルにしたいとき
    • DIを使うことで、依存関係の管理が容易になり、コードの可読性と保守性が向上します。

Enum

ポケモンのタイプを一つにまとめる図鑑

解説

1. ポケモンのタイプ一覧

Enumは、ポケモンのタイプのように、関連する複数の値を一つにまとめて管理するためのものです。例えば、炎、水、草などのタイプを一つのEnumで管理します。

2. 特定のタイプを選択

Enumは、特定のポケモンが持つタイプを一つに限定します。例えば、フシギダネは草タイプか毒タイプであることが明確になります。

3. タイプの比較が容易

Enumを使うことで、ポケモンのタイプ同士を簡単に比較できます。例えば、相手が炎タイプなら、水タイプのポケモンを選ぶと有利です。

4. コンパイル時の安全性

Enumを使うことで、存在しないポケモンのタイプを選ぶミスを防げます。例えば、Swiftは”電気タイプ”を持つEnumに存在しないタイプを使おうとするとエラーを出します。

// ポケモンのタイプを表現するEnum
enum PokemonType {
    case fire
    case water
    case grass
    case electric
    case psychic
    case ground
}

// ポケモンの構造体
struct Pokemon {
    let name: String
    let type: PokemonType
}

// ポケモンのインスタンスを作成
let charmander = Pokemon(name: "Charmander", type: .fire)
let squirtle = Pokemon(name: "Squirtle", type: .water)
let bulbasaur = Pokemon(name: "Bulbasaur", type: .grass)

// ポケモンのタイプをチェックする関数
func checkAdvantage(pokemon: Pokemon) {
    switch pokemon.type {
    case .fire:
        print("\(pokemon.name) is a Fire type. Strong against Grass.")
    case .water:
        print("\(pokemon.name) is a Water type. Strong against Fire.")
    case .grass:
        print("\(pokemon.name) is a Grass type. Strong against Water.")
    case .electric:
        print("\(pokemon.name) is an Electric type. Strong against Water.")
    case .psychic:
        print("\(pokemon.name) is a Psychic type. Strong against Fighting.")
    case .ground:
        print("\(pokemon.name) is a Ground type. Strong against Electric.")
    }
}

// 各ポケモンの強みをチェック
checkAdvantage(pokemon: charmander) // Charmander is a Fire type. Strong against Grass.
checkAdvantage(pokemon: squirtle)   // Squirtle is a Water type. Strong against Fire.
checkAdvantage(pokemon: bulbasaur)  // Bulbasaur is a Grass type. Strong against Water.

5. どういう時に使うのか

  • 決まった選択肢の集合を扱うとき
    • ポケモンのタイプや曜日など、あらかじめ決まった選択肢を扱うときに便利です。
  • コードの読みやすさと安全性を高めたいとき
    • Enumを使うことで、明示的な型として扱えるため、コードが分かりやすくなり、無効な値の使用を防げます。
  • 関連する定数をグループ化したいとき
    • 複数の関連する定数を一つの型としてまとめることで、管理が容易になります。

循環参照

ポケモンの友情と依存

解説

1. 循環参照(ポケモンの友情)

循環参照は、ポケモン同士が互いに依存し合っている状態です。例えば、あるポケモンが他のポケモンを助けるために、逆にそのポケモンからも助けられる関係です。このような関係が続くと、どちらも動けなくなってしまいます。

2. weak self(弱い友情)

weak selfは、ポケモン同士の友情を弱めることで、循環参照を防ぐ方法です。例えば、あるポケモンが他のポケモンを助けるとき、その友情が強すぎるとお互いに依存してしまいますが、`weak`を使うことで、依存を軽減します。

class Trainer {
    var name: String
    var pokemon: Pokemon?

    init(name: String) {
        self.name = name
    }

    func trainPokemon() {
        // selfをweakにすることで循環参照を防ぐ
        DispatchQueue.global().async { [weak self] in
            guard let self = self else { return }
            print("\(self.name) is training \(self.pokemon?.name ?? "a Pokémon")!")
        }
    }
}

3. 強い参照(強い友情)

強い参照は、ポケモン同士が強い友情で結ばれている状態です。この場合、どちらかが消えると、もう一方も消えてしまう可能性があります。例えば、トレーナーがポケモンを持っている限り、そのポケモンは存在し続けます。

class Pokemon {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Trainer {
    var pokemon: Pokemon

    init(pokemon: Pokemon) {
        self.pokemon = pokemon
    }
}

4. 循環参照の例(トレーナーとポケモンの関係)

トレーナーがポケモンを持ち、ポケモンがトレーナーを持つ場合、循環参照が発生します。これにより、メモリリークが発生する可能性があります。

class Trainer {
    var pokemon: Pokemon?

    init(pokemon: Pokemon) {
        self.pokemon = pokemon
        pokemon.trainer = self // 循環参照
    }
}

class Pokemon {
    var trainer: Trainer?
}

5. どういう時に使うのか

循環参照は、オブジェクトが互いに強い参照を持つことで発生し、メモリリークの原因となります。

`weak`や`unowned`を使うことで、依存関係を緩和し、オブジェクトが適切に解放されるようにします。

特に、クロージャやデリゲートの中で`self`を参照する場合に注意が必要です。