post Image
swiftでAbstract Classを表現しようとして、protocol extensionで実現できないかと検討したが断念した

目的

いくつかのclassが複数の同じ変数を持っていて(例えば、createdAt、updatedAt等)、
また、その変数を利用した同様の(ちょっとだけ違う)メソッドを持っていて、
これらの変数やメソッドを引き上げたいな、というのが目的でした。

目的の理由

  • 同様の処理がコピペに違い状態で、各classに分散していると、変更を複数箇所に適用しなければならないから
  • 変更に弱いというか、記述漏れも発生しそうで怖いから
  • 同じ処理を書くのが面倒だから

環境

  • swift 4.0
  • Xcode9.1
  • Realm 3.0.2

元々の状態(before/after のbefore)

RealmObjectA.swift
import RealmSwift
// realmに格納される propertyA というIntを保存するclass
class RealmObjectA: Object {
    @objc dynamic var propertyA = 0
    @objc dynamic var createdAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var updatedAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var deleteFlg = false

    // propertyAとdeleteFlgを更新すると同時にupdatedAtを現時刻に更新
    func setAttributes(propertyA:Int, deleteFlg:Bool = false){
        self.propertyA = propertyA
        self.deleteFlg = deleteFlg
        self.updatedAt = Int(Date().timeIntervalSince1970)
    }
}
RealmObjectB.swift
import RealmSwift
// realmに格納される propertyB というStringを保存するclass
class RealmObjectB: Object {
    @objc dynamic var propertyB = ""
    @objc dynamic var createdAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var updatedAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var deleteFlg = false

    // propertyBとdeleteFlgを更新すると同時にupdatedAtを現時刻に更新
    func setAttributes(propertyB:String, deleteFlg:Bool = false){
        self.propertyB = propertyB
        self.deleteFlg = deleteFlg
        self.updatedAt = Int(Date().timeIntervalSince1970)
    }
}

このRealmObjectがA〜Jまで10個存在するような状態でした。

これを一度、同様の処理をスーパークラスへ持たせるように変更しました。

クラスの継承を利用した変更(before/after の after その1)

RealmStoredObject.swift
import RealmSwift
// realmに格納されるobjectの親クラス
class RealmStoredObject: Object {
    @objc dynamic var createdAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var updatedAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var deleteFlg = false

    func setAttributes(deleteFlg:Bool = false){
        self.deleteFlg = deleteFlg
        self.updatedAt = Int(Date().timeIntervalSince1970)
    }
}
RealmObjectA.swift
// realmに格納される propertyA というIntを保存するclass
class RealmObjectA: realmStoredObject {
    @objc dynamic var propertyA = 0

    func setAttributes(propertyA: Int, deleteFlg:Bool = false){
        super.setAttributes(deleteFlg: deleteFlg)
        self.propertyA = propertyA
    }
}
RealmObjectB.swift
// realmに格納される propertyB というStringを保存するclass
class RealmObjectB: realmStoredObject {
    @objc dynamic var propertyB = ""

    func setAttributes(propertyB: String, deleteFlg:Bool = false){
        super.setAttributes(deleteFlg: deleteFlg)
        self.propertyB = propertyB
    }
}

realmObjectAとrealmObjectBがさっぱりしました。
ここにはsetAttributesというメソッドしか記述していませんが、
実際にはAPI連携のために各変数をjsonパラメータ化するメソッドなど
いくつものメソッドがあったので、変数が減ると一気にさっぱりしました。
この時はとても気分が良かったです。

変数引き上げの理由の再掲

- 同様の処理がコピペに違い状態で、各classに分散していると、変更を複数箇所に適用しなければならないから
- 変更に弱いというか、記述漏れも発生しそうで怖いから
- 同じ処理を書くのが面倒だから

after その1で解決出来たこと

  • 冗長になる処理を一つのclassのみに集約でき、変更があってもそのclassを修正すればよい
  • 記述漏れも発生しない(たとえば「realmObjectBでだけupdatedAtを更新してない!」が無い)
  • 同じ処理を書く面倒から開放された

after その1の問題点

  1. 親クラスであるrealmStoredObjectをインスタンス化できてしまう
  2. よく分からないけど、swiftっぽくないのでは?と思えた

1. 親クラスであるrealmStoredObjectをインスタンス化できてしまう について

realmStoredObjectはあくまで、realmに格納されるclassの共通部分だけを集約した抽象的なclassであり、インスタンス化させるものではない。本当ならインスタンス化を禁止したく

RealmStoredObject.swift
abstract class RealmStoredObject: Object {
}

