[ja] Translate 02.5.md into Japanese.
This commit is contained in:
325
ja/ebook/02.5.md
Normal file
325
ja/ebook/02.5.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# 2.5 オブジェクト指向
|
||||
前の2つの章で関数とstructをご紹介しました。関数をstructのフィールドとして処理したくなったんじゃないですか?今日は関数のもう一つの形態についてご説明します。受け取り手のいる関数で、我々が`method`とよんでいるものです。
|
||||
|
||||
## method
|
||||
今、このような状況にいると仮定します。あなたは長方形というstructを定義してこの面積を求めようとしています。では、我々の一般的な思考回路に基づけば下のような方法で実現するでしょう。
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
func area(r Rectangle) float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
fmt.Println("Area of r1 is: ", area(r1))
|
||||
fmt.Println("Area of r2 is: ", area(r2))
|
||||
}
|
||||
|
||||
このコードは長方形の面積を求めることができますが、area()はRectangleのメソッドで実現されたものではありません(オブジェクト指向のメソッドのようなもの)。Rectangleのオブジェクト(ここではr1,r2)を引数に面積を京s何する関数に渡しているだけです。
|
||||
|
||||
このように実現してももちろん構わないのですが、図形が増えてきて、正方形、五角形ついには多角形になってきた頃、これらの面積も求めようとするとどうでしょう?この場合関数を増やすしかなくなってしまいます。関数名はそれぞれ用意しなければなりません。`area_rectangle, area_circle, area_triangle...`といった具合に。
|
||||
|
||||
下の図で示すように、楕円が関数を代表します。これらの関数はstructに属していない(またはオブジェクト指向の専門用語でclassに属していないといいます)ので、structの外側に単独で存在しており、概念上どのstructにも属していないことになります。
|
||||
|
||||

|
||||
|
||||
図2.8 メソッドとstructの関係図
|
||||
|
||||
明らかにこのような実現方法はエレガントではありません。それに概念からしても"面積"は"形状"の一属性です。これはある特定の形状に属しています。長方形の縦と横と同じようなものです。
|
||||
|
||||
このような理由から`method`の概念が生まれました。`method`はある型の上に付属しています。この文法と関数の宣言の文法はほとんど同じです。ただ、`fenc`の後にreceiver(methodがくっついているということです。)を追加します。
|
||||
|
||||
上で述べた形状の例からすると、method `area()` はある形状(たとえばRectangle)に由来して発生しています。Rectangle.area()の主語はRectangle、area()はRectangleに属するメソッドで外側の関数ではありません。
|
||||
|
||||
より具体的に述べると、Rectangleにはフィールドlengthとwidthが存在します。同時にarea()メソッドが存在します。これらのフィールドとメソッドは共にRectangleに属しています。
|
||||
|
||||
Rob Pikeの言葉を借りると:
|
||||
|
||||
>"A method is a function with an implicit first argument, called a receiver."
|
||||
|
||||
methodの文法は以下のとおりです:
|
||||
|
||||
func (r ReceiverType) funcName(parameters) (results)
|
||||
|
||||
はじめの例をとってmethodを実現してみます:
|
||||
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
radius float64
|
||||
}
|
||||
|
||||
func (r Rectangle) area() float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func (c Circle) area() float64 {
|
||||
return c.radius * c.radius * math.Pi
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
c1 := Circle{10}
|
||||
c2 := Circle{25}
|
||||
|
||||
fmt.Println("Area of r1 is: ", r1.area())
|
||||
fmt.Println("Area of r2 is: ", r2.area())
|
||||
fmt.Println("Area of c1 is: ", c1.area())
|
||||
fmt.Println("Area of c2 is: ", c2.area())
|
||||
}
|
||||
|
||||
|
||||
|
||||
methodを使う時にはいくつか注意が必要です。
|
||||
|
||||
- methodは名前はまったく同じといってもレシーバが異なればmethodも異なります。
|
||||
- methodはレシーバのフィールドにアクセスすることができます。
|
||||
- methodのコールは`.`を通じてアクセスします。structがフィールドにアクセスするのと同じです。
|
||||
|
||||
図解:
|
||||
|
||||

