在Golang中使用组合的正确方式是什么?

huangapple go评论78阅读模式
英文:

What is the proper way using composition in Golang

问题

我是你的中文翻译助手,以下是你提供的代码的翻译:

第一个例子:

type BaseClass struct {
   db *DB
}

func NewBaseClass(db *DB) *BaseClass {
  return &BaseClass{db}
}

type ChildrenClass1 struct {
     baseClass *BaseClass
}

func NewChildrenClass1(db *DB) *ChildrenClass1 {
  baseClass := NewBaseClass(db)
  return &ChildrenClass1{baseClass}
}

type ChildrenClass2 struct {
     baseClass *BaseClass
}

func NewChildrenClass2(db *DB) *ChildrenClass2 {
  baseClass := NewBaseClass(db)
  return &ChildrenClass2{baseClass}
}

func main(){
  db := NewDB()
  chilrend1 := NewChildrenClass1(db)
  chilrend2 := NewChildrenClass2(db)
}

第二个例子:

type BaseClass struct {
   db *DB
}

func NewBaseClass(db *DB) *BaseClass {
  return &BaseClass{db}
}

type ChildrenClass1 struct {
     baseClass *BaseClass
}

func NewChildrenClass1(baseClass *BaseClass) *ChildrenClass1 {
  return &ChildrenClass1{baseClass}
}

type ChildrenClass2 struct {
     baseClass *BaseClass
}

func NewChildrenClass2(baseClass *BaseClass) *ChildrenClass2 {
  return &ChildrenClass2{baseClass}
}

func main(){
  db := NewDB()
  baseClass := NewBaseClass(db)
  chilrend1 := NewChildrenClass1(baseClass)
  chilrend2 := NewChildrenClass2(baseClass)
}

希望对你有帮助!

英文:

I'm OOP guy, recently I have to work on Golang the language that I haven't done it before. Though I already went through many articles talking about composition, I notice it's a bit tricky to use it properly on Golang

Let's say I have two examples of Golang composition, I don't know which one will be correct, and would it be different between of them? Thank you

First example

type BaseClass struct {
   db *DB
}

func NewBaseClass(db *DB) *BaseClass {
  return &BaseClass{db}
}

type ChildrenClass1 struct {
     baseClass *BaseClass
}

func NewChildrenClass1(db *DB) *ChildrenClass1 {
  baseClass := NewBaseClass(db)
  return &ChildrenClass1{baseClass}
}

type ChildrenClass2 struct {
     baseClass *BaseClass
}

func NewChildrenClass2(db *DB) *ChildrenClass2 {
  baseClass := NewBaseClass(db)
  return &ChildrenClass2{baseClass}
}

func main(){
  db := NewDB()
  chilrend1 := NewChildrenClass1(db)
  chilrend2 := NewChildrenClass2(db)
}

Second example

type BaseClass struct {
   db *DB
}

func NewBaseClass(db *DB) *BaseClass {
  return &BaseClass{db}
}

type ChildrenClass1 struct {
     baseClass *BaseClass
}

func NewChildrenClass1(baseClass *BaseClass) *ChildrenClass1 {
  return &ChildrenClass1{baseClass}
}

type ChildrenClass2 struct {
     baseClass *BaseClass
}

func NewChildrenClass2(baseClass *BaseClass) *ChildrenClass2 {
  return &ChildrenClass2{baseClass}
}

func main(){
  db := NewDB()
  baseClass := NewBaseClass(db)
  chilrend1 := NewChildrenClass1(baseClass)
  chilrend2 := NewChildrenClass2(baseClass)
}

答案1

得分: 3

在Go语言中,你可能不会找到像许多其他面向对象编程语言中那样定义组合或聚合的适当方式。这是因为Go语言没有类、对象、异常和模板。

但是Go语言有结构体。结构体是用户定义的类型。带有方法的结构体类型在其他语言中类似于类。

说到这里,让我们看一些常见的定义,看看我们能做些什么:

组合意味着子对象不能独立于父对象存在。例如:房子(父对象)和房间(子对象)。房间不能独立于房子存在[1]。

聚合,另一方面,意味着子对象可以独立于父对象存在。例如:教室(父对象)和学生(子对象)。删除教室,学生仍然存在[1]。

因此,在聚合和组合中,“实例”“拥有”另一种类型的对象。但是有一个微妙的区别:聚合意味着子对象可以独立于父对象存在。组合意味着子对象不能独立于父对象存在。

