探索Go语言的无类设计:从Struct到组合的优雅之道在众多编程语言中,Go以其简洁和高性能著称,但它却刻意摒弃了传统的面向对象特性——class。与C++、Java等语言的继承体系不同,Go选择了一条别样的道路:通过struct、方法关联以及组合(composition)来实现
在众多编程语言中,Go 以其简洁和高性能著称,但它却刻意摒弃了传统的面向对象特性——class。与 C++、Java 等语言的继承体系不同,Go 选择了一条别样的道路:通过 struct、方法关联以及组合(composition)来实现数据与行为的组织。这种设计不仅简化了代码结构,还赋予了开发者更大的灵活性。本文将深入剖析 Go 如何在无 class 的世界中实现面向对象的目标,从 struct 的基本用法到组合与转发的强大特性,带你领略 Go 的独特哲学。
Go 语言摒弃了传统的 class 和继承机制,转而使用 struct 结合方法关联来定义数据和行为。通过 struct 的复合字面值和构造函数,开发者可以灵活初始化复杂数据结构;而嵌入(embedding)特性则通过组合与方法转发,实现了类似继承的功能,但更为简洁和灵活。本文从 struct 的基本使用入手,展示了如何将方法绑定到类型上,并通过构造函数和组合替代 class 的功能。代码示例进一步阐释了这些概念的实践应用,包括距离计算和温度转换等场景。文章还探讨了组合优于继承的设计哲学,帮助读者理解 Go 的无类之道。
package main
import "fmt"
// coordinate in degrees, minutes, seconds in a N/S/E/W hemisphere.
type coordinate struct {
d, m, s float64
h rune
}
// decimal converts a d/m/s coordinate to decimal degrees.
func (c coordinate) decimal() float64 {
sign := 1.0
switch c.h {
case 'S', 'W', 's', 'w':
sign = -1
}
return sign * (c.d + c.m/60 + c.s/3600)
}
func main() {
// Bradbury Landing: 4°35'22.2" S, 137°26‘30.1“ E
lat := coordinate{4, 35, 22.2, 'S'}
long := coordinate{137, 26, 30.12, 'E'}
fmt.Println(lat.decimal(), long.decimal())
}
package main
import "fmt"
// coordinate in degrees, minutes, seconds in a N/S/E/W hemisphere.
type coordinate struct {
d, m, s float64
h rune
}
// decimal converts a d/m/s coordinate to decimal degrees.
func (c coordinate) decimal() float64 {
sign := 1.0
switch c.h {
case 'S', 'W', 's', 'w':
sign = -1
}
return sign * (c.d + c.m/60 + c.s/3600)
}
type location struct {
lat, long float64
}
// newLocation from latitude, longitude d/m/s coordinates.
func newLocation(lat, long, coordinate) location {
return location{lat.decimal(), long.decimal()}
}
func main() {
// Bradbury Landing: 4°35'22.2" S, 137°26‘30.1“ E
lat := coordinate{4, 35, 22.2, 'S'}
long := coordinate{137, 26, 30.12, 'E'}
fmt.Println(lat.decimal(), long.decimal())
// curiosity := location{lat.decimal(), long.decimal()}
curiosity := newLocation(lat, long)
fmt.Println(curiosity)
}
如果你想构建一个Universe类型的变量,那么你如何为该函数命名?
答案:newUniverse 或 NewUniverse,推荐 NewUniverse(Go 惯例)。
Go语言没有class,但使用struct并配备几个方法也可以达到同样的效果。
package main
import (
"fmt"
"math"
)
type location struct {
lat, long float64
}
type world struct {
radius float64
}
// distance calculation using the Spherical Law of Cosines.
func (w world) distance(p1, p2 location) float64 {
s1, c1 := math.Sincos(rad(p1.lat))
s2, c2 := math.Sincos(rad(p2.lat))
clong := math.Cos(rad(p1.long - p2.long))
return w.radius * math.Acos(s1*s2+c1*c2*clong)
}
// rad converts degrees to radians.
func rad(deg float64) float64 {
return deg * math.Pi / 180
}
func main() {
var mars = world{radius: 3389.5}
spirit := location{-14.5684, 175.472636}
opportunity := location{-1.9462, 354.4734}
dist := mars.distance(spirit, opportunity)
fmt.Printf("%.2f km\n", dist)
}
例子一
package main
type report struct {
sol int
high, low float64
lat, hong float64
}
func main() {
}
例子二
package main
type report struct {
sol int
temperature temperature
location location
}
type temperature struct {
hign, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func (r report) average() celsius {
return r.temperature.average()
}
func main() {
bradbury := location{-4.5895, 137.4417}
t := temperature{high: -1.0, low: -78.0}
fmt.Println(t.average())
report := report {
sol: 15,
temperature: t,
location: bradbury
}
fmt.Println(report.temperature.average())
fmt.Println(report.average())
fmt.Printf("%+v\n", report)
fmt.Printf("a balmy %v° C\n", report.temperature.high)
}
package main
import "fmt"
//type report struct {
// sol int
// temperature temperature
// location location
//}
type report struct {
sol int
temperature
location
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func main() {
bradbury := location{-4.5895, 137.4417}
t := temperature{high: -1.0, low: -78.0}
fmt.Println(t.average())
report := report {
sol: 15,
temperature: t,
location: bradbury
}
fmt.Println(report.average())
fmt.Println(report.high)
fmt.Printf("%+v\n", report)
fmt.Printf("a balmy %v° C\n", report.temperature.high)
}
package main
import "fmt"
//type report struct {
// sol int
// temperature temperature
// location location
//}
type sol int
type report struct {
sol
temperature
location
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func (s sol) days(s2 sol) int {
days := int(s2 - s)
if days < 0 {
days = -days
}
return days
}
func main() {
report := report {
sol: 15,
}
fmt.Println(report.sol.days(1446))
fmt.Println(report.days(1446))
}
package main
import "fmt"
//type report struct {
// sol int
// temperature temperature
// location location
//}
type sol int
type report struct {
sol
temperature
location
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func (s sol) days(s2 sol) int {
days := int(s2 - s)
if days < 0 {
days = -days
}
return days
}
func (l location) days(l2 location) int {
// To-do: complicated distance calculation
return 5
}
func (r report) days(s2 sol) int {
return r.sol.days(s2)
}
func main() {
report := report {
sol: 15,
}
fmt.Println(report.sol.days(1446))
//fmt.Println(report.days(1446)) // 报错
fmt.Println(report.days(1446))
}
Go 语言的无类设计并不是对面向对象的否定,而是对其的一种重新定义。通过 struct 和方法关联,Go 提供了简洁的数据与行为绑定方式;通过组合与转发,它在避免继承复杂性的同时,保留了灵活性和复用性。从构造函数的优雅实现到嵌入带来的方法共享,Go 用更少的概念解决了 class 所能解决的问题。正如“一切有为法,如梦幻泡影”,Go 的设计哲学提醒我们:技术不必拘泥于传统,简单的组合往往胜过繁琐的继承。无论你是面向对象编程的拥趸,还是初探 Go 的新手,这种无类之道都值得一试。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!