- Golangはオブジェクト指向プログラミングをサポートしていますが、従来のオブジェクト指向とは異なり、純粋なオブジェクト指向言語ではありません。Golangはオブジェクト指向プログラミング機能をサポートしているとしか言えません。
- Golangにはクラスがなく、非常に簡潔な構造体構造体を介してOOPを実装します。
構造体の定義
- Structフィールド宣言の構文は変数の構文と同じです。構造体を作成した後、フィールドに値が割り当てられていない場合、フィールドはデフォルト値になります。
- 構造はタイプを再定義でき、Golangはデフォルトで新しいタイプになります
- 構造体はユーザーが個別に定義する型であり、他の型と変換する場合はまったく同じフィールド(名前、数値、型)が必要です。
- 構造体の各フィールドは、リフレクションメカニズムを介して取得できるタグで書き込むことができます
- 構造のすべてのフィールドはメモリ内で連続しています
サンプルコード
//声明一个结构体
type Cat struct{
Name string
Age int
Color string
Hobby string
}
func test01(){
//创建结构体并赋值1
var cat1 Cat
cat1.Name = "小白"
cat1.Age = 10
cat1.Color = "白色"
cat1.Hobby = "吃 鱼"
var cat2 Cat
fmt.Println(cat1,cat2)//{小白 10 白色 吃 鱼} { 0 }
//创建结构体并赋值2
c3 := Cat{
"小黑",20,"黑色","shuijiao"}
//创建结构体并赋值3
var c4 *Cat = new(Cat)
(*c4).Name = "小花" //通过索引访问值 再访问属性
c4.Age = 10 //也可以直接通过索引访问属性(goLang做了优化)
//创建结构体并赋值4
var c5 *Cat = &Cat{
}
c5.Name = "c5"
fmt.Println(cat1,cat2,c3,c4,c5)
c6 := c3
c6.Name ="c6"
//赋值后会直接克隆一个变量给C6,修改c6的值 c3 不受影响
//{小黑 20 黑色 shuijiao} {c6 20 黑色 shuijiao}
fmt.Println(c3,c6)
//创建结构体变量时直接指定字段值
var c7 = Cat{
Name:"小绿",
Age:40,
}
fmt.Println(c7)
}
type Point struct{
x int
y int
}
type Rect struct{
leftUp, rightDown Point
}
type Rect2 struct{
leftUp, rightDown *Point
}
func test02(){
//结构体所有字段在内存中是连续的
r1 := Rect{
Point{
1,2} , Point{
3,4}}
fmt.Printf("r1.rd.x地址= %p r1.rd.y地址= %p r1.lu.x地址= %p r1.lu.y地址= %p \n",
&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
// fmt.Printf("r1.rd.x地址= %p r1.rd.y地址= %p r1.lu.x地址= %p r1.lu.y地址= %p \n",
//&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
r2 := Rect2{
&Point{
1,2} , &Point{
3,4}}
fmt.Printf("r2.rd.x地址= %p r2.rd.y地址= %p r2.lu.x地址= %p r2.lu.y地址= %p \n",
&r2.rightDown.x,&r2.rightDown.y,&r2.leftUp.x,&r2.leftUp.y)
//结构体是用户单独定义的类型,和其他类型转换时需要有完全相同的字段(名字、个数、类型)
type A struct{
Num int
}
type B struct{
Num int
}
var a A
var b B
a = A(b)
a.Num =10
fmt.Println(a,b)
//结构体进行type 重新定义,Golang默认是新类型,
type AA A
var aa AA
//var a A = aa 报错需要强转
var a3 A = A(aa)
fmt.Println(aa,a3)
//struct 每个字段 都可以写上一个tag,该tag 可以通过反射机制获取
//常见的场景就是 序列化和反序列化
type Monster struct {
Name string `json:"name"`
Age int `json:age`
}
m1 := Monster{
"牛魔王",300}
jsonstr ,err := json.marshal(m1)
if err != nil {
fmt.Println("json 字符串处理错误")
}
fmt.Println("json 字符串=",jsonstr)
}
構造体メソッド
structメソッド宣言構文は、A構造体にテストメソッド(a A)があり、このメソッドがAタイプにバインドされていることを示します。
func( a A)test(){
}
func( a A)test2(参数列表)(返回值列表){
方法体
return 返回值
}
注意点
- 構造体変数を介してメソッドを呼び出す場合、呼び出しメカニズムは関数のメカニズムと同じですが、構造体メソッドが呼び出されると、構造体変数もパラメーターとしてメソッドに渡される点が異なります。
- Golangのメソッドはデータ型で指定されているため、すべてのカスタム型にstruct、int、float32などのメソッドを含めることができます。
- 型がString()メソッドを実装している場合、fmt.Printlnは、デフォルトで出力される結果として、変数のこのメソッドを呼び出します。
- 変数を介してメソッドを呼び出す場合、その(パラメーター転送)メカニズムは関数のメカニズムと同じですが、変数自体もパラメーターとしてメソッドに渡される点が異なります(変数が値型の場合、値のコピーが実行されます。これが参照型の場合、geological Copyであり、メソッド内の構造の値を変更する場合は、ポインターを使用して渡すことができます)。
- メソッドのアクセススコープコントロールは関数のアクセススコープコントロールと同じです。メソッド名の最初の文字は小文字のパッケージ内でのみアクセスでき、大文字はパッケージの外部でアクセスできます。
サンプルコード
//定义结构体
type Person struct{
Name string
}
//定义方法
func (p Person) speak(){
fmt.Println(p.Name,"是一个好人")
}
func test03(){
p := Person{
"zhangsan"}
p.speak()
}
工場モード
ファクトリパターンは、指定されたメソッドによってインスタンスを作成するために使用されます(パッケージ/小文字の構造でのみ使用可能)。
そして、最初の属性構造の小文字クラスもプロセスによって取得できる場合(set / getメソッドjavaと同様)
サンプルコード
func test04(){
var stu = mode.NewStudent("tom",12.0)
fmt.Println(*stu)
fmt.Println(stu.Name,stu.GetScore())
}
package model
type student struct{
Name string
score float64
}
//通过工厂方法获取 隐藏结构的实例
func NewStudent(n string,s float64) *student{
return &student{
Name:n,score:s}
}
//通过方法或去被封装的属性
func( stu *student) GetScore () float64{
return stu.score
}
3つのオブジェクト指向機能
Golangにもオブジェクト指向プログラミングの3つの主要な機能がありますが、実装は他のOOP言語とは異なります。
カプセル化
抽象化されたフィールドを一緒にカプセル化します。
利点:実装の詳細を非表示にし、統合アクセス方法を使用してデータを検証し、データの合理性を確保します
。
- 構造とフィールドの最初の文字を小文字にします(java privateと同様)
- コンストラクターと同様に、最初の文字を大文字にして、カプセル化された構造体にファクトリモード関数を提供します
- プロパティの読み取りとサンプルコードの書き込みに使用される構造体に大文字で始まる最初の文字をSet / Getメソッドに指定し
ます
func test05(){
var per = mode.NewPerson("TOM")
per.SetAge(50)
per.SetSal(10000.0)
fmt.Println(per)
fmt.Println(per.GetAge(),per.GetSal(),per.Name)
}
package model
import (
"fmt"
)
type person struct{
Name string
age int
sal float64
}
// 封装工厂方法 创建结构体实例
func NewPerson(name string) *person{
return &person{
Name:name,
}
}
func (p *person)SetAge(age int){
if age>0 && age<150{
p.age = age
} else{
fmt.Println("年龄不在合法范围内")
}
}
func (p *person)SetSal(sal float64){
if sal>3000 && sal<30000{
p.sal = sal
} else{
fmt.Println("薪资不在合法范围内")
}
}
func (p *person)GetAge() int {
fmt.Println("====>",p.age)
return p.age
}
func (p *person)GetSal() float64 {
fmt.Println("====>",p.sal)
return p.sal
}
継承
継承はコードの再利用を解決できます。複数の構造が同じフィールドとメソッドを持っている場合、構造を抽象化してパブリックプロパティとメソッドを含めることができます。他の構造体は、これらのプロパティとメソッドを定義する必要はなく、この構造体をネストするだけで済みます。
つまり、構造体が別の匿名構造体とネストされている場合、この構造体は匿名構造体のメソッドとフィールドに直接アクセスできるため、継承が実現されます。
基本的な文法形式
type Goods struct{
Name string
Price int
}
type Book struct{
Goods //嵌套匿名结构体
Writer string
}
サンプルコード
func test06(){
pu := &Pupil{
}
pu.Student.Name = "TOM"
pu.Student.Age =12
pu.Age=13//对继承的字段可以简化访问
pu.testing();
pu.Student.ShowInfo()
pu.ShowInfo()//方法简化访问
//声明结构体时直接为嵌套结构体赋值
pu2 := &Pupil{
Student{
Name: "Jack",
Age:19,
Score:100,
},
}
pu2.ShowInfo()
}
//定义学生类
type Student struct{
Name string
Age int
Score int
}
//显示学生信息
func (stu *Student)ShowInfo(){
fmt.Println(stu)
}
func (stu *Student) SetCore(score int){
stu.Score = score
}
//定义小学生类
type Pupil struct{
Student//嵌入 学生结构体
}
func (p *Pupil) testing(){
fmt.Println("小学生正在考试...")
}
注意点
- 構造体は、最初の文字が大文字であるか小文字であるかに関係なく、匿名構造体のすべてのフィールドとメソッドを使用できます。
- 構造体へのアクセス匿名の構造体のメソッドとプロパティを簡略化して、アクセス中のアクセスロジックを簡略化できます。現在の構造体にアクセスプロパティ/メソッドがある場合、現在の構造体にプロパティ/メソッドがない場合は、この構造体のプロパティ/メソッドを直接呼び出します。次に、ネストされた匿名構造で属性/メソッドを探し、見つかったときに呼び出し、見つからない場合はエラーを報告します。
- 構造体とネストされた構造体が同じプロパティまたはメソッドを持っている場合、コンパイラは最も近いアクセスの原則を採用します。匿名構造のプロパティ/メソッドを呼び出す場合は、匿名構造の名前で呼び出す必要があります(簡略化された呼び出しはできません)。
- 構造体に複数の構造体がネストされており、複数のネストされた構造体に同じプロパティ/メソッドが含まれている場合は、呼び出すときに匿名の構造体名を指定する必要があります。それ以外の場合、コンパイラはエラーを報告します
- 構造体が名前付き構造体でネストされている場合、2つは組み合わせ関係です。このとき、複合構造体のプロパティ/メソッドにアクセスするときに、構造体の名前を持参する必要があります。
- 構造体変数をネストした後、構造体変数を作成するときに、ネストされた構造体に値を直接割り当てることができます。
- intなどの基本的なデータ型が構造体にネストされている場合、アクセス方法はA.int = 12です。複数のintフィールドがある場合は、名前を指定する必要があります。
多重継承
構造体が複数の匿名構造体でネストされている場合、構造体のすべてのフィールドとメソッドにアクセスできます。これはGolangの多重継承です。
*複数のネストされた構造体に同じ属性/メソッドが含まれている場合、ネストされた構造体の名前はメソッドを呼び出すときに指定する
インターフェース
インターフェイスタイプは一連のメソッドを定義できますが、実装する必要はなく、インターフェイスに変数を含めることはできません。これらのメソッドは、実際の状況に応じて具体的なタイプで実装されます。
type 接口名 interface{
method1()
method2()
}
サンプルプログラム
type Gun interface{
Fire()
}
type Gun51 struct{
}
func(g Gun51) Fire(){
fmt.Println("Gun51连续发射7mm子弹...")
}
type GunRPG struct{
}
func(g GunRPG) Fire(){
fmt.Println("RPC发射火箭弹...")
}
func test07(){
var g51 Gun51
var rpg GunRPG
var gun Gun = g51
gun.Fire();
gun = rpg
gun.Fire()
}
注意点
- インターフェイスのすべてのメソッドにはメソッド本体がありません。これは、プログラムの高い凝集度と低い結合度の概念を反映しています。
- Golangは、インターフェイスを明示的に実装していません。変数にインターフェイスによって宣言されたすべてのメソッドが含まれている限り、変数はインターフェイスを実装していると見なされます。
- インターフェイス自体はインスタンスを作成しませんが、インターフェイスを実装するインスタンスを指すことができます
- カスタムタイプは、特定のインターフェイスを実装している場合にのみ、そのタイプの変数をこのインターフェイスに割り当てることができます。
- どのカスタムタイプでも、インターフェイスは必ずしも構造である必要はなく、変数は複数のインターフェイスを実現できます。
- インターフェイスAは、他の複数のインターフェイスBおよびCを継承できます。このとき、変数がAインターフェイスを実装する場合は、BインターフェイスとCインターフェイスも同時に実装する必要があります。インターフェイス(継承されたインターフェイスを含む)に重複するメソッドが含まれている場合、コンパイラはエラーを報告します
- インターフェイスはデフォルトでポインタ型です。初期化されていない場合、デフォルトの出力はnilです。
- 空のインターフェイスにはメソッドがありません。すべてのタイプが空のインターフェイスを実装します。つまり、すべてのタイプを空のインターフェイスに割り当てることができます。
継承とインターフェースの比較
継承は、主にコードの再利用性と保守性を解決します。一般的な再利用を強調します。
インターフェイスは主に設計仕様であり、他のタイプが仕様に準拠できるようにします。共通の機能の実現を強調します。
インターフェイスは、ある程度のコードデカップリングを実現します
ポリモーフィズム
変数には多くの形式があり、Golangのポリモーフィズムはインターフェースを介して実装されます。異なる変数での同じインターフェースの異なる実装によれば、インターフェースはさまざまな異なる状態を示します。(インターフェースの例を参照)
ポリモーフィズムは通常、メソッドパラメーターに反映されます
型アサーション
インターフェイス変数をカスタム型変数に割り当てる方法は?最初に変数のタイプを判別する必要があります。つまり、タイプアサーションの
サンプルコードです。
func test08(){
var x interface{
}
var b2 int = 1
x = b2
// 将x 转换为int
y, ok := x.(int);
if ok {
fmt.Printf("转换成功 y的类型是%T 值是=%v \n",y,y);
}else{
fmt.Println("转换失败")
}
fmt.Println("x.(int)==>",x.(int))
//fmt.Println("x.(type)==>",x.(type)) 只能在switch 中使用 这里使用报错
//调用断言方法
TypeJudge(b2)
}
func TypeJudge(items... interface{
}){
for index,x := range items{
switch x.(type){
case bool :
fmt.Printf("第%v个参数是 bool 类型,值是 %v \n",index,x)
case float32:
fmt.Printf("第%v个参数是 float32 类型,值是 %v \n",index,x)
case float64:
fmt.Printf("第%v个参数是 float64 类型,值是 %v \n",index,x)
case int,int32,int64:
fmt.Printf("第%v个参数是 int 类型,值是 %v \n",index,x)
case string:
fmt.Printf("第%v个参数是 string 类型,值是 %v \n",index,x)
default:
fmt.Printf("第%v个参数 类型不确定,值是 %v \n",index,x)
}
}
}