8. Orientado a objetos

Introduzimos funções e estruturas anteriormente, mas você já pensou em tratar funções como campos de estrutura? Hoje vamos explicar outra forma de função, uma função com receptor, que chamamos de método.

método

Agora suponha que exista tal cenário. Você definiu uma estrutura chamada retângulo. Agora deseja calcular sua área. Então, de acordo com nossa ideia geral, você deve usar o seguinte método para alcançá-lo.

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)) 
}

Este código pode calcular a área de um retângulo, mas area() não é implementado como um método Rectangle (semelhante ao método orientado a objetos).Em vez disso, objetos Rectangle (como r1, r2) são passados ​​​​para a função como parâmetros para calcular a área.

Claro que não há problema com esta implementação, mas quando você precisa adicionar círculos, quadrados, pentágonos ou mesmo outros polígonos, o que deve fazer quando quiser calcular suas áreas? Então você só pode adicionar novas funções, mas terá que alterar os nomes das funções de acordo, tornando-se area_rectangle, area_circle, area_triangle...

Conforme mostrado na figura abaixo, os ovais representam funções, e essas funções não pertencem à estrutura (ou em termos orientados a objetos, não pertencem à classe).Elas existem sozinhas fora da estrutura, em vez de pertencerem conceitualmente a ela. de uma determinada estrutura.

Com base nas razões acima, existe o conceito de método. O método está anexado a um determinado tipo. Sua sintaxe é quase a mesma que a sintaxe de declaração de uma função, exceto que um receptor é adicionado após func (ou seja, o método para do qual o método depende. corpo principal).

Usando o exemplo de forma mencionado acima, o método area() depende de uma determinada forma (como Rectangle) para funcionar. O emissor de Rectangle.area() é Rectangle, e area() é um método pertencente a Rectangle, não uma função periférica.

Mais especificamente, Rectangle possui os campos comprimento e largura, e o método area(). Todos esses campos e métodos pertencem ao Rectangle.

A sintaxe do método é a seguinte:

func (r ReceiverType) funcName(parameters) (results)

Vamos usar o exemplo inicial para implementá-lo usando o método:

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()) 
}

Existem alguns pontos importantes a serem observados ao usar o método

  • Embora os nomes dos métodos sejam exatamente iguais, se os receptores forem diferentes, os métodos serão diferentes.
  • Você pode acessar os campos do destinatário no método
  • Chamar acessos de método através de ., assim como acessar campos em struct

Isso significa que o método só pode ser aplicado à estrutura? Claro que não, ele pode ser definido em qualquer um dos seus tipos personalizados, tipos integrados, estruturas e outros tipos. Você está um pouco confuso aqui? O que é um tipo personalizado? Um tipo personalizado não é apenas uma estrutura? Esse não é o caso. A estrutura é apenas um tipo especial entre os tipos personalizados. Existem outras declarações de tipo personalizado. Você pode fazer isso é alcançado através de uma declaração como a seguinte.

type typeName typeLiteral 
// 请看下面这个申明自定义类型的代码 
type ages int 
type money float32 
type months map[string]int 
m := months {
    
     
    "January":31, 
    "February":28, 
    ... 
    "December":31, 
}

Veja? É muito simples, então você pode definir tipos significativos em seu próprio código. **Na verdade, é apenas um alias definido, **É um pouco semelhante ao typedef em C. Por exemplo, age substitui int acima.

Ok, vamos voltar ao método

Você pode definir quantos métodos quiser em qualquer tipo personalizado. Vejamos um exemplo mais complexo.

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())
}

O código acima define algumas constantes por meio de const e, em seguida, define alguns tipos personalizados

  • Cor como apelido para byte
  • Uma struct:Box é definida, que contém três campos de comprimento, largura e altura e um atributo de cor.
  • Uma fatia:BoxList é definida, contendo Box

Em seguida, alguns métodos são definidos para o receptor usando o tipo personalizado acima

  • Volume() define o receptor como Box e retorna a capacidade do Box
  • SetColor (c Color), altere a cor da Box para c
  • BiggestsColor() é definido em BoxList e retorna a cor com maior capacidade na lista.
  • PaintItBlack() altera a cor de todas as caixas no BoxList para preto
  • String() é definido acima de Color e retorna a cor específica de Color (formato de string)

O código acima é muito simples depois de descrito em palavras? Geralmente resolvemos problemas descrevendo o problema e escrevendo o código correspondente para implementá-lo.

Ponteiro como receptor

Agora vamos voltar e dar uma olhada no método SetColor. Seu receptor é um ponteiro para Box. Sim, você pode usar *Box. Pense por que você deveria usar ponteiros em vez do próprio Box?

O verdadeiro propósito de definir SetColor é mudar a cor da Box. Se o ponteiro Box não for passado, SetColor na verdade aceita uma cópia da Box. Ou seja, a modificação do valor da cor no método afeta apenas a cópia da Caixa. , em vez de uma Caixa real. Então precisamos passar o ponteiro.

Aqui você pode olhar para o receptor como o primeiro parâmetro do método e, em seguida, combinado com a passagem de valor e passagem de referência explicada na função anterior, não é difícil de entender aqui. Você pode perguntar se *b.Color= c deve ser definido na função SetColor assim, em vez de b.Color=c, porque precisamos ler o valor correspondente do ponteiro.

Você está certo. Na verdade, ambos os métodos em Go estão corretos. Quando você usa um ponteiro para acessar o campo correspondente (embora o ponteiro não tenha nenhum campo), Go sabe que você deseja obter o valor através do ponteiro. Veja, o design do Go está se tornando cada vez mais atraente para você?

Talvez leitores cuidadosos façam esta pergunta: ao chamar SetColor em PaintItBlack, ele deveria ser escrito como (&bl[i]).SetColor(BLACK), porque o receptor de SetColor é *Box, não Box.

Você está certo novamente, ambos os métodos são possíveis, porque Go sabe que o receptor é um ponteiro e irá transferi-lo automaticamente para você.

Quer dizer:

Se o receptor de um método for *T, você poderá chamar esse método em uma variável de instância V do tipo T sem precisar &V para chamar esse método.

Da mesma forma, se o receptor de um método for T, você pode chamar esse método em uma variável do tipo *T P sem *P para chamar esse método.

Portanto, você não precisa se preocupar se está chamando um método de ponteiro ou não. Go sabe tudo o que você precisa fazer. Isso realmente resolve um grande problema para estudantes com muitos anos de experiência em programação C/C++.

herança de método

No capítulo anterior, aprendemos sobre herança de campo, então você também encontrará uma coisa mágica sobre Go, os métodos também podem ser herdados. Se um campo anônimo implementar um método, a estrutura que contém o campo anônimo também poderá chamar esse método. Vejamos o seguinte exemplo

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) 
}

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() 
}

substituição de método

No exemplo acima, o que o Empregado deve fazer se quiser implementar seu próprio SayHi? Simples, assim como os conflitos de campos anônimos, podemos definir um método em Emplyee e substituir o método de campo anônimo. Por favor veja o exemplo abaixo

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() 
}

Através desses conteúdos, podemos projetar programas básicos orientados a objetos, mas o programa orientado a objetos em Go é tão simples, sem quaisquer palavras-chave privadas ou públicas, e é implementado através de letras maiúsculas e minúsculas (aquelas que começam com maiúsculas são compartilhadas, e aquelas começar com letras minúsculas é privado), o mesmo princípio se aplica aos métodos.

Supongo que te gusta

Origin blog.csdn.net/u012534326/article/details/120399781
Recomendado
Clasificación