はじめに
この記事ではデフォルト引数を使って「完全コンストラクタ」を実現するという事を書いていきます。
完全コンストラクタとは
完全コンストラクタとは設計パターンの一つで、初期化時に全てのプロパティーの値をセットするという考え方です。
オブジェクト指向の話で良く出てくる話題で、シンプルな考え方なので覚えやすいし使いどころの多い考え方です。
完全コンストラクタの例
User1が完全コンストラクタを利用している例です。
初期化時に全てのプロパティーがセットされている事が分かります。
// User1が完全コンストラクタに準拠している
class User1 {
let name: String
init(name: String) {
self.name = name
}
}
let user1 = User1(name: "名前")
// User2は完全コンストラクタに準拠していない
class User2 {
var name: String = ""
}
let user2 = User2()
user2.name = "名前"
完全コンストラクタのメリット
完全コンストラクタの大きなメリットと思われる点は下の通りです。
安全性の向上と開発者への分かりやすさが大きなメリットになります。
1. 予期しない値の変更による不具合を避けれる
インスタンスの初期化位置と離れた所での値変更、メソッドの引数でインスタンスを渡したらメソッド内でプロパティー書き換えなどが該当します。
この辺りは見つけるのが困難なので可能なら避けたい所です。
2. 値のセットのタイミングによって起こる不具合を避けられる
メソッドがプロパティーの値を使う場合、プロパティーのセット前にメソッドを呼び出すと予期しない動きをする事があります。
完全コンストラクタを使えば値のセットが終わっている事が保証されるのでこういった不具合を回避できます。
私の場合はfontをセットする前にlabel.sizeToFite()を呼び出してしまう事が多いので、初期化時にfontもセットしたいと常々思います。
class User {
var firstName = ""
var lastName = ""
var fullName: String {
return firstName + lastName
}
}
let user = User()
print(user.fullName) // 空文字になる
user.firstName = "First"
user.lastName = "Last"
3. クラスを使う人が分かりやすい
初期化の時にプロパティーで渡すので、クラスを使う人がどのプロパティーが必須かが分かりやすいというメリットもあります。
安全性向上とは別の切り口のメリットです。
完全コンストラクタが実現しずらくなる状況
このように良いことが多い完全コンストラクタですが、プロパティーが増えてくると維持するのが大変になります。
初期化時に値を渡すので、プロパティーを1つ増やした時にはこのクラスを使っている箇所全てに影響が出ます。
都度コンストラクタを増やせば回避できるのですが、プロパティーを増やす度にコンストラクタを増やし続けるのも非常に大変です。
そういった時に役立つのがSwiftのデフォルト引数です。
*そもそもプロパティーが増えてきたらクラスの分割を考えるべきだったりするのですが、チームの方針などで分割しにくい事も有り得るので今回はその辺りは置いといて考えます。
Swiftのデフォルト引数
Swiftではメソッドがデフォルト引数を持つことができます。
func method(value1: Int = 0, value2: Int = 1, value3: Int = 2) {}
method(value2: 10)
それを使えばクラスの初期化は下のように書けます。
これならプロパティーが増えても既存のコードへの修正は最小限で済みますししプロパティーが増えても使う側は必要なものだけセットすればいいので非常に簡単です。
class User {
var firstName: String
var lastName: String
var age: Int
var prefecture: String
var fullName: String {
return firstName + lastName
}
init(firstName: String, lastName: String, age: Int = 20, prefecture: String = "東京") {
self.firstName = firstName
self.lastName = lastName
self.age = age
self.prefecture = prefecture
}
}
// ageだけ省略
let user = User(firstName: "firstName", lastName: "lastName", prefecture: "神奈川")
まとめとこの記事を書いたきっかけ
メソッドのラベルがあるおかげで堅牢なアプリケーションが作れるという話でした。
最後にこの記事を書こうと思った発端も少し話してみます。
記事を書くきっかけになったのは、メソッドのラベルでした。
Swiftのラベルとは下の例のlabel
部分です。
func method(label value: Int) {
}
method(label: 1)
昔Objective-cを使った時にラベルが非常に冗長に感じていてあまり良い仕様とは思っていませんでした。
Swiftができた時も既存のライブラリとの互換性の為にラベルを実装された事で残念に感じていました。
しかし最近になってラベルがあるおかげで好きな位置のデフォルト引数を指定でき、それのおかげでメソッドでコンストラクタを最小限にできる事を知って非常に感動しました。
この記事がきっかけでSwiftのメソッドのラベルって結構面白いと思って頂けたら幸いです。
参考URL
https://codeiq.jp/magazine/2013/10/496/
http://fukuchiharuki.me/wiki/index.php?%E8%A8%AD%E8%A8%88%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%2F%E5%AE%8C%E5%85%A8%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF