emahiro/b.log

Drastically Repeat Yourself !!!!

goのsortで複数条件でのsortを実現する上で考えたこと

サマリ

特定のModelに対して複数条件でsortしたい場合の実装方法についての考察

実装方法

  • sortしたいfield毎にモデルにsortのinterfaceを実装をする - ➀
  • embedで親のsortパッケージを継承する(sort条件を上書きする) - ➁

➀のとき

sortしたいStructのfield毎にLen,Swap,Lessのinterfaceを実装する。
古典的な実装方法で基本Structのfieldごとにsortで必要となるinterface3つを実装すればよい。

type Person struct{
  Name
  Age
}

type PeopleByName []Person

func (p PeopleByName)Len int{
  return len(p)
}

func (p PeopleByName)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Nameでsortする場合
func (p PeopleByName)Less (i, j int) {
  return p[i].Name < p[j].Name
}

func (p People)SortByName(){
  sort.Sort(PeopleByName(p))
}

type PeopleByAge []Person

func (p PeopleByAge)Len int{
  return len(p)
}

func (p PeopleByAge)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Ageでsortする場合
func (p PeopleByAge)Less (i, j int)  bool {
  return p[i].Age < p[j].Age
}

func (p People) SortByAge (){
  sort.Sort(PeopleByAge(p))
}

func main (){
  p := []Person{
  Person{
    Name: "Taro",
    Age: 1,
  },
  Person{
    Name: "Jiro",
    Age: 2,
  },
  Person{
    Name: "Saburo",
    Age: 3
  }
  
  // 名前でSort
  p.SortByName() // Jiro, Saburo, Taro
  // 年齢でsort
  p.SortByAge() // 1, 2, 3

}

古典的でわかりやすい反面、使わないinterfaceも定義してしまい、コード全体の見通しが悪い。

②の場合

ベースとなるStructにつき一度sortのinterface3つを実装する。
複数条件でsortする場合は、ベースとなるStructを埋め込んだ別のStructを作って、その新しく条件を追加するために作成したStructに必要なinterfaceのみを追加する。

ベースとなるStructを埋め込んだ、新しいStructではsortのinterfaceを全て実装しなくても、大本のStrcutでsortが実装されていれば、全て実装する必要はない。
必要となるinterfaceのみ実装すれば、sortがcallされる時にsort条件が上書きされる。
※ 継承みたいなイメージ

type Person struct{
  Name
  Age
}

type People []Person

func (p People)Len int{
  return len(p)
}

func (p People)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Nameでsortする場合
func (p People)Less (i, j int) bool {
  return p[i].Name < p[j].Name
}

最初にベースとなるPeople StructにNameでsortするための基準となるsortのinterfaceを定義しておきます。
次にAgeでsortするために、Ageでsortする場合はNameでsortするために定義したLessを上書きします。

type PeopleByAge struct{
  People 
}

func (b PeopleByAge) Less (i, j int) bool {
  return b.People[i].Age < b.People[j].Age // ここでPeopleのLessを上書きしている。
}

func main (){
  p := []Person{
  Person{
    Name: "Taro",
    Age: 1,
  },
  Person{
    Name: "Jiro",
    Age: 2,
  },
  Person{
    Name: "Saburo",
    Age: 3
  }
  
  // 名前でSort
  sort.Sort(p)
  // AgeでSort
  sort.Sort(PeopleByAge{p})
}
type PeopleByAge struct{
  People 
}

の箇所でPeople Structを埋め込んでいるので、PeopleByAgeに新しく3つ全てのinterfaceを実装しなくても良くなります。

考察

古典的な方法はわかりやすい、かつ実装しやすいため基本➀で実装するのをベースに置きつつ、埋め込み型の方がよりsort条件を明確にでき、かつコードの記述量もすくないと思うので、使えるならStructに埋め込むパターンの方がいい気がする。

一方でsortについては➁の実装方法はベースのStructに依存することになるので、もしかしたら改修のときなどに予期せぬ影響が出る可能性がある。

refs

https://qiita.com/Jxck_/items/fb829b818aac5b5f54f7