はじめに
アプリを作っているときに複数画面で同じような表示をみかけると共通のカスタム View として切り出すことがよくあります。
しかし、安易に共通化すると後々の仕様変更時に痛い目にあう可能性があります。痛い目にあわないように共通のカスタム View はどのように作るべきなのか考えたいと思います。
(明確な答えがあるわけじゃないのでポエムのようなものです)
事象
添付のような画面があった場合、下部の金額表示部分は同じなので共通化したくなるはずです。
画面A | 画面B |
---|---|
![]() |
![]() |
こんな感じです。

// LoadXIBViewはxibからViewを生成するためのクラスです
final class HogePriceView: LoadXIBView {
struct Config {
let fugaPrice: String
let piyoPrice: String
let totalPrice: String
}
@IBOutlet private weak var fugaPriceLabel: UILabel!
@IBOutlet private weak var piyoPriceLabel: UILabel!
@IBOutlet private weak var totalPriceLabel: UILabel!
func configure(_ config: Config) {
fugaPriceLabel.text = config.fugaPrice
piyoPriceLabel.text = config.piyoPrice
totalPriceLabel.text = config.totalPrice
}
}
いい感じです(DRY!DRY!)
仕様変更1
開発が進んでいくと HogePriceView
は色々な画面で使用されるようになりました。ある日、「画面 F では項目 Foo も表示してほしい」という仕様変更がありました。
下記のように HogePriceView
を修正します

final class HogePriceView: LoadXIBView {
struct Config {
let fugaPrice: String
let piyoPrice: String
let totalPrice: String
let fooPrice: String?
let isFooShown: Bool
init(fugaPrice: String,
piyoPrice: String,
totalPrice: String,
fooPrice: String? = nil,
isFooShown: Bool = false) {
self.fugaPrice = fugaPrice
self.piyoPrice = piyoPrice
self.totalPrice = totalPrice
self.fooPrice = fooPrice
self.isFooShown = isFooShown
}
}
@IBOutlet private weak var fugaPriceLabel: UILabel!
@IBOutlet private weak var piyoPriceLabel: UILabel!
@IBOutlet private weak var totalPriceLabel: UILabel!
@IBOutlet private weak var fooPriceLabel: UILabel!
@IBOutlet private weak var fooContainer: UIView!
func configure(_ config: Config) {
fugaPriceLabel.text = config.fugaPrice
piyoPriceLabel.text = config.piyoPrice
totalPriceLabel.text = config.totalPrice
fooPriceLabel.text = config.fooPrice
fooContainer.isHidden = !config.isFooShown
}
}
画面 F でのみ下記のように呼び出せば画面 F でのみ項目 Foo が表示されます。
hogeView.configure(.init(
fugaPrice: "100円", piyoPrice: "250円",
totalPrice: "750円", fooPrice: "400円",
isFooShown: true
))
完璧です
仕様変更2
その後も仕様変更は続き、「画面 G では赤文字で警告文を表示してほしい」「画面 F では合計に注釈を表示してほしい」など様々な修正が入ります。
完成した HogePriceView
がこちらです。

final class HogePriceView: LoadXIBView {
struct Config {
let fugaPrice: String
let piyoPrice: String
let totalPrice: String
let fooPrice: String?
let isFooShown: Bool
let warning: String?
let isWarningShown: Bool
let isAnnotationShown: Bool
init(fugaPrice: String,
piyoPrice: String,
totalPrice: String,
fooPrice: String? = nil,
isFooShown: Bool = false,
warning: String? = nil,
isWarningShown: Bool = false,
isAnnotationShown: Bool = false) {
self.fugaPrice = fugaPrice
self.piyoPrice = piyoPrice
self.totalPrice = totalPrice
self.fooPrice = fooPrice
self.isFooShown = isFooShown
self.warning = warning
self.isWarningShown = isWarningShown
self.isAnnotationShown = isAnnotationShown
}
}
@IBOutlet private weak var fugaPriceLabel: UILabel!
@IBOutlet private weak var piyoPriceLabel: UILabel!
@IBOutlet private weak var totalPriceLabel: UILabel!
@IBOutlet private weak var fooPriceLabel: UILabel!
@IBOutlet private weak var fooContainer: UIView!
@IBOutlet private weak var warningLabel: UILabel!
@IBOutlet private weak var warningContainer: UIView!
@IBOutlet private weak var annotationLabel: UILabel!
func configure(_ config: Config) {
fugaPriceLabel.text = config.fugaPrice
piyoPriceLabel.text = config.piyoPrice
totalPriceLabel.text = config.totalPrice
fooPriceLabel.text = config.fooPrice
fooContainer.isHidden = !config.isFooShown
warningLabel.text = config.warning
warningContainer.isHidden = !config.isWarningShown
}
}
HogePriceView
はあらゆる画面で使用されているのでもう迂闊にはさわれない View になってしまいました。。。
どうすべきだったのか
共通化する場合、「変更頻度・変更理由が同じ」ものを共通化すべきです。ただ処理が似ているからという理由で安易に共通化してはいけません。
仕様変更1のときに画面 F でのみ必要な機能を HogePriceView
に追加しています。HogePriceView
に追加してしまうと今後も画面 F だけの修正のために共通 View である HogePriceView
を修正する必要がでてきます。思い切って画面 F 専用の View を作るのが無難かなと思います。
まとめ
共通化するときは下記2つの原則を意識するといいはず(たぶん対象が違うだけでどっちも同じようなこと)。
- 閉鎖性共通の原則(CCP)
同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること。変更の理由やタイミングが異なるクラスは、別のコンポーネントにわけること。
Clean Architecture 達人に学ぶソフトウェアの構造と設計 p.119
- 単一責任の原則(SRP)
アクターの異なるコードは分割するべき
Clean Architecture 達人に学ぶソフトウェアの構造と設計 p.83
最初から分離できていればいいのですがなかなか最初に判断するのは難しいです。異なるアクターというのがわりと判断が難しいです。最初から分離するのは困難なのでわからなければとりあえず共通化しといて仕様変更や機能追加のたびに共通化すべきか分離すべきかを考えるべきです。
おわりに
実装の初期段階から共通化すべきか分離すべきか判断するのは難しいのでおや?と思ったときに改めて考えるのがいいんじゃないかなというふわっとした結論です。
何回か修正しているとこの画面だけ他と違うんじゃないか?と思うことがあります。最初はとりあえず共通化しといて疑問に思ったときに分離するのもありじゃないかなと思います。
経験を積めば最初からいい感じに分離できるのかも