# 面向对象

1. 面向对象是软件开发的方法
2. 泛指现实中一切实体事务，每种事务都具备自己的属性和行为
3. 将事务的属性、行为抽象出来，描述成计算机事件的设计思想

## 类

在golang中表示一个类，使用的是结构体。

## 封装

golang中的封装，主要是通过类型、方法名称的首字母大小写为主要依据。如果首字母大写，代表将该类型、方法、函数导出，在其他包下，则可以直接使用。反之，如果首字母小写，则只能被相同包的人直接引用。下面是代码示例：

```go
// Package person
// 包 person下，person.go
package person

import "fmt"

// 小写开头，结构体person不导出
type person struct {
    Name string
    age uint8
    sal float32
}

// SetAge 大写字母，方法SetAge导出
func (p *person) SetAge(age uint8) {
    if age > 200 {
        fmt.Println("年龄范围在0-200之间")
    } else {
        p.age = age
    }
}

func (p *person) GetAge() uint8 {
    return p.age
}

func (p *person) SetSal(sal float32) {
    if sal <= 0 {
        fmt.Println("薪水不可小于零")
    } else {
        p.sal = sal
    }
}

// 使用工厂模式创建对象
func NewPerson(name string) *person{
    return &person{
        Name: name,
    }
}
```

```go
// package main
// 包main，main.go
package main

import (
    "fmt"
    "person"
)

func main() {
    p := person.NewPerson("张三")
    p.Name = "李四" // 导出字段可以直接使用
    p.SetAge(10) // 通过导出方法操作非导出字段
    fmt.Printf("姓名: %v 年龄： %v", p.Name, p.GetAge())
}
```

## 继承

继承可以解决代码复用的问题，当多个结构体出现相同的字段、方法，可以从这些结构体中抽象出一个新的结构体，在这个结构体中定义这些共用字段、方法。最后所有的结构体都去继承这个结构体即可。

golang没有完整严格的地继承方式（耦合更低），他总是优先使用组合方式——可以通过内嵌另一个结构体的方式实现继承。当结构体内嵌了另一个结构体，则当前的结构体可以直接访问内嵌的结构体中的所有属性和方法，代码如下：

```go
// package main
// 包main，main.go
package main

import (
    "fmt"
)

type Animal struct {
    Name string
}

func (a *Animal)Eat() {
}

type Dog struct {
    Animal // Dog 继承Animal，dog拥有Animal的所有字段与方法
    // 准确的说：Dog嵌入了一个匿名结构体Animal
    // 如果一个struct嵌套了另一个匿名的struct，
    // 那么这个struct可以直接访问匿名结构体中的任何字段和方法

    // 这是Dog特有的属性，毛色
    Color string
}

func (d Dog) Eat() { // 相当于重写方法Eat
    fmt.Println("吃骨头")
}

// Look 这是Dog特有的方法
func (d Dog) Look() {
    fmt.Println(d.Name, "在看家")
}

func main() {
    dog := Dog{}
    dog.Name = "旺财" // 继承Animal 的可以直接使用
    dog.Eat() // 调用Eat方法
    dog.Animal.Eat() // 调用父结构体的Eat方法

    // 注意，这里Eat方法在Animal与Dog中都声明了，所以默认使用子构造器的字段
    // 如果要使用父构造器的字段，需要指定内嵌结构体名称
}
```

**总结：**

1. 结构体可以使用嵌套匿名结构体所有的字段和方法，即：首字母大写、小写的字段、方法，都可以使用
2. 匿名结构体的字段访问可以简化：`dog.Animal.Name` 可以简化为`dog.Name`
3. 当结构体与匿名结构体有相同的字段或者方法时，编译器采用就近访问原则访问，如果希望访问匿名结构体的字段与方法，可以使用匿名结构体的名称区分，比如：`Eat()`在`Animal`与`Dog`中都含有此方法，默认使用`dog.Eat()`会调用`Dog`的方法（就近原则），如果想要调用匿名结构体的`Eat()`，需要使用`dog.Animal.Eat()`
4. 结构体嵌入两个或者多个匿名结构体，如果两个匿名结构体具有相同的字段和方法（同时结构体本身没有这个字段与方法），在访问时就必须明确指定匿名结构体名字，否则编译器报错。比如，结构体`A`同时继承`B`与`C`，且`B`和`C`都有方法H`ello()`，当`A`结构体变量访问 `Hello()`时，必须且只能通过`a.B.Hello()`或者 `a.C.Hello()`进行访问，如果使用`a.hello()`则会报错

### 嵌入基本数据类型

```go
package main

import "fmt"

type A struct {
    int  // 相当于 int int，也就是相当于声明了一个名称为int、数据类型为int的字段
  // 也就是 匿名字段的默认字段名称就是类型的名称
  // int // 不能重复声明
    Name string
}

func main() {
    a := A{}
    a.int = 10
    fmt.Println(a.int) // 10
}
```

