Go 面向对象

开发 开发工具
Chapter 6说明了一个简单的问题。维修工需要知道自行车出行需要带上的备件,决定于哪一辆自行车已经被租出去。问题可以通过经典的继承来解决,山地车和公路自行车是自行车基类的一个特殊化例子。

Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。

Go没有使用classes,但提供很多相似的功能:

·通过嵌入实现的自动消息委托

·通过接口实现多台

·通过exports实现的命名空间

Go语言中没有继承。忘记is-a的关系,而是就组合而言的面向对象设计。

“使用经典的继承始终是可选的;每个问题都可以通过其他方法得到解决” - Sandi Metz

通过例子说明组合

最近阅读了一篇Ruby的面向对象编程实践, 我决定使用Go语言翻译这个例子。

Chapter 6说明了一个简单的问题。维修工需要知道自行车出行需要带上的备件,决定于哪一辆自行车已经被租出去。问题可以通过经典的继承来解决,山地车和公路自行车是自行车基类的一个特殊化例子。Chapter 8使用组合改写了同一个例子。我很高兴这个例子翻译成Go。让我们看看。

Packages(包)

package main  
 
import "fmt" 
  • 1.
  • 2.
  • 3.

包提供了命名空间概念. main() 函数是这个包的入口函数. fmt包提供格式化功能

Types(类型)