到目前为止,这就是我们从组合中所知道的:

  • 子对象不能没有父对象而存在
  • 组合是指将较简单的类型组合成更复杂的类型
  • 当然,我们主要使用它来能够重用代码

回答你的问题:
两个例子看起来都是正确的,但是:

  • 第一个例子更接近组合,因为子对象没有父对象就不存在;
  • 第二个例子更像是聚合,因为如果删除父对象,子对象仍然存在。

我重新编写了你的代码以进行示例:

重新编写的第一个例子

package main

// Lamp结构体在这里是为了替代原始示例中的*DB
type Lamp struct {}

type Room struct {
	Lamps *[]Lamp
}


func NewRoom(l *[]Lamp) *Room {
  return &Room{l}
}

type House1 struct {
	Room *Room
}

func NewHouse1(l *[]Lamp) *House1 {
  r := NewRoom(l)
  return &House1{r}
}

type House2 struct {
	Room *Room
}

func NewHouse2(l *[]Lamp) *House2 {
  r := NewRoom(l)
  return &House2{r}
}

func main(){
  lamps := []Lamp{}
  house1 := NewHouse1(&lamps)
  house2 := NewHouse2(&lamps)
}

重新编写的第二个例子:

package main

type LibraryCard struct {}

type Student struct {
   LibCard *LibraryCard
}

func NewStudent(l *LibraryCard) *Student {
  return &Student{l}
}

type Classroom1 struct {
	Student *Student
}

func NewClassroom1(s *Student) *Classroom1 {
  return &Classroom1{s}
}

type Classroom2 struct {
	Student *Student
}

func NewClassroom2(s *Student) *Classroom2 {
  return &Classroom2{s}
}

func main(){
  lc := new(LibraryCard)
  student := NewStudent(lc)
  classroom1 := NewClassroom1(student)
  classroom2 := NewClassroom2(student)
}
英文:

In Go you probably will not find a proper way to define composition or aggregation as you may find in many other languages that are OOP based. This is just because Go has no classes, no objects, no exceptions, and no templates.

But Go has structs. Structs are user-defined types. Struct types (with methods) serve similar purposes to classes in other languages.

Said that, let's see some common definitions and see what we can do:

Composition implies a relationship where the child cannot exist independent of the parent. Example: House (parent) and Room (child). Rooms don't exist separate to a House[1].

Aggregation, in other hand, implies a relationship where the child can exist independently of the parent. Example: Classroom (parent) and Student (child). Delete the Classroom and the Students still exist[1].

So, in aggregation and composition the "instance" "owns" an object of another type. But there is a subtle difference: Aggregation implies a relationship where the child can exist independently of the parent. Composition implies a relationship where the child cannot exist independent of the parent.

So far, that's what we know now from composition:

  • The child cannot exist without the parent
  • Composition refers to combining simpler types to make more complex ones
  • And of course, we mainly use it to be able to reuse code

Answer to your question:
Both looks correct but,

  • The first example is closer to a composition, because the child will not exist without the parent;
  • The second example is more like an aggregation, because if you remove the parent, the child will keep existing.

I re-wrote your code on an attempt to exemplify it:

First example re-written

package main

//Lamp struct is here to suppress the *DB that was in the original example
type Lamp struct {}

type Room struct {
	Lamps *[]Lamp
}


func NewRoom(l *[]Lamp) *Room {
  return &Room{l}
}

type House1 struct {
	Room *Room
}

func NewHouse1(l *[]Lamp) *House1 {
  r := NewRoom(l)
  return &House1{r}
}

type House2 struct {
	Room *Room
}

func NewHouse2(l *[]Lamp) *House2 {
  r := NewRoom(l)
  return &House2{r}
}

func main(){
  lamps := []Lamp{}
  house1 := NewHouse1(&lamps)
  house2 := NewHouse2(&lamps)
}

Second example re-written:

package main

type LibraryCard struct {}

type Student struct {
   LibCard *LibraryCard
}

func NewStudent(l *LibraryCard) *Student {
  return &Student{l}
}

type Classroom1 struct {
	Student *Student
}

func NewClassroom1(s *Student) *Classroom1 {
  return &Classroom1{s}
}

type Classroom2 struct {
	Student *Student
}

func NewClassroom2(s *Student) *Classroom2 {
  return &Classroom2{s}
}

func main(){
  lc := new(LibraryCard)
  student := NewStudent(lc)
  classroom1 := NewClassroom1(student)
  classroom2 := NewClassroom2(student)
}

huangapple
  • 本文由 发表于 2021年11月24日 18:02:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/70094078.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定