SOLID原則をGO言語で理解する-LSP
デザインパターンの理解を深めようという話。第二弾。LSP、リスコフ置換の原則についてまとめます。
LSPとは
LSP(Liskov Substitution Principle)。リスコフの置換原則。
S 型のオブジェクト o1 の各々に、対応する T 型のオブジェクト o2 が 1 つ存在し、T を使って定義されたプログラム P に対して o2の代わりに o1 を使っても P の振る舞いが変わらない場合、S は T の派生型である。
Barbara Liskov
Subtypes must be substitutable for their base types.
派生型は基本型と代替可能でなければならない。
Robert C. Martin
継承したクラスは継承元のクラスと同じ動作をする必要があるということ。
実践
serviceBicycleName: stringSpeed: float64Ride(): stringTransport(dist: string): stringCarName: stringSpeed: float64Ride(): stringTransport(dist: string): stringRocketName: stringSpeed: float64Ride(): stringTransport(dist: string): stringVehicleInterfaceRide(): stringTransport(: string): string
Bicycle, Car, Rocketという構造体がVehicleInterfaceを実装している図。
VehicleInterfaceが基本型で、その他の構造体はVehicleInterfaceの派生型。
ディレクトリ構造
.
├── go.mod
├── main.go
└── service
├── bicycle.go
├── car.go
├── interface.go
└── rocket.go
interface.go
package service
type VehicleInterface interface {
Ride() string
Transport(string) string
}
car.go
VehicleInterfaceを実装(VehicleInterfaceから派生した構造体とメソッド)。 CarImpl VehicleInterface = &Car{}
という箇所でインターフェースを実装しています。
package service
import "fmt"
type Car struct {
Name string
Speed float64
}
var CarImpl VehicleInterface = &Car{}
func (c *Car) Ride() string{
return fmt.Sprintf("Ride in %!s(MISSING).", c.Name)
}
func (c *Car) Transport(dist string) string {
return fmt.Sprintf("Go to %!s(MISSING) at %!f(MISSING)[km/h]", dist, c.Speed)
}
bicycle.go
car.goと同様、VehicleInterfaceの派生型。
package service
import "fmt"
type Bicycle struct {
Name string
Speed float64
}
var BicycleImpl VehicleInterface = &Bicycle{}
func (b *Bicycle) Ride() string{
return fmt.Sprintf("Ride in %!s(MISSING).", b.Name)
}
func (b *Bicycle) Transport(dist string) string {
return fmt.Sprintf("Go to %!s(MISSING) at %!f(MISSING)[km/h]", dist, b.Speed)
}
rocket.go
car.go、bicycle.goと同じくVehicleInterfaceの派生型。しかしTransport()メソッドの中身が少し違う。
package service
import "fmt"
type Rocket struct {
Name string
Speed float64
}
var RocketImpl VehicleInterface = &Rocket{}
func (s *Rocket) Ride() string{
return fmt.Sprintf("Ride in %!s(MISSING).", s.Name)
}
func (s *Rocket) Transport(dist string) string {
var text string
if dist == "space" {
text = fmt.Sprintf("Go into the space at %!f(MISSING)[km/s].", s.Speed)
} else {
text = "This rocket can only go into the space."
}
return text
}
main.go
package main
import (
"fmt"
"main.go/service"
)
var (
car = service.CarImpl
bicycle = service.BicycleImpl
rocket = service.RocketImpl
)
func transport(v service.VehicleInterface, d string) string {
return fmt.Sprintf("%!s(MISSING) %!s(MISSING)", v.Ride(), v.Transport(d))
}
func main() {
car = &service.Car{
Name: "TOYOTA Land Cruiser",
Speed: 60,
}
bicycle = &service.Bicycle{
Name: "Jamis Ventura Comp",
Speed: 10,
}
rocket = &service.Rocket{
Name: "SpaceX Falcon 9",
Speed: 7.9,
}
fmt.Println(transport(car, "Tokyo"))
fmt.Println(transport(bicycle, "nearby park"))
fmt.Println(transport(rocket, "space"))
}
まとめ
LSP完全に理解した。
どこにinterfaceを実装するコードを置くのか迷う。今回は派生型(car.go, bicycle.go, rocket.go)に置いたけど、interface.goにまとめて置いても良いかもしれない。ここら辺はパッケージの分け方とかで最適解は変わりそうです。
↓コード置き場。
https://github.com/hodanov/solid-principles/tree/master/liskov_substitution