のようにしたい所なのですが、swiftにabstract classの概念はなく、抽象クラスは実現できません。

そのため、

let realmStoredObject = RealmStoredObject()

のように宣言するとインスタンス化できてしまい、最悪この本来存在しないobjectをrealmに格納してしまう、なんてことにも成り得るかもしれません。

2. よく分からないけど、swiftっぽくないのでは?と思えた について

そもそも何故swiftにはabstract classが用意されていないのでしょうか。
未熟な自分にはその理由を察することはできませんでしたが、
用意していないにも理由があり、
「swiftの哲学が抽象クラスを作らせたくない」ということなのでしょう。

なので、別の方法を模索してみることにしました。

protocol & protocol extensionでの実現妄想(before/after の after その2)

  • 「classよりできるだけstruct」(今回はrealm objectを継承するため無理ですが)
  • 「継承よりもprotocol」

swiftといえば、そんなイメージがなんとなくあります。
そのためスーパークラスによる抽象化ではなく、
振る舞いを定義できるprotocolと、
その振る舞いにデフォルトの動きを定義できるprotocol extensionで
実現できないかを検討してみることにしました。

変数引き上げの理由の再々掲

- 同様の処理がコピペに違い状態で、各classに分散していると、変更を複数箇所に適用しなければならないから
- 変更に弱いというか、記述漏れも発生しそうで怖いから
- 同じ処理を書くのが面倒だから

それでは、protocolでswiftyに書いてみようと思います。

RealmStoredProtocol.swift
import SwiftyJSON

protocol RealmStoredProtocol {
    var createdAt    :Int  { get set }
    var updatedAt    :Int  { get set }
    var deleteFlg    :Bool { get set }

    func setAttributes(measuredOn: Date, deleteFlg:Bool)
}

extension RealmStoredProtocol {
    mutating func setAttributes(deleteFlg:Bool = false){
        self.deleteFlg = deleteFlg
        self.createdAt = Int(Date().timeIntervalSince1970)
        self.updatedAt = Int(Date().timeIntervalSince1970)
    }
}

protocol と protocol extensionはこんな感じになりそうですね。

ではRealmObjectAはどうなるでしょうか。

RealmObjectA.swift
class RealmObjectA: Object, RealmStoredProtocol {
    @objc dynamic var propertyA = 0
    @objc dynamic var createdAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var updatedAt = Int(Date().timeIntervalSince1970)
    @objc dynamic var deleteFlg = false

    func setAttributes(measuredOn: Date, propertyA :Int, deleteFlg:Bool = false) {
        // ↓extension のデフォルト実装が呼べない…
        setAttributes(measuredOn: measuredOn, deleteFlg: deleteFlg)
        self.propertyA = propertyA
    }


すでに発生した問題点

1. 変数は引き上げられないので、全部定義しなきゃだめ

protocolでデフォルトを定義したコンピューテッドプロパティ(値を保持せず、既に存在する値を引っ張ってくるプロパティ)は
準拠する側(今回のRealmObjectA)で定義する必要はありません。

SomeProtocol.swift
protocol SomeProtocol {
  var defaultNum :Int { get }
}

extension SomeProtocol {
  // SomeProtocolに準拠するclassでは定義しなくてもdefaultNumが使える。
  var defaultNum :Int {
    return 1
  }
}

ただし、今回はrealm objectであることもあり、ストアドプロパティ(値を保持するプロパティ)であるため、この方法は使えません。
とはいえ、protocolに準拠するためには、それらの変数を定義しないとコンパイルエラーになってくれるので、
定義漏れの心配は無いため、とりあえず良しとしましょう。

2. protocol extensionで実装したメソッドはoverrideできない

これが困っていて、今回のsetAttributesや、その他定義しているメソッドでは
共通しない変数によって追加の動作が存在しています。
(正確にはsetAttributesの場合は引数が変わるため、overrideではなくオーバーロードして、protocol extension側のsetAttributesを呼び、処理を追加、みたいなことをしたいのですが…)

今回は素直に継承でよいのでは…?

結局良い方法にたどり着けず、

  • ストアドプロパティの実装を共有したい
  • サブクラス側でデフォルト実装に処理を足したい

自分の中では、この場合は継承の方が適しているのかな、という結論に終わりました。
「いやいや、何を言っとるんやお前は。こうすればええやん」というご指摘があれば、随時大募集中です。

少々歯切れの悪い状態で申し訳ないですが、ひとまず記事はここまでです。
まだまだ師走も始まったばかりですが、
みなさま素敵なクリスマスシーズンをお過ごしください。


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。