"Enums as configuration: the anti-pattern"から見るStruct + static varの組み合わせの便利さ


はじめに

少し前の話題になりますが、以下のタイトルの海外ブログが投稿されていました。

記事の中では、Enumを使って表現されがちな実装を例に上げつつ、代替としてStructの利用を推奨しています。私自身、これを読んでからEnumとStructのどちらが最適な選択であるのか都度意識して考えるようになり、Struct + static varの組み合わせの使い勝手の良さを実感するようになりました。そこで、その内容に関してまとめてみたいと思います。


SwiftのEnumは便利

Swiftについて勉強し始めると、きっとEnumの魅力について色んな人から説明を受けることでしょう。


  • caseを書くとき型を省略表記できる

  • パターンマッチングによる網羅性チェック

  • 計算プロパティやメソッドを持つことができる

などなど、Objective-Cを使っていた頃に比べて色々便利な拡張がされているためについつい色んな所で使ってしまいがちです。その結果、わざわざEnumにする必要あるのかこれ?ってところで使ったり、Enumにすることでむしろ使い勝手が悪くしてしまった...ってなったことはないでしょうか(自分はあります...)。


設定周りでEnumを使うのはアンチパターン

その一つとして、先程の記事では"設定周りでEnumを使ってしまうアンチパターン"について触れています。

例えば似たような例になりますが、Enumを使ってCellのスタイルを定義したとします。

enum CellStyle {

case `default`
case subtytle
case image
}

class TableViewCell: UITableViewCell {
// ...
// ...
var style: CellStyle {
didSet {
switch style {
case `default`:
largeLabel.hidden = false
smallLabel.hidden = true
imageView.hidden = true
case subtytle:
largeLabel.hidden = true
smallLabel.hidden = true
imageView.hidden = false
case image:
largeLabel.hidden = false
smallLabel.hidden = true
imageView.hidden = false
}
}
}
// ...
// ...
}

このようにすると、設定が抽象化されていてわかりやすくなっている反面、拡張性に乏しいというデメリットがあります。

特にモジュールを分けてライブラリ化する時などには注意が必要で、利用者側からcaseの追加をする手段がなくなってしまいます。

そこでより良い手段として、Struct + static varの利用が推奨されていました。


SwiftのStructは便利

この話を実務の例で考えてみます。

私の職場では、一から何かを実装するよりも既に作られたものへ手を加えることの方が多くあります。

また、一人で開発しているわけではなく数人の開発メンバーと一緒になって同じコードをいじっています。

ある日、共通で使われている”CommonLoadingView"というエラー画面のスタイルを、特定のViewController用に変えてほしいという依頼をうけました。

通常は白背景に黒テキスト・黒インジケータになっているのですが、あるViewControllerへ遷移するときだけ黒背景で白テキスト・白インジケータにしてほしいとのことです。

これが一からViewを作る場合であるならいいのですが、既にあってなおかつ色んな所から別の案件でも使われているようなViewであるため、その場しのぎ的な対応はしたくありませんでした。

なおかつ他のデザイナが別案件で細かいスタイルの調整を求めてきたりした時でも簡単に拡張できるようにしたいと思いました。(デザインポリシーを事前に決めておけばいいのではという最もな意見もあるかと思いますが、デザイナが変わったり全員がポリシー決めに協力的であるとは限らないなど実務においてはそうもいかない色んな事情があったりします...)

そこで先程の記事を活用して実装することにしました。

まず、Styleという構造体と外から設定可能なスタイルを定義します。

// 設定できる項目を定義した構造体

struct CommonLoadingViewStyle {
let textColor: UIColor
let backgroundColor: UIColor
let indicatorStyle: UIActivityIndicatorViewStyle
}

// 設定を外から注入するView
class CommonLoadingView: UIView {
// ...
// ...
var style: CommonLoadingViewStyle? {
didSet {
aLabel.textColor = style.textColor
aIndicator.activityIndicatorViewStyle = style.indicatorStyle
backgroundColor = style.backgroundColor
}
}
// ...
// ...
}

そして、サービス内で決められたスタイルの仕様をstatic var で定義していきます。

struct CommonLoadingViewStyle {

let textColor: UIColor
let backgroundColor: UIColor
let indicatorStyle: UIActivityIndicatorViewStyle

/// ライトモード
static var light: CommonLoadingViewStyle {
return CommonLoadingViewStyle(
textColor: .black,
backgroundColor: .white,
indicatorStyle: .gray
)
}

/// ダークモード
static var dark: CommoLoadingViewStyle {
return CommonLoadingViewStyle(
textColor: .white,
backgroundColor: .black,
indicatorStyle: .white
)
}
}

使う時はこんな感じにします。

// テンプレートから選択

view.style = .dark
// 自分で細かく調整をする
view.style = CommonLoadingViewStyle(textColor: .white, backgroundColor: .pink, indicatorStyle: .white)

StructもEnumと同様に型の省略が可能になっているため、同じようなスッキリとした記述が可能になっています。こうして値に関して拡張性を持っている一方で、設定できる項目を制限できました。


まとめ

やってる事自体は割と普通な感じですが、これをパターンとして認識しておくことで日々の業務の中でのコード設計が楽になりました。以前では色んな場面でEnumを使っていましたが、最近では以前書いたようなSwitch文のパターンマッチングが強力に作用するケースぐらいになってきている気がします。

Swift その2 Advent Calendar 2016の初日はこんな感じで。


参考資料