Golang 是面向对象的么?
是,也不是。尽管 Go 有类型和方法,并且允许面向对象风格的编程,但没有类型层次结构。Go 的『接口』概念提供了一种不同的实现方式,在某些方面更通用。同时,缺少类型层次结构使 Go 的『对象』感觉上比 C++ 或 Java 等语言中的『对象』轻很多。本文的目的就是通过示例来说明,如何使用 Golang 进行面向对象编程
过程化
以下是一个关于身份证ID的示例,用于从身份证中提取生日。通常的实现版本如下:
func Birthday(id string) string { return id[6:14] }
const ( id = "412717199109031697" ) func Test_Birthday(t *testing.T) { found := Birthday(id) wanted := "19910903" if found != wanted { t.Errorf("unexpected birthday, wanted:%v, found:%v", wanted, found) } }
简单的数据,如此实现倒也问题不大。但生产环境中往往遇到的都是复杂的多的数据和操作。此时就需要将数据和操作封装在一起
封装
Golang 中可以通过 type
关键字创建新的类型,同时使用 NewXXX
的风格创建对象。
type ID string func NewID(id string) (ID, error) { if len(id) != 18 { return "", errors.New(fmt.Sprintf("error id length:%v", len(id))) } return ID(id), nil } func (i ID) Birthday() string { return string(i[6:14]) }
func TestID_Birthday(t *testing.T) { found := ID(id).Birthday() wanted := "19910903" if found != wanted { t.Errorf("unexpected birthday, wanted:%v, found:%v", wanted, found) } }
当业务变得更加复杂,同一种功能,存在多种实现方式,比如支付方式,微信支付、京东支付、支付宝、银联等不同渠道的大概流程大抵相似,但实现细节有所区别。此时就需要借助多态的动态绑定来进行业务抽象。
多态
Golang 中动态绑定方法的唯一方式是通过接口(interface)来实现的。结构或其他具体类型上的方法始终是静态的。
type ID interface { Birthday() string } type id string func NewID(i string) (ID, error) { if len(i) != 18 { return nil, errors.New(fmt.Sprintf("error id length:%v", len(i))) } return id(i), nil } func (i id) Birthday() string { return string(i[6:14]) }
const ( fakeid = "412717199109031697" ) func TestID_Birthday(t *testing.T) { var i ID i, _ = NewID(fakeid) found := i.Birthday() wanted := "19910903" if found != wanted { t.Errorf("unexpected birthday, wanted:%v, found:%v", wanted, found) } }
除了线性的业务逻辑处理场景,在生产中还会遇到层状的业务流,但是不同层次之间又有很多功能是相通的,此时就需要借助“继承”之类的能力,来实现代码复用。遗憾的是 Golang 中并不存在继承,下面我们介绍它的替代者
组合
在最知名的语言中,面向对象编程很多讨论是关于类型之间关系的。Go 采用了不同的实现 —— 隐藏类式的类型依赖。
在 Go 中,类型会自动满足指定其方法子集的任何接口,无需提前声明两种类型相关联。类型可以一次满足许多接口,而没有传统的多重继承的复杂性。 类型和接口之间没有明确的关系,所以不涉及类型层次结构。类似想法可以用来构造像类型安全的 Unix 管道一样的实现。
所有的“继承”之类的实现,在 Golang 中都能以组合(或内嵌)的方式来实现,组合和内嵌的对象可以是具体的类型,也可以抽象的接口。以下示例介绍了两种风格的实现:
type Man interface { Birthday() string } func NewMan(name, id string) (Man, error) { i, err := NewID(id) return &man{ id: i, name: name, }, err } func NewManEmbedding(name, id string) (Man, error) { i, err := NewID(id) return &manEmbedding{ ID: i, name: name, }, err } type man struct { id ID name string } func (m *man) Birthday() string { return m.id.Birthday() } type manEmbedding struct { ID name string }
const ( peterid = "412717199109031697" samid = "312717199109036148" ) func TestMan_Birthday(t *testing.T) { peter, _ := NewMan("peter", peterid) sam, _ := NewMan("sam", samid) mans := []Man{peter, sam} for _, man := range mans { found := man.Birthday() wanted := "19910903" if found != wanted { t.Errorf("unexpected birthday, wanted:%v, found:%v", wanted, found) } } }
总结
Write go in go way. Golang 很多经典的思想都可以通过官方文档获得,例如:FAQ。
源代码:https://github.com/cyningsun/go-test/tree/master/20210311-oop-in-go
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/03-12-2021/oop-in-go.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!