### 多重继承

如果一个struct嵌套了多个匿名结构体，那么该结构体可以直接访问嵌套的匿名结构体的字段与方法，从而实现多继承。

```go
type A struct {
    AString string
}

type B struct {
    BString string
}

type C struct {
    A
    B
    CString string
}
```

> 尽量不使用多重继承

## 接口

`interface`类型可以定义一组方法，但是这些方法不需要实现，并且`interface`不可以包含任何变量。到某个自定义类型要使用的时候，通过定义所有的接口方法（也就是实现方法），来完成接口实现的效果。

```go
type 接口名 interface {
  method1(参数列表) 返回值列表
  method2(参数列表) 返回值列表
}
```

**注意事项：**

1. 接口本身不能创建实例，但是可以指向一个实现了该接口的自定义类型的变量
2. 接口中所有的方法都没有方法体，即都是没有实现的方法
3. 接口中不能含有任何变量或常量
4. 在golang中，一个自定义类型需要将某个接口的所有方法都实现，我们才说这个类型实现了该接口
5. 只要是自定义类型都是可以实现接口，不仅仅是结构体类型
6. 一个自定义类型可以实现多个接口
7. 一个接口可以继承多个别的接口，如果要实现接口`A`，那么他所继承的`B`以及`C`接口中的方法也必须实现

   ```go
   type A interface {
     B  // A接口继承B以及C接口
     C
     a()
   }
   type B interface {
     b()
   }
   type C interface {
     c()
   }
   ```
8. 如果接口继承之间含有相同的方法名，会报错（**go1.7不报错**）

   ```go
   type A interface {
     B
     C // 报错，含有两个相同的test方法
   }
   type B interface {
     test()
     b()
   }
   type C interface {
     test()
     c()
   }
   ```

**示例：**

```go
package main

import "fmt"

// Usb 定义一个接口
type Usb interface {
    Start() // 给接口声明两个方法 Start 和 Stop
    Stop()
}

// Phone 定义一个手机结构体，实现Usb接口的方法
type Phone struct {
}
func (p Phone) Start() {
    fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
    fmt.Println("手机结束工作")
}

// Camera 定义一个相机结构体，实现Usb接口的方法
type Camera struct {
}
func (c Camera) Start() {
    fmt.Println("相机开始工作")
}

func (c Camera) Stop()  {
    fmt.Println("相机结束工作")
}

// Computer 定义一个计算机
type Computer struct {

}
// Working 方法接收一个Usb接口类型的变量，并调用Usb方法中的方法
func (c Computer) Working(usb Usb) { // 这里的Usb就是多态的体现，类型上溯
    usb.Start()
    usb.Stop()
}

func main() {

    // 在golang中，实现接口 = 定义了接口中的所有方法
    var camera Usb = Camera{} // 多态
    var phone Usb =  Phone{}
    computer := Computer{}
    computer.Working(camera)
    computer.Working(phone)

}
```

## 多态

```go
package main

import "fmt"

type A interface {
    sayA()
}

type B struct {
    Name string
}

// B 实现 A 接口
func (b B) sayA() {
    fmt.Println("你好 a")
}

func (b B) sayB() {
    fmt.Println("你好 b", b.Name)
}


func main() {
    // 上溯造型
    var a A = B{"小熊"}
    a.sayA()

    // 下塑造型 (类型断言，不是类型转换，因为这个变量本身就是该类型，只是断言)
    var b B = a.(B) // 将变量a转换为类型B，如果类型断言失败将会报错
    b.sayB()
}
```

\*\*类型断言：\*\*由于接口是一般类型，不知道具体类型，如果需要具体类型，就需要使用类型断言

1. 要进行类型断言的变量本身就是这个类型，不是类型强转，类型断言只是断言类型
2. 如果类型不匹配，类型断言会抛出`panic: interface conversion`
3. 所以，在进行类型断言时，一定要确定类型
4. 类型断言之前进行检测机制：

   ```go
   // 下塑造型 (类型断言)
   b, ok := a.(B)
   if ok {
     b.sayB()
   } else {
     fmt.Println("类型断言失败")
   }
   ```
5. `switch` + `类型断言`：

   ```go
   func TypeJudge(items ...interface{}) {
       for i, item := range items {
           switch item.(type) { // type 是一个关键字，代表使用case语句中的类型对item进行类型断言
           case bool:
               fmt.Printf("param %d is bool, value is %t", i, item)
           case float32, float64:
               fmt.Printf("param %d is float, value is %v", i, item)
           case int8, int16, int32, int64, int:
               fmt.Printf("param %d is int, value is %v", i, item)
           case nil:
               fmt.Printf("param %d is nil, value is %v", i, item)
           case string:
               fmt.Printf("param %d is string, value is %v", i, item)
           }

       }
   }
   ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/golang/mian-xiang-dui-xiang.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
