teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

質問者の「実装したいこと」に対する補足を追記しました。

2020/06/23 14:02

投稿

TomohiroKumagai
TomohiroKumagai

スコア441

answer CHANGED
@@ -134,4 +134,67 @@
134
134
  }
135
135
  ```
136
136
 
137
- ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。
137
+ ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。
138
+
139
+
140
+ #### 質問者の「実装したいこと」に対する補足
141
+
142
+ オブジェクト指向と違って、特にプロパティーの実装位置が『親が持つか、子が持つか』的な大きく違う点があるので感覚的に違和感を感じるかもしれませんけれど、これまでに紹介した事柄を使って、質問者 ushi さんの挙げたコードを書き換えてみると、次のようになりそうです。
143
+
144
+ ```swift
145
+ protocol Base {
146
+
147
+ // 変数の実態は持てないが、存在する前提で参照できる。
148
+ var isFirstTurn: Bool! { get }
149
+
150
+ // 適用先で実装しなければならないメソッドを指定できる。
151
+ func setListener(result: (Int) -> Void)
152
+ }
153
+
154
+ extension Base {
155
+
156
+ // プロトコル拡張で共通のメソッドを実装できる。
157
+ func setBoardData() {
158
+
159
+ // この中で、存在するはずのプロパティーを参照できる。
160
+ if isFirstTurn {
161
+
162
+ }
163
+
164
+ // do something
165
+ }
166
+ }
167
+
168
+ class OnlineGame: Base {
169
+
170
+ // プロトコルが要求するプロパティーの実装が必要。
171
+ var isFirstTurn: Bool! = false
172
+
173
+ // プロトコルが要求するメソッドの実装が必要。
174
+ func setListener(result:(Int) -> Void) {
175
+ //... received signal
176
+ result(signal)
177
+ }
178
+ }
179
+
180
+ class COMGame: Base {
181
+
182
+ // プロトコルが要求するプロパティーの実装が必要。
183
+ var isFirstTurn: Bool! = false
184
+
185
+ // プロトコルが要求するメソッドの実装が必要。
186
+ func setListener(result: (Int) -> Void){
187
+
188
+ //... received signal
189
+ result(signal)
190
+ }
191
+
192
+ func comMove() -> Int {
193
+
194
+ //... think next move
195
+ return next
196
+ }
197
+ }
198
+ ```
199
+
200
+ プロパティーの初期化を `Base` ではなく各型が担わないといけないところもプロトコルを使った場合の特徴的なところです。この辺りが吉と出るか凶と出るかは状況によると思いますけれど、場合によってはプロトコルで、イニシャライザー `init(isFirstTurn:)` を記載して実装必須にしておいて、型の設計時ではなくインスタンスを作るときに外側から適切な `isFirstTurn` の値を設定する道筋を作ってみるのも悪くない選択肢のひとつに思います。

1

プロパティーについてを追記しました。

2020/06/23 14:02

投稿

TomohiroKumagai
TomohiroKumagai

スコア441

answer CHANGED
@@ -79,4 +79,59 @@
79
79
  }
80
80
  ```
81
81
 
82
- このように、性質に合わせたプロトコルを並列に加えていきながら、条件を絞ってプロトコル拡張を行うことで、まるで `Player` を既定クラスのように使いながら、クラス継承を重ねるような系統分けも行えます。
82
+ このように、性質に合わせたプロトコルを並列に加えていきながら、条件を絞ってプロトコル拡張を行うことで、まるで `Player` を既定クラスのように使いながら、クラス継承を重ねるような系統分けも行えます。
83
+
84
+ #### プロパティーについて
85
+
86
+ プロパティーは、プロトコルに実装することは `extension` を使ってもできないので、プロトコルに存在だけ宣言することで必須にして、型に都度実装する必要があります。
87
+
88
+ ```swift
89
+ protocol Player {
90
+
91
+ var cards: [Card] { get set }
92
+ var level: Int { get }
93
+ }
94
+
95
+ class OnlinePlayer : Player {
96
+
97
+ var cards: [Card]
98
+ var level: Int
99
+ }
100
+
101
+ class ComputerPlayer : Player {
102
+
103
+ var cards: [Card]
104
+ let level: Int = 1
105
+ }
106
+ ```
107
+
108
+ たとえばこのような記述が必要になるので、慣れないうちは手間が増えて複雑に感じられるかもしれないですけれど、プロパティー定義の記述自体はシンプルですし、プロトコルで実装が義務付けられているのも手伝って、型を定義するときに実装し忘れることもない(忘れるとコンパイラーに指摘される)ため、思いのほか負担なく実装することができます。
109
+
110
+ プロパティーの実装を型に定義しないといけないですけれど、存在自体は `Player` プロトコルに規定されているので、たとえば `Player` プロトコルを拡張して既定の実装を作るときには「プロパティーがあること前提で」コードを記述できるので、まるで `Player` にプロパティーが実装されているかのようにコードをかけます。
111
+
112
+ ```swift
113
+ extension Palyer {
114
+
115
+ func drawCard() -> Card? {
116
+
117
+ return cards.randomElement()
118
+ }
119
+ }
120
+ ```
121
+
122
+ もし、何も実装しなければ特定の値を取得できるようにしたいときには、既定の実装で計算型プロパティー (Computed Property) を実装してあげることは可能です。
123
+
124
+ ```swift
125
+ extension Player {
126
+
127
+ var level: Int {
128
+
129
+ get {
130
+
131
+ return 1
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。