回答編集履歴

2

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

2020/06/23 14:02

投稿

TomohiroKumagai
TomohiroKumagai

スコア441

test CHANGED
@@ -271,3 +271,129 @@
271
271
 
272
272
 
273
273
  ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。
274
+
275
+
276
+
277
+
278
+
279
+ #### 質問者の「実装したいこと」に対する補足
280
+
281
+
282
+
283
+ オブジェクト指向と違って、特にプロパティーの実装位置が『親が持つか、子が持つか』的な大きく違う点があるので感覚的に違和感を感じるかもしれませんけれど、これまでに紹介した事柄を使って、質問者 ushi さんの挙げたコードを書き換えてみると、次のようになりそうです。
284
+
285
+
286
+
287
+ ```swift
288
+
289
+ protocol Base {
290
+
291
+
292
+
293
+ // 変数の実態は持てないが、存在する前提で参照できる。
294
+
295
+ var isFirstTurn: Bool! { get }
296
+
297
+
298
+
299
+ // 適用先で実装しなければならないメソッドを指定できる。
300
+
301
+ func setListener(result: (Int) -> Void)
302
+
303
+ }
304
+
305
+
306
+
307
+ extension Base {
308
+
309
+
310
+
311
+ // プロトコル拡張で共通のメソッドを実装できる。
312
+
313
+ func setBoardData() {
314
+
315
+
316
+
317
+ // この中で、存在するはずのプロパティーを参照できる。
318
+
319
+ if isFirstTurn {
320
+
321
+
322
+
323
+ }
324
+
325
+
326
+
327
+ // do something
328
+
329
+ }
330
+
331
+ }
332
+
333
+
334
+
335
+ class OnlineGame: Base {
336
+
337
+
338
+
339
+ // プロトコルが要求するプロパティーの実装が必要。
340
+
341
+ var isFirstTurn: Bool! = false
342
+
343
+
344
+
345
+ // プロトコルが要求するメソッドの実装が必要。
346
+
347
+ func setListener(result:(Int) -> Void) {
348
+
349
+ //... received signal
350
+
351
+ result(signal)
352
+
353
+ }
354
+
355
+ }
356
+
357
+
358
+
359
+ class COMGame: Base {
360
+
361
+
362
+
363
+ // プロトコルが要求するプロパティーの実装が必要。
364
+
365
+ var isFirstTurn: Bool! = false
366
+
367
+
368
+
369
+ // プロトコルが要求するメソッドの実装が必要。
370
+
371
+ func setListener(result: (Int) -> Void){
372
+
373
+
374
+
375
+ //... received signal
376
+
377
+ result(signal)
378
+
379
+ }
380
+
381
+
382
+
383
+ func comMove() -> Int {
384
+
385
+
386
+
387
+ //... think next move
388
+
389
+ return next
390
+
391
+ }
392
+
393
+ }
394
+
395
+ ```
396
+
397
+
398
+
399
+ プロパティーの初期化を `Base` ではなく各型が担わないといけないところもプロトコルを使った場合の特徴的なところです。この辺りが吉と出るか凶と出るかは状況によると思いますけれど、場合によってはプロトコルで、イニシャライザー `init(isFirstTurn:)` を記載して実装必須にしておいて、型の設計時ではなくインスタンスを作るときに外側から適切な `isFirstTurn` の値を設定する道筋を作ってみるのも悪くない選択肢のひとつに思います。

1

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

2020/06/23 14:02

投稿

TomohiroKumagai
TomohiroKumagai

スコア441

test CHANGED
@@ -161,3 +161,113 @@
161
161
 
162
162
 
163
163
  このように、性質に合わせたプロトコルを並列に加えていきながら、条件を絞ってプロトコル拡張を行うことで、まるで `Player` を既定クラスのように使いながら、クラス継承を重ねるような系統分けも行えます。
164
+
165
+
166
+
167
+ #### プロパティーについて
168
+
169
+
170
+
171
+ プロパティーは、プロトコルに実装することは `extension` を使ってもできないので、プロトコルに存在だけ宣言することで必須にして、型に都度実装する必要があります。
172
+
173
+
174
+
175
+ ```swift
176
+
177
+ protocol Player {
178
+
179
+
180
+
181
+ var cards: [Card] { get set }
182
+
183
+ var level: Int { get }
184
+
185
+ }
186
+
187
+
188
+
189
+ class OnlinePlayer : Player {
190
+
191
+
192
+
193
+ var cards: [Card]
194
+
195
+ var level: Int
196
+
197
+ }
198
+
199
+
200
+
201
+ class ComputerPlayer : Player {
202
+
203
+
204
+
205
+ var cards: [Card]
206
+
207
+ let level: Int = 1
208
+
209
+ }
210
+
211
+ ```
212
+
213
+
214
+
215
+ たとえばこのような記述が必要になるので、慣れないうちは手間が増えて複雑に感じられるかもしれないですけれど、プロパティー定義の記述自体はシンプルですし、プロトコルで実装が義務付けられているのも手伝って、型を定義するときに実装し忘れることもない(忘れるとコンパイラーに指摘される)ため、思いのほか負担なく実装することができます。
216
+
217
+
218
+
219
+ プロパティーの実装を型に定義しないといけないですけれど、存在自体は `Player` プロトコルに規定されているので、たとえば `Player` プロトコルを拡張して既定の実装を作るときには「プロパティーがあること前提で」コードを記述できるので、まるで `Player` にプロパティーが実装されているかのようにコードをかけます。
220
+
221
+
222
+
223
+ ```swift
224
+
225
+ extension Palyer {
226
+
227
+
228
+
229
+ func drawCard() -> Card? {
230
+
231
+
232
+
233
+ return cards.randomElement()
234
+
235
+ }
236
+
237
+ }
238
+
239
+ ```
240
+
241
+
242
+
243
+ もし、何も実装しなければ特定の値を取得できるようにしたいときには、既定の実装で計算型プロパティー (Computed Property) を実装してあげることは可能です。
244
+
245
+
246
+
247
+ ```swift
248
+
249
+ extension Player {
250
+
251
+
252
+
253
+ var level: Int {
254
+
255
+
256
+
257
+ get {
258
+
259
+
260
+
261
+ return 1
262
+
263
+ }
264
+
265
+ }
266
+
267
+ }
268
+
269
+ ```
270
+
271
+
272
+
273
+ ただし、値を読み書きするための記憶領域をプロパティーに持たせる (Stored Property) ことは、プロトコルではできないので、そういったものは必ず、型に直接実装する必要があります。