ch17、面向对象编程-扩展与复用

本文探讨Go语言中面向对象编程的扩展与复用,通过结构体内嵌实现类似继承的效果。虽然Go不支持继承和多态,但可以通过接口实现多态机制。同时介绍了如何分析一个类,并指出在Go中匿名嵌入结构体会遵循就近原则处理重名情况。
摘要由CSDN通过智能技术生成

ch17、面向对象编程 - 扩展与复用

1、扩展与复用(即结构体内嵌)

Go不支持继承,通过组合(内嵌)的方式来实现(没有override的特性,Go本身不支持隐式类型转换,对于内嵌来说显式转换也不行)

与其它主要编程语言的差异:

  • 不支持子类替换
  • 子类并不是真正继承了父类的方法(“duck type”)
  • 父类定义的方法无法访问子类的数据和方法

内嵌:B嵌入A,则A具备B的所有字段和行为(方法)

type B struct{
    X,Y int
}

type A struct{
    B
    Z int
}

// 则A具备有X,Y,Z字段

匿名嵌入结构体实例

package extension

import (
	"fmt"
	"testing"
)

type Pet struct {
}

func (p *Pet) Speak() {
	fmt.Print("...")
}

func (p *Pet) SpeakTo(host string) {
	p.Speak()
	fmt.Println(" ", host)
}

type Dog struct {
	Pet
}

func (d *Dog) Speak() {
	fmt.Println("wang!")
}

func TestDog(t *testing.T) {
	dog := new(Dog)
	dog.Speak()
	dog.SpeakTo("Chao")
}
/*
output:
wang!
...  Chao
*/

嵌入的类型可以是指针,此时,字段和方法间接的来自于所指向的对象(匿名嵌入结构体实例指针):

package extension

import (
	"fmt"
	"testing"
)

type Pet struct {
}

func (p *Pet) Speak() {
	fmt.Print("...")
}

func (p *Pet) SpeakTo(host string) {
	p.Speak()
	fmt.Println(" ", host)
}

type Cat struct {
	*Pet
}

func (c *Cat) Speak() {
	fmt.Println("miaomiao!")
}

func (c *Cat) toPet(p *Pet, s string) {
	p.SpeakTo(s)
}

func TestCat(t *testing.T) {
	cat := new(Cat)
	cat.Speak()
	cat.SpeakTo("Chao")
	cat.toPet(&Pet{}, "cat miaomiao")
}
/*
output:
miaomiao!
...  Chao
...  cat miaomiao
*/
  • 继承结构中出现重名情况, 采用就近原则
package main

import "fmt"

type Person struct {
	name string // 属性重名
	age  int
}
type Student struct {
	Person
	name  string // 属性重名
	score int
}

func main() {
	s := Student{Person{"zs", 18}, "ls", 99}

	fmt.Println(s.Person.name) // zs
	fmt.Println(s.name)        // ls
	//fmt.Println(s.Person.age)
	fmt.Println(s.age) // 两种方式都能访问
	fmt.Println(s.score)
}

2、Go嵌入式"继承"机制的局限

不支持Overriding Methods。

package main

import (
	"fmt"
)

type Pet struct {
	name string
}

type Dog struct {
	Pet
	Breed string
}

func (p *Pet) Play() {
	fmt.Println(p.Speak())
}

func (p *Pet) Speak() string {
	return fmt.Sprintf("my name is %v", p.name)
}

func (p *Pet) Name() string {
	return p.name
}

func (d *Dog) Speak() string {
	return fmt.Sprintf("%v and I am a %v", d.Pet.Speak(), d.Breed)
}

func main() {
	d := Dog{Pet: Pet{name: "spot"}, Breed: "pointer"}
	fmt.Println(d.Name())
	fmt.Println(d.Speak())
	d.Play() // 依然会调用pet的Speak,而不是dog的Speak,即嵌入的方法不会被
}
/*
output:
spot
my name is spot and I am a pointer
my name is spot
*/

不存在Subtyping(没有多态)。

在Java中,Dog继承自Pet,那么Dog类型就是Pet子类。这意味着在任何需要调用Pet类型的场景都可以使用Dog类型替换。这种关系称作多态性,但Go的结构体类型不存在这种机制。

package main

import (
	"fmt"
)

type Pet struct {
	speaker func() string
	name    string
}

type Dog struct {
	Pet
	Breed string
}

func NewPet(name string) *Pet {
	p := &Pet{
		name: name,
	}
	p.speaker = p.speak
	return p
}

func (p *Pet) Play() {
	fmt.Println(p.Speak())
}

func (p *Pet) Speak() string {
	return p.speaker()
}

func (p *Pet) speak() string {
	return fmt.Sprintf("my name is %v", p.name)
}

func (p *Pet) Name() string {
	return p.name
}

func NewDog(name, breed string) *Dog {
	d := &Dog{
		Pet:   Pet{name: name},
		Breed: breed,
	}
	d.speaker = d.speak
	return d
}

func (d *Dog) speak() string {
	return fmt.Sprintf("%v and I am a %v", d.Pet.speak(), d.Breed)
}

func Play(p *Pet) {
	p.Play()
}

func main() {
	d := NewDog("spot", "pointer")
	fmt.Println(d.Name())
	fmt.Println(d.Speak())
	Play(d) //  cannot use d (type *Dog) as type *Pet in argument to Play
}

但是,可以通过接口类型中存在子类化来实现多态机制!

package main

import (
	"fmt"
)

type Pet interface {
	Name() string
	Speak() string
	Play()
}

type pet struct {
	speaker func() string
	name    string
}

type Dog interface {
	Pet
	Breed() string
}

type dog struct {
	pet
	breed string
}

func NewPet(name string) *pet {
	p := &pet{
		name: name,
	}
	p.speaker = p.speak
	return p
}

func (p *pet) Play() {
	fmt.Println(p.Speak())
}

func (p *pet) Speak() string {
	return p.speaker()
}

func (p *pet) speak() string {
	return fmt.Sprintf("my name is %v", p.name)
}

func (p *pet) Name() string {
	return p.name
}

func NewDog(name, breed string) *dog {
	d := &dog{
		pet:   pet{name: name},
		breed: breed,
	}
	d.speaker = d.speak
	return d
}

func (d *dog) speak() string {
	return fmt.Sprintf("%v and I am a %v", d.pet.speak(), d.breed)
}

func Play(p Pet) {
	p.Play()
}

func main() {
	d := NewDog("spot", "pointer")
	fmt.Println(d.Name())
	fmt.Println(d.Speak())
	Play(d)
}
/*
output:
spot
my name is spot and I am a pointer
my name is spot and I am a pointer
*/

参考文档:

Go中的面向对象 - “继承”

在线运行结果

3、如何分析一个类

  • 一般名词都是类(名词提炼法)
    • 飞机发射两颗炮弹摧毁了8辆装甲车
飞机
炮弹
装甲车
  • 隔壁老王在公车上牵着一条叼着热狗的草泥马
老王
热狗
草泥马
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值