|
||||
|
||||
図2.9 異なるstructのmethodは異なる。
|
||||
|
||||
上の例では method area() はそれぞれRectangleとCircleに属します。この時これらの Receiver は Rectangle と Circleになります。またはこのarea()メソッドはRectangle/Circleを主語とします。
|
||||
|
||||
>特に、図中のmethodは破線で表示しています。これは、このメソッドのレシーバは値渡しであり、参照渡しではありません。そうです。レシーバはポインタでもいいのです。両者の違いはポインタはレシーバがエンティティの内容に操作を行うことがあるのに対し、普通の型ではレシーバは操作するオブジェクトのコピーでしかありません。オリジナルのエンティティに対して操作が発生しないのです。詳細は後述します。
|
||||
|
||||
methodはstructの上でしか使用されないのでしょうか?当然違います。これはカスタム定義型、ビルトイン型、structなどあらゆる型でも定義することができます。ちょっとよくわからなくなってきましたか?何がカスタム定義型だ、カスタム定義型はstructじゃないのか。そういうわけではありません。structはカスタム定義型のなかでも比較的特殊な型であるだけです。下のような宣言で実現します。
|
||||
|
||||
type typeName typeLiteral
|
||||
|
||||
以下のカスタム定義型の宣言のコードをご覧ください。
|
||||
|
||||
type ages int
|
||||
|
||||
type money float32
|
||||
|
||||
type months map[string]int
|
||||
|
||||
m := months {
|
||||
"January":31,
|
||||
"February":28,
|
||||
...
|
||||
"December":31,
|
||||
}
|
||||
|
||||
わかりましたか?簡単でしょう?このように自分のコードの中に意味のある型を定義することができるのです。実際はエイリアスを定義しているだけです。Cのtypedefににたようなもので、例えば上のagesはintの代わりになっています。
|
||||
|
||||
それじゃあ、`method`にもどりましょう。
|
||||
|
||||
カスタム定義型の中で任意の`method`を定義することができます。次にちょっと複雑な例を見てみましょう。
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
const(
|
||||
WHITE = iota
|
||||
BLACK
|
||||
BLUE
|
||||
RED
|
||||
YELLOW
|
||||
)
|
||||
|
||||
type Color byte
|
||||
|
||||
type Box struct {
|
||||
width, height, depth float64
|
||||
color Color
|
||||
}
|
||||
|
||||
type BoxList []Box //a slice of boxes
|
||||
|
||||
func (b Box) Volume() float64 {
|
||||
return b.width * b.height * b.depth
|
||||
}
|
||||
|
||||
func (b *Box) SetColor(c Color) {
|
||||
b.color = c
|
||||
}
|
||||
|
||||
func (bl BoxList) BiggestsColor() Color {
|
||||
v := 0.00
|
||||
k := Color(WHITE)
|
||||
for _, b := range bl {
|
||||
if b.Volume() > v {
|
||||
v = b.Volume()
|
||||
k = b.color
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (bl BoxList) PaintItBlack() {
|
||||
for i, _ := range bl {
|
||||
bl[i].SetColor(BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) String() string {
|
||||
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
|
||||
return strings[c]
|
||||
}
|
||||
|
||||
func main() {
|
||||
boxes := BoxList {
|
||||
Box{4, 4, 4, RED},
|
||||
Box{10, 10, 1, YELLOW},
|
||||
Box{1, 1, 20, BLACK},
|
||||
Box{10, 10, 1, BLUE},
|
||||
Box{10, 30, 1, WHITE},
|
||||
Box{20, 20, 20, YELLOW},
|
||||
}
|
||||
|
||||
fmt.Printf("We have %d boxes in our set\n", len(boxes))
|
||||
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
|
||||
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
|
||||
fmt.Println("Let's paint them all black")
|
||||
boxes.PaintItBlack()
|
||||
fmt.Println("The color of the second one is", boxes[1].color.String())
|
||||
|
||||
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
|
||||
}
|
||||
|
||||
上のコードはconstでいくつかの定数を定義しています。その後カスタム定義型を定義しています。
|
||||
|
||||
- Colorはbyteのエイリアスです。
|
||||
- struct:Boxを定義します。3つの縦横高さのフィールドと色プロパティを持っています。
|
||||
- slice:BoxListを定義します。Boxを持っています。
|
||||
|
||||
次に上のカスタム定義型をレシーバとしてmethodを定義します。
|
||||
|
||||
- Volume()はレシーバをBoxとして定義します。Boxの堆積を返します。
|
||||
- SetColor(c Color)はBoxの色をcに変更します。
|
||||
- BiggestsColor()はBoxListに定義されており、listの中の体積が最大の色を返します。
|
||||
- PointItBlack()はBoxListのすべてのBoxの色を全部黒に変更します。
|
||||
- String()はColorに定義されており、Colorno具体的な色を返します(文字列形式)
|
||||
|
||||
上のコードは文字で書くと非常に簡単に思えませんか?私達は問題を解決する場合問題の描写を通して、対応するコードを書くことで実現します。
|
||||
|
||||
### ポインタとしてのreceiver
|
||||
ではここで、SetColorのメソッドを見なおしてみましょう。このreceiverはBoxのポインタをさしています。そうです。*Boxを使えるのです。どうしてBox本体ではなくポインタを使うのでしょうか?
|
||||
|
||||
SetColorを定義した本当の目的はこのBoxの色を変更することです。もしBoxのポインタを渡さなければ、SetCororが受け取るのは実はBoxのコピーになってしまいます。つまり、メソッド内で色の変更を行うと、Boxのコピーを操作しているだけで、本当のBoxではないのです。そのため、ポインタを渡す必要があります。
|
||||
|
||||
ここではreceiverをメソッドの第一引数にました。こうすれば前の関数で説明した値渡しと参照渡しも難しくなくなるでしょう。
|
||||
|
||||
もしかしたらSetColor関数の中で以下のように定義すべきじゃないかと思われたかもしれません。`*b.Color=c`、ところが`b.Color=c`でよいのです。ポインタに対応する値を読み込むことが必要ですから。
|
||||
|
||||
そのとおりです。Goの中ではこの2つの方法はどちらも正しいのです。ポインタを使って対応するフィールドにアクセスした場合(ポインタになんのフィールドがなかったとしても)、Goはあなたがポインタを通してその値を必要としていることを知っています。どうです。Goのデザインに魅了されてきたんじゃないですか?
|
||||
|
||||
注意深い読者はこのように思うかもしれません。PointItBlackの中でSetColorをコールした時、ひょっとして`(&bl[i]).SetColor(BLACK)`と書かなければならないんじゃないかと。SetColorのreceiverは*Boxであり、Boxではありませんから。
|
||||
|
||||
ええ、その通りなんです。この2つの方法はどちらでもかまいません。Goはreceiverがポインタであることを知っています。こいつは自動的に解釈してくれるのです。
|
||||
|
||||
つまり:
|
||||
>もしメソッドのreceiverが*Tであれば、T型のエンティティの変数V上でこのメソッドをコールすることができます。&Vによってメソッドをコールする必要はありません。
|
||||
|
||||
同じように
|
||||
>もしメソッドのreceiverがTであれば、*T型の変数P上でこのメソッドをコールすることができます。*Pを使ってメソッドをコールする必要はありません。
|
||||
|
||||
ですので、コールしているポインタのメソッドがポインタのメソッドであるかどうかは気にする必要がありません。Goはあなたが行おうとしているすべてのことを知っているのです。C/C++でプログラムを経験されてこられた方にとっては、とてもとても大きな苦痛が解決されることでしょう。
|
||||
|
||||
### method継承
|
||||
前の章でフィールドの継承を学びました。するとあなたはGoの不思議なところに気がついたかもしれません。methodも継承できるのです。もし匿名フィールドが一つのメソッドを実現している場合、この匿名フィールドを含むsturctもこのメソッドをコールすることができるのです。例をお見せします。
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名フィールド
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名フィールド
|
||||
company string
|
||||
}
|
||||
|
||||
//human上でメソッドを定義
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
|
||||
### methodの書き直し
|
||||
上の例で、もしEmployeeにSayHiを実現したい場合はどうすればよいでしょうか?簡単です。匿名フィールドの衝突と同じ道理で、Employee上でもメソッドを定義することができます。匿名フィールドを書き直す方法は下の例をご確認ください。
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名フィールド
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名フィールド
|
||||
company string
|
||||
}
|
||||
|
||||
//Humanでmethodを定義
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
//EmployeeのmethodでHumanのmethodを書き直す。
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
|
||||
上のコードのデザインはこのように絶妙です。Goのデザインに驚くことでしょう。
|
||||
|
||||
このように、基本的なオブジェクト指向のプログラムを設計することができます。ですが、Goのオブジェクト指向はこのように簡単です。プライベートやパブリックといったキーワードは出てきません。大文字と小文字によって実現しているのです(大文字で始まるものはパブリック、小文字で始まるものはプライベートです)、メソッドにも同じルールが適用されます。
|
||||
## links
|
||||
* [目次](<preface.md>)
|
||||
* 前へ: [struct型](<02.4.md>)
|
||||
* 次へ: [interface](<02.6.md>)
|
||||
Reference in New Issue
Block a user