オブジェクト指向
Swift

【swift】オブジェクト指向について簡単にまとめてみた【初心者向け?】


概要

「オブジェクト指向」というものがなんなのか、

どういう意味があるのかを具体的に把握できていなかったので、

自分なりに整理してみました\\\٩( 'ω' )و ////

イメージ→設計面でのメリット→実際どう書くのかの3部構成でまとめてみようと思います🐱

分かりにくいところや間違いなどありましたらコメント頂けるととても嬉しいです🙇‍♂️


オブジェクト指向あるある

いろんな記事や本を読むと結構、


  • クラス:たい焼きを焼く機械🐟

  • オブジェクト:たい焼き🐟

として、1つの機械で色々な味のたい焼きを作っていくというイメージや、


  • カレーを作るには、野菜切って炒めて煮込んで...と色々な行程があるが、
    コックさんにお願いすれば、「カレー作って」というだけでその行程を考慮せずにカレーを作ることができる。

として、処理の内容を隠蔽して呼ぶ側が気にしなくていい様にするイメージなどがありました。

最初の取っ掛かりとしては私は何と無くイメージはわかる、となったのですが、

これだけだと「で、結局何がいいのだ?🤔」と悩む結果になりました。


設計面からのアプローチ

イメージが浮かんだところで、システム設計の原則に基ずいたオブジェクト指向の必要性を考えてみようと思います\\\٩( 'ω' )و ////

こちら文献を読んでみて、「で、オブジェクト指向って結局何がいいの?🤔」についてを一言返すとしたら、


  • オブジェクト指向(での設計)は変更を楽で安全にするための工夫

というところに落ちるのかなと思います。

swiftやJavaはいわゆる「オブジェクト指向プログラミング言語」と呼ばれるものに分類されると思うのですが、この記事の言葉を借りると、


オブジェクト指向プログラミングをJavaで行う事はできます。でもJavaで作ると必ずオブジェクト指向プログラミングになるわけではありません。


とあり、これってつまり、


  • iOSアプリを作る際、swiftでオブジェクト指向設計にもとずいて作るのは、言語がそう書かなければいけないというわけではなく、誰が見てもわかりやすく変更を楽にするため

ということになりそうです。

その目的のために

オブジェクト指向の原則に基づいて設計を行う、というのが大事なんだなとわかりました。

(なんかこれをちゃんと先に念頭においた方がなんか腹落ちしやすい気がします)

また、上記本では


  • データを持つクラスにロジックを置くことがオブジェクト指向設計の原則である

と書いてありましたのでこちらについても言及したいと思います\\\٩( 'ω' )و ////


オブジェクト指向の要素

じゃあそのオブジェクト指向の原則ってなんなんだ、というと、


  • 多態性 (polymorphism)

  • 継承 (inheritance)

  • カプセル化 (encapsulation) - 内部隠蔽

の3つが主に肝の様です。オブジェクト指向3大要素

それぞれを見て行きましょう\\٩( 'ω' )و ////


🍎ポリモーフィズム(多様性)

ポリモーフィズム(polymorphism / 多相)とは、

「複数の異なる型に対して、共通のインタフェースを提供すること」です。

簡単にいうと、

「🐱とか🐶をいい具合に呼ぶ様にできる書き方」って感じでしょうか?🤔

そして、ポリモーフィズムには「アドホック多相、パラメータ多相、部分型付け」と3種類あります。


🍏アドホック多相

例えば、

🐱を呼ぶときは、「猫、おいで!」、🐶を呼ぶときは「Dog,Come here!」と言わないといけないよりも、

どちらに対しても「(猫/犬)、おいで!」と同じ呼び方ができる方がいいですよね。

以下のコードの様にメソッドを定義すると、callAnimal(of: xxx)という共通の呼び出し方で呼べる様になります。

class Cat {

let name: String = "たま"
let like: String = "魚"
}

class Dog {
let name: String = "ぽち"
let like: String = "肉"
}

func callAnimal(of animal: Cat) -> String {
return animal.name + "は猫なので、" + animal.like + "が好きです。"
}

func callAnimal(of animal: Dog) -> String {
return animal.name + "は犬なので、" + animal.like + "が好きです。"
}

callAnimal(of: Cat()) // →「たまは猫なので、魚が好きです。」
callAnimal(of: Dog()) // →「ぽちは犬なので、肉が好きです。」


🍏パラメータ多相

例えば、

🐱と🐶どっちがその場にいないかわからない場合、

アドホック多相の様に「(猫/犬)、おいで!」と対象を指定するのではなく、「おいで!」と呼ぶだけで、

その場にいるどちらかが自動的に呼べる様になるので、callAnimalのメソッドが汎用的に定義できる様になります。

以下のコードでは、ジェネリクス関数を用いることで、

型の指定をしなくても必要に応じて処理を分けることができる様になります。

class Cat {

let name: String = "たま"
let like: String = "魚"
}

class Dog {
let name: String = "ぽち"
let like: String = "肉"
}

func callAnimal<T>(of t: T) -> String {
if let cat = t as? Cat {
return cat.name + "は猫なので、" + cat.like + "が好きです。"
}
if let dog = t as? Dog {
return dog.name + "は犬なので、" + dog.like + "が好きです。"
}
return ""
}

callAnimal(of: Cat()) // →「たまは猫なので、魚が好きです。」
callAnimal(of: Dog()) // →「ぽちは犬なので、肉が好きです。」


🍏部分型付け

これは、みんな大好き?protocolや継承を使った書き方です。

🐱と🐶を動物という括りでまとめて、必要な情報は動物クラスの方で管理する、というやり方です。


protocolを使う

protocol Animal {

var name: String { get }
var like: String { get }
}

