defined type(メソッドのオーバーライド, 新しいメソッドの追加, キャストの必要有無)

goでは、特定の型を元にして、新たに別の型を定義することができます。

例えば、int型を元に「新たに別の型」を定義すると、「新たな型」で独自のメソッドを追加することができます。
(ただのint型が、独自の処理をもった新たな型になります。)

type宣言で新しい型を定義

例として、「Original型」 と 「Original型 をもとにした Custom型」 を定義します。

Custom型を別途定義することで、以下のようなことができます。

  • 同名メソッドの処理を変更したい
  • 独自の新しい処理を追加したい
package main

import "fmt"

type Original struct {
	X string
	Y string
}

func NewOriginal(x, y string) Original {
	return Original{X: x, Y: y}
}

// Func1 .
func (o *Original) Func1() {
	fmt.Printf("Original Func1\n")
}

// Func2 .
func (o *Original) Func2() {
	fmt.Printf("Original Func2\n")
}

type Custom Original

func NewCustom(x, y string) Custom {
	return Custom{X: x, Y: y}
}

// Func1 同名メソッドを定義することでオーバーライド
func (c *Custom) Func1() {
	fmt.Printf("Custom Func1\n")
}

// Func3 新しいメソッドを追加
func (c *Custom) Func3() {
	fmt.Printf("Custom Func3\n")
}

func main() {
	fmt.Println("Original ----------------")
	o1 := NewOriginal("hello", "world")
	o1.Func1()
	o1.Func2()

	fmt.Println("Custom ----------------")
	c1 := NewCustom("hello", "world")
	c1.Func1() // オーバーライドしたメソッドを実行
	// Func2はCustom型で定義してないため実行できません。
	c1.Func3() // 追加したメソッドを実行

	// キャスト
	fmt.Println("Cast ----------------")
	o2 := Original(c1)
	o2.Func1() // Originalのメソッドが実行される
}
Original ----------------
Original Func1
Original Func2
Custom ----------------
Custom Func1
Custom Func3
Cast ----------------
Original Func1

Custom型 のインスタンスは Custom型 で定義したメソッドのみしか実行できません。

キャストで Original型 に変換すると、Original型 で定義したメソッドのみ利用できます。

埋込みフィールドを活用して新しい型を定義

前述の例では、Custom型 のインスタンスは Custom型 で定義したメソッドしか利用できません。

埋込みフィールドを活用すると、Original型 で定義したメソッドも利用できつつ、特定のメソッドだけオーバーライドできます。

Custom型 の定義の違いに注目してください。

package main

import "fmt"

type Original struct {
	X string
	Y string
}

func NewOriginal(x, y string) Original {
	return Original{X: x, Y: y}
}

// Func1 .
func (o *Original) Func1() {
	fmt.Printf("Original Func1\n")
}

// Func2 .
func (o *Original) Func2() {
	fmt.Printf("Original Func2\n")
}

type Custom struct {
	Original
}

func NewCustom(x, y string) Custom {
	return Custom{Original{X: x, Y: y}}
}

// Func1 同名メソッドを定義することでオーバーライド
func (c *Custom) Func1() {
	fmt.Printf("Custom Func1\n")
}

func main() {
	fmt.Println("Original ----------------")
	o1 := NewOriginal("hello", "world")
	o1.Func1()
	o1.Func2()

	fmt.Println("Custom ----------------")
	c1 := NewCustom("hello", "world")
	c1.Func1() // オーバーライドしたメソッドを実行
	c1.Func2()
}
Original ----------------
Original Func1
Original Func2
Custom ----------------
Custom Func1
Original Func2

代入時のCastの必要有無

タイプ定義によって作られた新しい型は、キャストが必要なものと不要なものが存在します。

タイプ定義の元となる型の種類によってキャストの必要有無が変わります。

タイプ定義の元となる型 キャスト必要有無
predeclared
( Goで事前定義された型 )
int, string, bool, float64… 必要
type literal ArrayType
StructType
PointerType
FunctionType
InterfaceType
SliceType
MapType
ChannelType
不要

エイリアス宣言という宣言も存在します。合わせて動作確認します。

エイリアス宣言は、リファクタ用途に利用されるものです。名前が違うだけで、同じ型(よって、新しいメソッドを追加できない)なので、当然キャストは不要です。

package main

import "fmt"

type MyInt1 int   // Type definitions(タイプ定義)
type MyInt2 []int // Type definitions(タイプ定義)
type MyInt3 = int // Alias declarations(エイリアス宣言)

func main() {
	mi1 := MyInt1(100)
	mi2 := MyInt2([]int{100, 200})
	mi3 := MyInt3(100)

	var i1 int
	var i2 []int
	var i3 int

	// Cast必要
	i1 = int(mi1)

	// Cast不要
	i2 = mi2

	// Cast不要
	i3 = mi3

	fmt.Printf("mi1 %v %T\n", mi1, mi1)
	fmt.Printf("mi2 %v %T\n", mi2, mi2)
	fmt.Printf("mi3 %v %T\n", mi3, mi3)
	fmt.Printf("i1 %v %T\n", i1, i1)
	fmt.Printf("i2 %v %T\n", i2, i2)
	fmt.Printf("i3 %v %T\n", i3, i3)
}
mi1 100 main.MyInt1
mi2 [100 200] main.MyInt2
mi3 100 int
i1 100 int
i2 [100 200] []int
i3 100 int

参考