プロトコルで名前の衝突をさける方法は…?
カジュアル Swift プログラミング
Swift 2.0 でプロトコル拡張が積極的に使われそうな印象を受けて、プロトコルが求める機能の名前が他のプロトコルのと衝突しそうに思えてきました。
そこで、どんな風にすると名前の衝突を抑えることができるかなと想像してみることにしました。
先ほど こちら で、Swift 2.0 のプロトコル主体なプログラミングの話をしましたが、そうすると気になってくるのが名前の衝突です。
名前の衝突はさけられない?
Swift ではモジュールによって名前空間が分けられ、同じ名前の型を持つことができます。これによって、頭に NS とか UI とかをつけない自然な名前を安心してつけられるようになりました。
ただ、プロトコル自体の名前までは名前空間で区別できても、それが要求するメソッドやプロパティの名前までは区別できません。
そんな名前が衝突したときに、両方のプロトコルが同じ主旨の実装を求めているならいいのですが、違った場合には区別することができません。
衝突が問題にならない例
たとえば AudioModule と VideoModule という 2 つのモジュールがあったとします。そしてそのそれぞれに CodecType というプロトコルが定義されていたとします。
このとき、たとえばこのプロトコルに『名前の取得』を目的にした name プロパティが定義されていたとします。
/// AudioModule
protocol CodecType {
var name:String { get }
}
/// VideoModule
protocol CodecType {
var name:String { get }
}
ただ、この場合であれば衝突が問題になることは少ないように思います。
これら両方のプロトコルに準拠させたとき、ひとつの name プロパティを実装することで、両方のプロトコルに対応できたことになりますが『名前を返す』というプロパティであれば、どちらの機能を期待されたとしても、同じ名前を返すことは多いでしょう。
struct MultimediaContent : AudioModule.CodecType, VideoModule.CodecType {
var name:Strig {
return "The Movie"
}
}
衝突が問題になり得る場合
これが decode という名前のメソッドだった場合、問題になる場合があります。
/// AudioModule
protocol CodecType {
func decode() -> NSData
}
/// VideoModule
protocol CodecType {
func decode() -> NSData
}
このようなとき、たとえば AudioModule の decode メソッドであれば音声データのデコードを目的としたものであることが想像でき、VideoModule の decode メソッドであれば映像データのデコードを目的としたものであると想像できます。
それでも、この両方のプロトコルに準拠させた型では decode メソッドの実装をひとつしか持てないため、結果的に両方に準拠させることはできなくなります。
名前の衝突をさけるためには...
ただ、Swift 2.0 からはプロトコルに extension で実装を追加する方法が自然なものになりそうなので、そうなると、名前の衝突をさけるための方法を考えておきたいところです。
自作プロトコル以外は exntension しない案は効果が低そう
Objective-C までであれば、ほとんどがクラス継承による設計だったため、それまでの継承関係さえ自分が考慮していれば名前の衝突をさけることもできました。
同じように Swift でも自分で作ったプロトコル以外は extension しないようにすれば衝突を抑えられるかな、と思ったんですが、クラスと違ってプロトコルの場合、さまざまなプロトコルを混ぜて使うため、効果はあまり期待できなそうです。どれかの仕様が変わったときに、名前が重なる可能性が出てきます。
衝突しそうな名前をさける案
名前空間で分けられないなら、衝突をさける基本的な方法として『重なるような名前を使わない』というのが挙げられると思います。
先ほどの例であれば decode というメソッドだと『デコードする』という広い範囲を表現する名前のため、それこそ上記の例だけでなく、たとえば音声を FAX 機能にも対応させたりしたときに MODEM プロトコルの decode メソッドとだって衝突する可能性があります。
それをさけるためには、もう少し具体的な名前をつける必要があります。
/// AudioModule
protocol CodecType {
func audioDecode() -> NSData
}
/// VideoModule
protocol CodecType {
func videoDecode() -> NSData
}
このようにすることで、両方の働きができる型を作ったときにも問題なく実装ができます。
import AudioModule
import VideoModule
struct MultimediaContent : AudioModule.CodecType, VideoModule.CodecType {
func audioDecode() -> NSData {
}
func videoDecode() -> NSData {
}
}
これくらいまで名前を詳しく分ければ、名前が衝突する可能性はなさそうです。もし audioDecode という名前が衝突してしまうことがあったとしても、同じ用途が期待される可能性が高まります。
もし違う用途が求められたとしても、そのときはそもそも型からして違うことになる可能性が出てきます。たとえば MP3AudioContent と WaveAudioContent みたいな感じですね。
これくらいの次元であれば、名前の衝突は問題になってこない気がします。
型で衝突をさける案
ただ、このような名前のつけ方は神経を使うのと、良い名前が見つからなくて Objective-C のようなとても長い名前をつけるのが主流になってしまう危険性も秘めています。
それをさける方法として、Swift のオーバーロードを活かす方法も考えられます。
/// AudioModule
protocol CodecType {
func decode() -> AudioData
}
/// VideoModule
protocol CodecType {
func decode() -> VideoData
}
それぞれのモジュールに AudioData と VideoData という独自の型を定義して、それを返すメソッドをプロトコルで要求するようにします。
そうすると、Swift では戻り値の違いによってメソッドをオーバーロードできるため、これらのメソッドが異なるものとして実装できるようになります。そしてそれぞれが、自分自身のモジュールで定義された型を扱っているため、他のプロトコルと偶然一致する可能性は極めて低くなりそうです。
import AudioModule
import VideoModule
struct MultimediaContent : AudioModule.CodecType, VideoModule.CodecType {
func decode() -> AudioData {
}
func decode() -> VideoData {
}
}
もし両方が同じ Data という名前の型を定義して使っていたとしても、型名であれば AudioModule.Data や VideoModule.Data のように名前空間で区別できるため、名前の衝突がすぐに問題になることもありません。
呼び出すときは、目的の型を受け皿にして decode メソッドを呼び出すことで、呼び出すメソッドを選ぶことができます。
let movie = MultimediaContent()
// AudioModule 側の decode メソッドが呼ばれる。
let data:AudioData = movie.decode()
// VideoModule 側の decode メソッドが呼ばれる。
let data:VideoData = movie.decode()
流れ的に、変数に型名を添えるのが難しい場合でも、最後に as 演算子を使って型を明記することで、型推論を使った代入文を書くこともできます。
let movie = MultimediaContent()
// AudioModule 側の decode メソッドが呼ばれる。
let data = movie.decode() as AudioData
// VideoModule 側の decode メソッドが呼ばれる。
let data = movie.decode() as VideoData
まとめ
このように考えてみると『衝突しそうな名前をさける案』と『型で衝突をさける案』の両方を上手に組み合わせて使い分けるのが、今のところいちばん良さそうな方法のように思えました。
名前に頼りすぎると延々と長くなりますし、型でさける方法だと使い勝手が悪くなる場面もありそうなので。もっとも後者が気持ち悪く感じられるのは『単にそういう書き方に慣れていないだけ』という理由もあるかもわかりません。