今回のような場面ではプロトコルを使って表現するのが一般的かもしれません。プロトコルの構造も複雑なので、今回の場面で使えるかもしれない基本的なところを紹介してみます。
プロトコルでインターフェイスを規程
たとえば Player
というプロトコルを作成して、そこに必要な機能を規定します。
swift
1protocol Player {
2
3 func nextMove() -> Location
4}
これを、たとえば OnlinePlayer
と ComputerPlayer
に適用すると、それらの型ではプロトコルで規定した機能の実装を必須にすることができます。
swift
1class OnlinePlayer : Player {
2
3 func nextMove() -> Location {
4
5 return ・・・
6 }
7}
8
9class ComputerPlayer : Player {
10
11
12 func nextMove() -> Location {
13
14 return ・・・
15 }
16}
プロトコル型でインスタンスを表現
このとき、共通するプロトコル Player
を基底クラスのように Player
型として使うことで、どちらのインスタンスでも取り扱えるようになります。
swift
1let player: Player = ComputerPlayer()
共通機能(既定の実装)を持たせたい場合
もし OnlinePlayer
と ComputerPlayer
とで共通して使いたい機能があるようでしたら、Player
プロトコルに「プロトコル拡張」を使って機能を実装しておくことも可能です。
swift
1extension Player {
2
3 func nextMove() -> Location {
4
5 return ・・・
6 }
7}
特定の条件だけに共通機能を持たせたいようなときは、たとえば、手動操作のプレイヤーに限って何かをしたいときは ManualControl
プロトコルを作成して、自動操縦なプレイヤーにだけ適用することも可能です。
swift
1protocol ManualControl {
2
3}
4
5class ComputerPlayer : Player, ManualControl {
6
7}
このようにすると、プロトコル拡張を行うときに条件を指定して、より柔軟な既定の実装を行えます。
swift
1extension Player : where Self : ManualControl {
2
3 func nextMove() -> Location {
4
5 return ・・・
6 }
7}
このように、性質に合わせたプロトコルを並列に加えていきながら、条件を絞ってプロトコル拡張を行うことで、まるで Player
を既定クラスのように使いながら、クラス継承を重ねるような系統分けも行えます。
プロパティーについて
プロパティーは、プロトコルに実装することは extension
を使ってもできないので、プロトコルに存在だけ宣言することで必須にして、型に都度実装する必要があります。
swift
1protocol Player {
2
3 var cards: [Card] { get set }
4 var level: Int { get }
5}
6
7class OnlinePlayer : Player {
8
9 var cards: [Card]
10 var level: Int
11}
12
13class ComputerPlayer : Player {
14
15 var cards: [Card]
16 let level: Int = 1
17}
たとえばこのような記述が必要になるので、慣れないうちは手間が増えて複雑に感じられるかもしれないですけれど、プロパティー定義の記述自体はシンプルですし、プロトコルで実装が義務付けられているのも手伝って、型を定義するときに実装し忘れることもない(忘れるとコンパイラーに指摘される)ため、思いのほか負担なく実装することができます。
プロパティーの実装を型に定義しないといけないですけれど、存在自体は Player
プロトコルに規定されているので、たとえば Player
プロトコルを拡張して既定の実装を作るときには「プロパティーがあること前提で」コードを記述できるので、まるで Player
にプロパティーが実装されているかのようにコードをかけます。
swift
1extension Palyer {
2
3 func drawCard() -> Card? {
4
5 return cards.randomElement()
6 }
7}
もし、何も実装しなければ特定の値を取得できるようにしたいときには、既定の実装で計算型プロパティー (Computed Property) を実装してあげることは可能です。
swift
1extension Player {
2
3 var level: Int {
4
5 get {
6
7 return 1
8 }
9 }
10}
ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。
質問者の「実装したいこと」に対する補足
オブジェクト指向と違って、特にプロパティーの実装位置が『親が持つか、子が持つか』的な大きく違う点があるので感覚的に違和感を感じるかもしれませんけれど、これまでに紹介した事柄を使って、質問者 ushi さんの挙げたコードを書き換えてみると、次のようになりそうです。
swift
1protocol Base {
2
3 // 変数の実態は持てないが、存在する前提で参照できる。
4 var isFirstTurn: Bool! { get }
5
6 // 適用先で実装しなければならないメソッドを指定できる。
7 func setListener(result: (Int) -> Void)
8}
9
10extension Base {
11
12 // プロトコル拡張で共通のメソッドを実装できる。
13 func setBoardData() {
14
15 // この中で、存在するはずのプロパティーを参照できる。
16 if isFirstTurn {
17
18 }
19
20 // do something
21 }
22}
23
24class OnlineGame: Base {
25
26 // プロトコルが要求するプロパティーの実装が必要。
27 var isFirstTurn: Bool! = false
28
29 // プロトコルが要求するメソッドの実装が必要。
30 func setListener(result:(Int) -> Void) {
31 //... received signal
32 result(signal)
33 }
34}
35
36class COMGame: Base {
37
38 // プロトコルが要求するプロパティーの実装が必要。
39 var isFirstTurn: Bool! = false
40
41 // プロトコルが要求するメソッドの実装が必要。
42 func setListener(result: (Int) -> Void){
43
44 //... received signal
45 result(signal)
46 }
47
48 func comMove() -> Int {
49
50 //... think next move
51 return next
52 }
53}
プロパティーの初期化を Base
ではなく各型が担わないといけないところもプロトコルを使った場合の特徴的なところです。この辺りが吉と出るか凶と出るかは状況によると思いますけれど、場合によってはプロトコルで、イニシャライザー init(isFirstTurn:)
を記載して実装必須にしておいて、型の設計時ではなくインスタンスを作るときに外側から適切な isFirstTurn
の値を設定する道筋を作ってみるのも悪くない選択肢のひとつに思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/23 07:21
2020/06/23 07:57
2020/06/23 08:29
2020/06/23 12:05