Skip to content

swift_access

kobayashiharuto edited this page Aug 7, 2021 · 18 revisions

アクセス修飾子

オブジェクトのように、取り繕って。醜い部分は隠せばいいんだ。


問題のあるコード

2人で割り勘できる計算機クラスでも作るか~

といって以下の Calc クラスを作りました。

class Calc {
    var member = 2
    var tax = 0.1
    
    // 実際に使う計算メソッド 
    func calc(price: Int) {
        var priceWithTax = calcTax(price) // 消費税追加
        var pricePerPerson = calcPerPerson(priceWithTax) // 割り勘計算
        print("一人当たり\(pricePerPerson)円です!")
    }
    
    // 税率計算用メソッド
    func calcTax(price: Int) {
        return price * (tax + 1)
    }
    
    // 割り勘メソッド
    func calcPerPerson(price: Int) {
        return price / self.member
    }
}

calc = Calc()
calc.calc(1000) // 一人当たり 550 円です!

さて、どんな気持ちですか?別にどうも思わない?

そうですか...


問題のあるリモコン

今日は誕生日。あなたは両親から車のラジコンをもらいました。

「やったー!早速遊んでみよー!」

喜び勇んで開けてみると、中からこんなリモコンが出てきました。

img

さて、どんな気持ちですか?

「急ブレーキボタン、自動で使われるんだったら押せるようにすんなや!間違って押して壊れるわ!」

「モーターのトルクとか電圧とか知らんわ!勝手に決めとけ!調節できるようにすんな!」

「速度調節、壊れるなら調節できるようにすんな!友達に貸したら絶対最大速度にしてぶっ壊すわ!」

「多すぎてどれ押していいかわかんねえわ!自動で使われるボタンいらねええええ!」


じゃあさ

確かにそうですね。では、さっきのコードはどうですか?

class Calc {
    var member = 2
    var tax = 0.1
    
    func calc(price: Int) {
        var priceWithTax = calcTax(price)
        var pricePerPerson = calcPerPerson(priceWithTax)
        print("一人当たり\(pricePerPerson)円です!")
    }
    
    func calcTax(price: Int) {
        return price * (tax + 1)
    }
    
    func calcPerPerson(price: Int) {
        return price / self.member
    }
}

calc = Calc()
calc.tax = 0 				// 税率が変えられちゃうね
calc.member = 0 			// メンバーの値が変えられちゃうね
calc.calcTax(1000) 			// どのメソッドを使っていいかわかんないね
calc.calcPerPerson(1000) 	        // あれ、そういやメンバーの人数0にしたよな。こr(ランタイムエラー)

「税率とか変わらんやろ!勝手に決めとけ!調節できるようにすんな!」

「人数、壊れるなら調節できるようにすんな!そもそも二人用なんだから変えなくていいやろ!」

「calcTax とか calcPerPerson とか内部でしか使わなくていいなら使えるようにすんなや!どれ使っていいかわからんわ!」

そういうことですね。問題点がわかっていただけたでしょうか。


こんな時にアクセス修飾子

外部から使われたくないプロパティやメソッドは、private をつけることで外部から触れなくなります。もちろんクラス内では触れられます。

これをアクセス修飾子と言います。アクセスを制限するやつってわけですね。

不正にアクセスしようとするとコンパイル時にエラーが出るので、ランタイムエラーや意図しない動作を防げます。

class Calc {
    private var member = 2
    private var tax = 0.1
    
    func calc(price: Int) {
        var priceWithTax = self.calcTax(price)	// もちろん内部では触れられる
        var pricePerPerson = self.calcPerPerson(priceWithTax)	// もちろん内部では触れられる
        print("一人当たり\(pricePerPerson)円です!") 
    }
    
    private func calcTax(price: Int) {
        return price * (tax + 1)
    }
    
    private func calcPerPerson(price: Int) {
        return price / self.member  // もちろん内部では触れられる
    }
}

calc = Calc()
calc.tax = 0 				// 税率が変えられない(コンパイル時エラー)
calc.member = 0 			// メンバーの値が変えられない(コンパイル時エラー)
calc.calcTax(1000) 			// メソッドも使えない(コンパイル時エラー)

嬉しいことに、private のやつはそもそも Xcode の予測に出なくなります!

え?なに?クラス内で触れられるんならエラー防ぎきれないじゃんって?
確かにそうですが...それは仕方がないのです。

さっきのリモコンの例を思い出してください。
そもそも内部の実装が狂って壊れるのは、リモコンの作成者の問題です。これは防ぎようがありません。気を付けて実装するしかないのです。
しかし、リモコンを使う人が押してはいけないボタンを押して壊れるのは、ボタンさえなければ防げたはずです。
このように、防げるはずのものは防ぎたいというのが考え方となります。

クラスも同じで、クラスをインスタンス化して使うときは、何をしても安全であってほしいわけです。
このメソッド使っても大丈夫かな~って不安になり、クラス内部の実装を読みにいく、なんてことは避けたいわけです。
何をしても安全なら、内部実装の事を気にしながら使う必要は無いですよね?
これは複数人開発だったり、他人が作ったライブラリを使ってるときになんかに特に重宝します。

つまり、コントローラーの内部構造を気にしなくてもラジコンを安心して動かしたいってことです。
速度最大にしても大丈夫かな?とか考えてコントローラー分解して中身確認するとかありえないですよね...
同じく、インスタンスを使うときは内部構造なんて見なくても、安心して使えるようになりたいということです。

オブジェクトっぽいと思いません?内部のごちゃごちゃを隠ぺいして、使う必要があるものだけ使えるようにする...
個人的に大好きな機能の一つです。


読み取りはしたけど、書き変えはしたくない!

さて、先ほどのコードだと読み取りも書き変えも出来ない状態でした。

calc.tax = 0 				// 税率が変えられない(コンパイル時エラー)
print(calc.tax)             // 読み取りもできない(コンパイル時エラー)

でも、これだと読み取りはしたいんだけどなぁーって場合に困ります。
そんなときは private(set) を使いましょう!

set...つまり書き込みをプライベートにってことですね。

class Calc {
    private var member = 2
    private(set) var tax = 0.1  // 書き込みだけプライベートに!
    
    func calc(price: Int) {
        var priceWithTax = self.calcTax(price)	// もちろん内部では触れられる
        var pricePerPerson = self.calcPerPerson(priceWithTax)	// もちろん内部では触れられる
        print("一人当たり\(pricePerPerson)円です!") 
    }
    
    private func calcTax(price: Int) {
        return price * (tax + 1)
    }
    
    private func calcPerPerson(price: Int) {
        return price / self.member // もちろん内部では触れられる
    }
}

calc.tax = 0 		    // 税率が変えられない(コンパイル時エラー)
print(calc.tax)             // 0.1 (読み取りはできる!)

サクッと実現できましたね!

Clone this wiki locally