class Cat: Animal {
var name: String
var like: String

let friend = "とら"

init(name: String,
like: String) {
self.name = name
self.like = like
}
}

class Dog: Animal {
var name: String
var like: String

init(name: String,
like: String) {
self.name = name
self.like = like
}
}

func callAnimal(of animal: Cat) -> String {
return animal.name + "は猫なので、" + animal.like + "が好きです。お友達は" + animal.friend + "さんです。"
}

func callAnimal(of animal: Dog) -> String {
return animal.name + "は犬なので、" + animal.like + "が好きです。"
}

callAnimal(of: Cat(name: "たま", like: "魚")) // →「たまは猫なので、魚が好きです。お友達はとらさんです。」
callAnimal(of: Dog(name: "ぽち", like: "犬")) // →「ぽちは犬なので、肉が好きです。」



継承を使う

class Animal {

let name: String
let like: String

init(name: String,
like: String) {
self.name = name
self.like = like
}
}

class Cat: Animal {
let friend = "とら"
}

class Dog: Animal {
}

func callAnimal(of animal: Cat) -> String {
return animal.name + "は猫なので、" + animal.like + "が好きです。お友達は" + animal.friend + "さんです。"
}

func callAnimal(of animal: Dog) -> String {
return animal.name + "は犬なので、" + animal.like + "が好きです。"
}

callAnimal(of: Cat(name: "たま", like: "魚")) // →「たまは猫なので、魚が好きです。お友達はとらさんです。」
callAnimal(of: Dog(name: "ぽち", like: "犬")) // →「ぽちは犬なので、肉が好きです。」



🍎継承

継承は、↑の多様性の部分型付けと同じです。

なるべく共通化できる部分を作って、重複を減らし再利用性を高める、というところがポイントです💡


🍎カプセル化

カプセル化 (encapsulation)とは、隠蔽のことです。

お薬のカプセル同じく、薬の効果を中に閉じ込めるイメージ。

プログラムだと薬が各プロパティになると思います。

これを意識するメリットとしては、


  • 意図しないところで値が好きな様に書き換えられてしまうことを防ぐことができる

です。

値をprivateにしその値を触るのをpbilicのメソッド経由にするとカプセル化💊となります。


カプセル化しない

public class Cat {

let name: String = "たま"
let like: String = "魚"
}

var mike = Cat().name
mike = "みけ"
print(mike) // →「みけ」


カプセル化しないと、作者の知らないところでnameを書き換えて使われてるなんてことが起きてしまう😱

letにしても意味がありません。


カプセル化する

class Cat {

// privateだから直接プロパティを変更できない
private let name: String = "たま"
private let like: String = "魚"

public func getName() -> String {
return self.name
}
public func getLike() -> String {
return self. like
}
}

let name = Cat().getName()
let like = Cat().getLike()
let data = name + "は猫なので、" + like + "が好きです。"
print(tama) // →「たまは猫なので、魚が好きです。」


こうするとnameに直接アクセスできないのでnameを書き換える手立てがないので意図しない操作ができなくなります。

が、上記の書き方がいいかというとそうではない様ですΣ(・□・;)


⚠️注意点

カプセル化した場合でも、もし↓の様なセット用のメソッドを追加したとすると、

普通に書き換えることができる様になってしまいます。

class Cat {

public func setName(name: String) {
self.name = name
}
}

let mike = Cat()
mike.setName(name: "みけ")
let like = Cat().getLike()
let data = name + "は猫なので、" + like + "が好きです。"
print(mike.getName()) // →「みけ」

そうなるとこちらの記事でも述べられている通り、カプセル化したところで。。となってしまいます。

(セット用メソッド追加した時点でそれを許容してるはずだとは思うのですが)

これは、最初の方でリンクしたこちら文献の11章でも述べられていて、


  • getter、setter、プロパティを使わない


    • メソッドはなんらなの判断計算加工をすべきであり、そのまま値を返すメソッドを書いてはいけない

    • インスタンス変数を書き換えるsetterはバグの原因になるので使わない



とあり、そうすると、setメソッドだけではなく、上で書いたgetName()の様なロジックがなく意味のないgetメソッドもあまりよろしくない様です🙅‍♂️

その場合は、「値オブジェクト」を用いてデータを普遍にするのが良い設計だと述べられています。


ロジックをデータクラスに置く

上記の値オブジェクトを使うべき、と、最初の方の「データを持つクラスにロジックを置くことがオブジェクト指向設計の原則である」についてです。

まずこの思考にもとずいたコードはこんな感じになるかと思います。

struct Cat {

private let name: String
private let like: String

init(name: String,
like: String) {
self.name = name
self.like = like
}

public func getTama() -> String {
return self.name + "は猫なので、" + self.like + "が好きです。"
}
}

let tama = Cat(name: "たま", like: "魚")
print(tama) // →「たまは猫なので、魚が好きです。」

structを使うことで値オブジェクトとなるので、インスタンス化するたびに値がコピーされるので、データそのものを変えるだったり、privateでも値を書き換えるという心配も無くなります\\\٩( 'ω' )و ////


終わりに

オブジェクト指向となると、結構最初に書いた「たい焼きがーコックさんがー」という説明から入るものが多いですが、そこではなく、

「なぜオブジェクト指向の考えが必要なのか」をちゃんと理解することがなんだかんだいって初心者には一番しっくりくるのかなと思いました\\\٩( 'ω' )و ////

(最初にオブジェクト指向を調べて、先輩に意気揚々とカレーとコックさんの話をした自分が今となってはいい思い出。。。(・ω・`)「じゃあそれをどうするの?」と返されて回答に困ったのには自分でもなんだこいつと思いました。)

以上です。