type Part struct {  
    Name        string  
    Description string  
    NeedsSpare  bool  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

我们定义了一个新的类型名为Part, 非常像c的结构体

type Parts []Part 
  • 1.

Parts类型是包含Part类型的数组切片, Slice可以理解为动态增长的数组, 在Go中是很常见的.

我们可以在任何类型上声明方法,  所以我们不需要要再去封装 []Part, 这意味着 Parts 会拥有slice的所有行为, 再加上我们自己定义的行为方法.

方法

func (parts Parts) Spares() (spares Parts) {  
    for _, part := range parts {  
        if part.NeedsSpare {  
            spares = append(spares, part)  
        }  
    }  
    return spares  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

Go中定义方法就像一个函数,除了它有一个显式的接收者,紧接着func之后定义。这个函数利用命名返回变量,并为我们初始化备件。

方法的主体十分简单。我们重复parts,忽略索引的位置(_),过滤parts后返回。append builtin 需要分配和返回一个大的切片,因为我们并没有预先分配好它的容量。

这段代码没有ruby代码来得优雅。在Go语言中有过滤函数,但它并非是builtin.

内嵌

type Bicycle struct {  
    Size string  
    Parts  

  • 1.
  • 2.
  • 3.
  • 4.

自行车由Size和Parts组成。没有给Parts指定一个名称,我们是要保证实现 内嵌。这样可以提供自动的委托,不需特殊的声明,例如bike.Spares()和bike.Parts.Spares()是等同的。

如果我们向Bicycle增加一个Spares()方法,它会得到优先权,但是我们仍然引用嵌入的Parts.Spares()。这跟继承十分相似,但是内嵌并不提供多态。Parts的方法的接收者通常是Parts类型,甚至是通过Bicycle委托的。

与继承一起使用的模式,就像模板方法模式,并不适合于内嵌。就组合和委托而言去考虑会更好,就如我们这个例子一样。

Composite Literals(复合语义)

var (  
    RoadBikeParts = Parts{  
        {"chain""10-speed"true},  
        {"tire_size""23"true},  
        {"tape_color""red"true},  
    }  
 
    MountainBikeParts = Parts{  
        {"chain""10-speed"true},  
        {"tire_size""2.1"true},  
        {"front_shock""Manitou"false},  
        {"rear_shock""Fox"true},  
    }  
 
    RecumbentBikeParts = Parts{  
        {"chain""9-speed"true},  
        {"tire_size""28"true},  
        {"flag""tall and orange"true},  
    }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

Go提供优美的语法,来初始化对象,叫做 composite literals。使用像数组初始化一样的语法,来初始化一个结构,使得我们不再需要ruby例子中的Parts工厂。

func main() {  
    roadBike := Bicycle{Size: "L", Parts: RoadBikeParts}  
    mountainBike := Bicycle{Size: "L", Parts: MountainBikeParts}  
    recumbentBike := Bicycle{Size: "L", Parts: RecumbentBikeParts} 
  • 1.
  • 2.
  • 3.
  • 4.

Composite literals(复合语义)同样可以用于字段:值的语法,所有的字段都是可选的。

简短的定义操作符(:=)通过Bicycle类型,使用类型推论来初始化roadBike,和其他。

输出

fmt.Println(roadBike.Spares())  
fmt.Println(mountainBike.Spares())  
fmt.Println(recumbentBike.Spares()) 
  • 1.
  • 2.
  • 3.

我们将以默认格式打印 Spares 的调用结果:

[{chain 10-speed true} {tire_size 23 true} {tape_color red true}]  
[{chain 10-speed true} {tire_size 2.1 true} {rear_shock Fox true}]  
[{chain 9-speed true} {tire_size 28 true} {flag tall and orange true}] 
  • 1.
  • 2.
  • 3.

组合 Parts

    comboParts :Parts{}  
    comboParts = append(comboParts, mountainBike.Parts...)  
    comboParts = append(comboParts, roadBike.Parts...)  
    comboParts = append(comboParts, recumbentBike.Parts...)  
 
    fmt.Println(len(comboParts), comboParts[9:])  
    fmt.Println(comboParts.Spares())  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

Parts 的行为类似于 slice。按照长度获取切片,或者将数个切片结合。Ruby 中的类似解决方案就数组的子类,但是当两个 Parts 连接在一起时,Ruby 将会“错置” spares 方法。

“……在一个完美的面向对象的语言,这种解决方案是完全正确的。不幸的是,Ruby语言并没有完美的实现……”
—— Sandi Metz

在 Ruby 中有一个那看的解决方法,使用 Enumerable、forwardable,以及 def_delegators。 Go有没有这样的缺陷。 []Part 正是我们所需要的,且更为简洁(更新:Ruby 的 SimpleDelegator 看上去好了一点)。

接口 Interfaces

Go的多态性由接口提供。不像JAVA和C#,它们是隐含实现的,所以接口可以为不属于我们的代码定义。

和动态类型比较,接口是在它们声明过程中静态检查和说明的,而不是通过写一系列响应(respond_to)测试完成的。

“不可能不知不觉的或者偶然的创建一个抽象;在静态类型语言中定义的接口总是有倾向性的。” - Sandi Metz

给个简单的例子,假设我们不需要打印Part的NeedsSpare标记。我们可以写这样的字符串方法:

func (part Part) String() string {  
    return fmt.Sprintf("%s: %s", part.Name, part.Description)  

  • 1.
  • 2.
  • 3.

然后对上述Print的调用将会输出这样的替代结果:

[chain: 10-speed tire_size: 23 tape_color: red]  
[chain: 10-speed tire_size: 2.1 rear_shock: Fox]  
[chain: 9-speed tire_size: 28 flag: tall and orange] 
  • 1.
  • 2.
  • 3.

这个机理是因为我们实现了fmt包会用到的Stringer接口。它是这么定义的:

type Stringer interface {  
    String() string  

  • 1.
  • 2.
  • 3.

接口类型在同一个地方可以用作其它类型。变量与参数可以携带一个Stringer,可以是任何实现String() string方法签名的接口。

Exports 导出

Go 使用包来管理命名空间, 要使某个符号对其他包(package )可见(即可以访问),需要将该符号定义为以大写字母开头,  当然,如果以小写字母开关,那就是私有的.包外不可见.

type Part struct {  
    name        string  
    description string  
    needsSpare  bool  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

为了对Part类型应用统一的访问原则(uniform access principle), 我们可以改变Part类型的定义并提供setter/getter 方法,就像这样:

func (part Part) Name() string {  
    return part.name  
}  
 
func (part *Part) SetName(name string) {  
    part.name = name  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

这样可以很容易的确定哪些是public API, 哪些是私有的属性和方法, 只要通过字母的大小写.(例如(part.Name()vs.part.name)

注意 我们不必要对 getters 加前Get, (例如.GetName),Getter不是必需,特别是对于字符串,当我们有需要时,我们可以使用满足Stringer 类型接口的自定义的类型去改变Name 字段。

找到一些私有性

私有命名(小写字母)可以从同一个包的任何地方访问到,即使是包含了跨越多个文件的多个结构。如果你觉得这令人不安,包也可以像你希望的那么小。

可能的情况下用(更稳固的)公共API是一个好的实践,即使是来自经典语言的同样的类中。这需要一些约定,当然这些约定可以应用在GO中。

最大的好处

组合,内嵌和接口提供了Go语言中面向对象设计的强大工具。继承概念的思想真的不起什么作用。相信我,我尝试了

习惯Go需要思维的改变,当触及到Go对象模型的力量时,我非常高兴的吃惊于Go代码的简单和简洁。

原文链接:http://www.oschina.net/translate/go-object-oriented-design

责任编辑:张伟 来源: oschina
相关推荐

2024-04-02 07:32:58

Go语言接口

2024-01-08 07:02:48

数据设计模式

2021-05-20 08:54:16

Go面向对象

2023-01-10 09:38:09

面向对象系统

2021-11-08 07:48:48

Go语言对象

2022-07-30 23:41:53

面向过程面向对象面向协议编程

2013-08-21 17:20:49

.NET面向对象

2013-04-17 10:46:54

面向对象

2017-04-21 09:07:39

JavaScript对象编程

2012-01-17 09:34:52

JavaScript

2010-07-15 13:56:24

面向对象面向过程

2023-11-30 08:00:54

面向对象面向切面

2012-12-13 11:01:42

IBMdW

2021-10-21 18:47:37

JavaScript面向对象

2009-06-26 13:29:11

面向对象

2010-07-06 09:43:34

UML面向对象

2012-02-27 09:30:22

JavaScript

2012-06-07 10:11:01

面向对象设计原则Java

2009-07-14 16:51:50

Jython中的对象

2023-09-27 23:28:28

Python编程
点赞
收藏

51CTO技术栈公众号