はじめに
Swift でコードを書いていると Protocol を使う場面は多いですが、
Protocol にどのような名前をつけるべきか、今までよく悩んできました。
標準ライブラリでよく見られる「 Equatable
」や「 Hashable
」のような、
「 **able
」 Protocol はどういう時に定義すべきなのかも曖昧だったので、
一般的な命名パターンをある程度把握しておきたいと思いました。
そこで本記事では、一般的にどのように Protocol の命名がなされているのかを Web で調査して、自分なりに整理してみました。
公式ドキュメントの記述
API Design Guidelines には、プロトコルの命名規則についてはそれほど多く語られておらず、
主な記述は以下の数行にまとめられています。
- Protocols that describe what something is should read as nouns (e.g. Collection).
何かが何であるかを記述するプロトコルは、名詞として読むべきである(例:Collection
)
- Protocols that describe a capability should be named using the suffixes able, ible, or ing (e.g. Equatable, ProgressReporting).
能力を記述するプロトコルは、接尾辞able、ible、またはingを使用して命名されるべきである(例:Equatable
、ProgressReporting
)
短い説明ではありますが、プロトコルに名前をつける際には基本的に
上記ガイドラインで説明されているいずれかのパターンに分類できると思います。
それぞれのパターンについて、以下で詳しく見ていきます。
基本的なプロトコル命名規則の分類
上述した API Design Guidelines の定義に基づくと、
プロトコルの命名規則は以下の3パターンに分類できます。
- プロトコル名が名詞になるパターン
- プロトコル名の末尾が「ing」になるパターン
- プロトコル名の末尾が「able」または「ible」になるパターン
それぞれのパターンがどのようなケースで適用されるかについて、
具体例とともに以下に記載します。
1. プロトコル名が名詞になるパターン
適用条件
- 準拠型が「何であるか」を表すプロトコルは、名詞で命名する
もう少し具体的にいうと、以下のように表現できると思います。
- 準拠型を「カテゴリ」や「グループ」等に分類できる場合、その分類名(名詞)をプロトコル名とする
標準ライブラリの例
Sequence
MTLDevice
- SwiftUI の
View
,Shape
など
具体例
「動物であること」を表すプロトコル
// 「動物であること」を表すプロトコル
protocol Animal {
func eat(_ food: Eatable)
}
struct Dog: Animal { ... }
struct Cat: Animal { ... }
「車であること」を表すプロトコル
// 「車であること」を表すプロトコル
protocol Car {
var fuelLevel: Int { get }
}
struct Prius: Car { ... }
struct Crown: Car { ... }
「BLEデバイスであること」を表すプロトコル
// 「BLEデバイスであること」を表すプロトコル
protocol BLEDevice {
var batteryLevel: Int { get }
func connect(_ device: BLEDevice)
}
struct BLEEarphone: BLEDevice { ... }
struct BLESpeaker: BLEDevice { ... }
「APIリクエストであること」を表すプロトコル
// 「APIリクエストであること」を表すプロトコル
protocol APIRequest {
var httpHeaders: [String: String] { get }
var httpMethod: String { get }
}
struct LoginAPIRequest: APIRequest { ... }
2. プロトコル名の末尾が「ing」になるパターン
適用条件
以下条件の両方に合致する場合、このパターンを適用します。
- プロトコルが「能力」や「機能」を説明する場合
- プロトコルの準拠型が「 処理を実行する主体(Subject) 」である場合
標準ライブラリの例
ProgressReporting
標準ライブラリには「ing」で終わるプロトコルはあまりなさそうでした。
具体例
「デバッグログを出力する」機能を表すプロトコル
// 「デバッグログを出力する」機能を表すプロトコル
protocol DebugLogging {
func log(_ message: String)
}
// 準拠型自身が「ログを出力する」処理を行う主体である
struct DebugLogger: DebugLogging { ... }
「音声を録音する」機能を表すプロトコル
// 「音声を録音する」という機能を表すプロトコル
protocol VoiceRecording {
func startRecording()
func stopRecording()
}
// 準拠型自身が「音声を録音する」処理を行う主体である
struct VoiceRecorder: VoiceRecording { ... }
3. プロトコル名の末尾が「able」または「ible」になるパターン
適用条件
以下条件の両方に合致する場合、このパターンを適用します。
- プロトコルが「能力」や「機能」を説明する場合
- プロトコルの準拠型が「 処理される対象(Object) 」である場合
標準ライブラリの例
-
Equatable
- 「値が等しいか比較できる」という能力を表すプロトコル
- 自身が「等しいか比較される」対象である
-
Hashable
- 「ハッシュ値を生成できる」という能力を表すプロトコル
- 自身が「ハッシュ値を生成される」対象である
-
CustomStringConvertible
- 「カスタム文字列に変換できる」という能力を表すプロトコル
- 自身が「カスタム文字列に変換される」対象である
具体例
「リセットできる」能力を表すプロトコル
// 「リセットできる」能力を表すプロトコル
protocol Resettable {
mutating func reset()
}
// 準拠型自身が「リセットされる」対象である
struct AccountSettings: Resettable { ... }
「食べることができる」能力を表すプロトコル
// 「食べることができる」能力を表すプロトコル
protocol Eatable {
var kcal: Int { get }
}
// 準拠型自身が「食べられる」対象である
struct Apple: Eatable { ... }
struct Peach: Eatable { ... }
// Eatable なオブジェクトは、先述した Animal によって食べられる
let dog = Dog()
dog.eat(Peach())
備忘録: 個人的に悩ましいプロトコルの命名について
基本的なプロトコルの命名規則についてはある程度理解できましたが、
「この場合はどのように命名したらいいのだろう」と個人的に悩ましいケースがいくつかありました。
以下ではそうしたケースについて見ていき、個人的に考えたことを備忘録として記載しています。
アーキテクチャ・デザインパターンの命名
アーキテクチャやデザインパターンで一般的に使用される名称が存在する場合は、
その名称をプロトコル名として使用する方針が良いかと考えています。
具体例は後述していますが、
例えば MVVM アーキテクチャでは 「 **ViewModel
」プロトコルを定義し、
MVP アーキテクチャでは「 **Presenter
」プロトコルを定義する方針です。
理由
理由としては、API Design Guidelines - Use Terminology Well の記述を参考にしており、意訳すると以下の通りです。
- 専門用語や、広く知られている用語は、できるだけそのまま使うこと
例えば、標準ライブラリにもデザインパターン由来と想像されるプロトコルの命名が見られます。
-
UITableViewDelegate
などの**Delegate
プロトコル- デリゲートパターン
- Combine の
Publisher
/Subscriber
プロトコル- Publish–subscribe パターン
例えば、Publisher
という広く知られている名称を採用することで、
このプロトコルがどんな機能を持っているかを感覚で理解できること、
同時に Subscriber
プロトコルが存在することも推測できるかと思います。
また、広く知られている名称は Web で検索するとその元となっているパターンがすぐに調べられるので、コード理解の助けにもなりそうです。
具体例: アーキテクチャ
- MVP
-
**View
プロトコル -
**Presenter
プロトコル
-
- MVVM
-
**View
プロトコル -
**ViewModel
プロトコル
-
- VIPER
-
**View
プロトコル -
**Presenter
プロトコル -
**Router
プロトコル -
**Interactor
プロトコル
-
具体例: デザインパターン
- Repository パターン
-
**Repository
プロトコル
-
- Iterator パターン
- 標準ライブラリの例)
IteratorProtocol
AsyncIteratorProtocol
- 標準ライブラリの例)
プロトコル名と準拠型名が同じになる場合
例えば、MVVM で「メイン画面の ViewModel」を作成するとします。
この ViewModel のプロトコルと準拠型を定義しようとすると、
両方 MainViewModel
という名前になってしまうかもしれません。
この場合、どのようにして名前の競合を防ぐことができるでしょうか?
例えば以下のようなアプローチが考えられます。
- 準拠型名の接尾辞に任意の文字列をつける
MainViewModelImpl
- 準拠型の接頭辞に任意の文字列をつける
AppMainViewModel
TYMainViewModel
悩ましいですが、私は以下の命名で区別しようと考えています。
-
プロトコルの末尾に
Protocol
をつけるMainViewModelProtocol
- 準拠型は
MainViewModel
など
理由
理由は以下の通りです。
-
API Design Guidelines にも、プロトコル名の末尾に
Protocol
をつけて衝突を回避する例が記載されていること-
If an associated type is so tightly bound to its protocol constraint that the protocol name is the role, avoid collision by appending Protocol to the protocol name:
-
- 標準ライブラリのプロトコルにも末尾が
Protocol
の命名は見られること- 例)
-
AsyncIteratorProtocol
- 準拠型は
AsyncIterator
- 準拠型は
-
NSObjectProtocol
- 準拠型は
NSObject
- 準拠型は
-
- 例)
型情報(Protocol
)の繰り返しになってしまい少し微妙な気もしますが、
他に良いアプローチが見つかるまではこの方法で検討しています。
ColorProvider
vs ColorProviding
参考文献で意見が分かれていた「色を提供する」というプロトコルについて考えてみます。
このプロトコルの命名として、参考文献では以下の2パターンが提示されていました。
-
ColorProvider
- 「色の提供者である」とみなし、名詞で命名
- この命名を採用している参考文献
- 「色の提供者である」とみなし、名詞で命名
-
ColorProviding
- 「色を提供する」機能を持つとみなす
- 準拠型自身が「色を提供する主体」とみなし、「ing」で命名
- この命名を採用している参考文献
どの命名を採用するかはプロジェクトの方針次第で絶対的な正解はないと思いますが、
個人的には ColorProvider
が良いかなと考えています。
理由としては以下の通りです。
-
Provider
という名称は広く知られていると思われること- 先述した「API Design Guidelines - Use Terminology Well」 の「専門用語はそのまま使う」と同じ考え方で、Provider は広く知られている用語とみなし、そのまま Provider プロトコルとして命名
- 公式に RepositoryProvider というプロトコルがあること
- Provider プロトコルは公式でも使用されていることから不自然な命名ではないと判断できます
おわりに
プロトコル名について言及されているWeb上の記事が多くあり、大変参考になりました。
ただ、記事によって解釈が異なっている部分もあったりするため、
「絶対的な正解はこれだ!」というものはなさそうに思います。
なので、どういう命名にするかは自分のプロジェクトの方針に応じて柔軟に変えていければと思います。
本記事も私の主観でまとめたものであり、解釈がおかしい恐れも多分にあると思いますが、
その際はご指摘いただけますと幸いです🙇♂️