SOLID原則について
SOLID原則とは
- SRP(単一責任の原則)
- OCP(オープン・クローズドの原則)
- LSP(リスコフの置換原則)
- ISP(インターフェース分離の原則)
- DIP(依存関係逆転の原則)
の頭文字を取ったものである。
なぜSOLID原則?
SOLIDを適用する理由として、
- 変更に強くなる
- 理解しやすくなる
- 多くのソフトウェアシステムで利用できること
が挙げられます。
以下から、各原則の説明、コードについて触れていきます。
SOLIDの各原則について
SRP(単一責任の原則)
###説明
1つのモジュールやクラス、関数などを変更する際に、理由が複数あってはいけないという原則
理由としては以下のような事象が起きることを防ぐため
- 複数の人が一つのソースファイルを変更した際に、マージやコンフリクトが発生
- 変更した内容が予期しないところに影響を受ける
コード
悪い例
一つのインタフェースで従業員の管理、従業員のデータの保存と複数の責務を負っている。
type EmployeeWorkManage interface {
calculatePay()
reportHours()
saveEmployee()
}
####改善後
従業員の管理、従業員のデータの保存のインターフェースを分けて作成して、責務をそれぞれ1つになるようにする。
type EmployeeWorkManage interface {
calculatePay()
reportHours()
}
type EmployeeDataManage interface {
saveEmployee()
}
OCP(オープン・クローズドの原則)
###説明
ソフトウェアの拡張に対して開いていて、修正に対して閉じてなければならないという原則
そうすることで変更の影響を受けずにシステムを拡張できる。
コード
Animalというインターフェイスを作成して、それに依存した形で、Dog型やCat型のメソッドを作成することによって、mainの処理ではDog型やCat型を気にせずに、barkメソッドを実行できる。
また新しくBirdを作りたい場合でも、Animalのインターフェースに依存した作りにすることによって追加だけで済む。
package main
import(
"fmt"
)
type Animal interface {
bark()
eat()
}
type Dog struct {}
type Cat struct {}
func (d Dog) bark(){
fmt.Println("ワンワン")
}
func (d Dog) eat(){
fmt.Println("バクバク")
}
func (c Cat) bark(){
fmt.Println("にゃー")
}
func (c Cat) eat(){
fmt.Println("ムシャムシャ")
}
func main(){
dog := Dog{}
cat := Cat{}
animals := []Animal{dog, cat}
for _, animal := range animals {
animal.bark()
}
}
LSP(リスコフの置換原則)
###説明
S型のオブジェクト :o1
T型のオブジェクト: o2
がある時に、Tを使って定義されたプログラムPに対して、o2の代わりに、o1を使ってもPの振る舞いが変わらない場合、SはTの派生型であるという原則
コード
Goには継承がないため、リスコフの置換原則に違反することがなく意識されない(?)
ISP(インターフェース分離の原則)
###説明
不要なインターフェースに依存することを強制してはいけないという原則
関連のあるインターフェースだけをグループ化、利用しないメソッドの依存を失くす。
コード
悪い例
以下のようなAnimalInterfaceから人間型を作ろうとすると、使用しないflyメソッドまで記述をしないといけなくなる。
type AnimalInterface interface {
fly()
run()
swim()
}
####改善後
鳥だったらBirdInterface、人間だったらHumanInterfaceとインターフェースを分けるようにする。
そうすることによって不要なメソッドを記述せずに済む。
type BirdInterface interface {
fly()
}
type HumanInterface interface {
run()
swim()
}
type Bird struct {}
type Human struct{}
func (b Bird) fly(){
fmt.Println("飛びます!!")
}
func (h Human) run(){
fmt.Println("走るぞ〜")
}
func (h Human) swim(){
fmt.Println("泳ぎます〜")
}
func main(){
bird := Bird{}
human := Human{}
bird.fly()
human.run()
}
DIP(依存関係逆転の原則)
###説明
頻繁に変更されている具象モジュール(関数の実装が書かれているモジュール)に依存してはいけない原則
変更しやすい具象への依存を避け、安定した抽象インターフェースに依存する。
Abstract Factoryパターンを利用して、抽象インターフェイスを参照するようにする。
コード
Abstract Factoryに依存することによって、依存先が図のようなイメージになり、DBかテキストどちらかを気にせずに、ユーザのデータを取得できるようになる。
package main
import(
"fmt"
)
type User struct {
id int
name string
}
type AbstractFactory interface {
getData() User
}
type DbManage struct {}
func (db DbManage) getData() User {
return User{1, "DB TARO"}
}
type TextManage struct {}
func (text TextManage) getData() User {
return User{2, "TEXT JIRO"}
}
func getUserData(manageType string) User {
var manageFactry AbstractFactory
switch manageType {
case "DB":
manageFactry = DbManage{}
return manageFactry.getData()
case "TEXT":
manageFactry = TextManage{}
return manageFactry.getData()
default:
return User{3, "名無し"}
}
}
func main(){
user := getUserData("DB")
fmt.Println(user.id, user.name)
}
参考
https://labs.septeni.co.jp/entry/2017/02/21/164127
https://maroyaka.hateblo.jp/entry/2017/05/22/165355
https://qiita.com/shunp/items/646c86bb3cc149f7cff9
https://golangvedu.wordpress.com/2017/01/31/golang-design-pattern-abstract-factory-and-factory-method/
最後に
なんとなくですが、SOLID原則について分かったような気がします。
質問やご指摘がございましたら、コメントに書いていただけると幸いです。