Go Language Object Oriented Programming: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(111 intermediate revisions by the same user not shown)
Line 5: Line 5:
* [[Go_Language#Object_Oriented_Programming|Go Language]]
* [[Go_Language#Object_Oriented_Programming|Go Language]]
* [[Go Functions]]
* [[Go Functions]]
* [[Go_Interfaces#Overview|Go Interfaces]]
* [[Object-Oriented_Programming#Overview|Object-Oriented Programming]]
* [[Object-Oriented_Programming#Overview|Object-Oriented Programming]]


Line 10: Line 11:
Go is an object-oriented language, but the object orientation programming model is relatively simple, compared with other object-oriented languages. It has been said about Go that is "weakly" object-oriented. Go does not use the term "[[Object-Oriented_Programming#Class|class]]", there is no <code>class</code> keyword in the language. However, Go allows associating data with methods, which what a class in other programming languages really is.  
Go is an object-oriented language, but the object orientation programming model is relatively simple, compared with other object-oriented languages. It has been said about Go that is "weakly" object-oriented. Go does not use the term "[[Object-Oriented_Programming#Class|class]]", there is no <code>class</code> keyword in the language. However, Go allows associating data with methods, which what a class in other programming languages really is.  


Go provides syntax to associate an arbitrary [[Go_Language#Local_Type|local type]] with arbitrary functions, turning them into [[#Method|methods]] of that type. The local type can be a [[Go_Language#Type_Alias|type alias]], a <code>[[Go_Structs#Overview|struct]]</code> or a <font color=darkkhaki>function</font>. In this context, "local" means a type that is defined in the same package as the function. That is why a programmer cannot add new methods to built-in types.
Go provides syntax to associate an arbitrary [[Go_Language#Local_Type|local type]] with arbitrary functions, turning them into [[#Method|methods]] of that type. The local type can be a [[Go_Language#Type_Alias|type alias]], a <code>[[Go_Structs#Overview|struct]]</code> or a [[#Functions_as_Receiver_Types|function]]. In this context, "local" means a type that is defined in the same package as the function. That is why a programmer cannot add new methods to built-in types.


The most common pattern for implementing object-orientation is to use a <code>struct</code> as data encapsulation mechanism, optionally declaring fields as [[Go_Packages#Unexported_Members|unexported]], thus preventing direct access to them from outside the package, and then associating the struct with functions, by declaring the struct type as the [[#Receiver_Type|receiver type]] for the function and the functions into [[#Method|methods]] of the type. This construct is the closest equivalent to a class in other programming languages. The standard dot notation is then used to call a method on the instance of the receiver type.
<span id='Structs_as_Receiver_Types'></span><span id='Structs_as_Receiver_Type'></span>The most common pattern for implementing object-orientation is to use a <code>struct</code> as data encapsulation mechanism, optionally declaring fields as [[Go_Packages#Unexported_Members|unexported]], thus preventing direct access to them from outside the package, and then associating the struct with functions, by declaring the struct type as the [[#Receiver_Type|receiver type]] for those functions. The functions become [[#Method|methods]] of the type. This construct is the closest equivalent to a class in other programming languages. The standard dot notation is then used to call a method on the instance of the receiver type.


<span id='Polymorphism'></span>Go does not have formal [[Object-Oriented_Programming#Inheritance|inheritance]], constructors or generics. Inheritance can be replaced to a certain extent by composition, [[Go_Structs#Embedded_Fields|struct field embedding]] and [[Go_Interfaces#Overview|interfaces]], which support code reuse and [[#Polymorphism|polymorphism]].
Go does NOT offer inheritance, overriding and polymorphism in the language syntax the same way other OOP languages do. [[#Go_Does_Not_Have_Type_Inheritance|Type inheritance is NOT available]]. Behavior inheritance can be implemented with [[Go_Interfaces#Overview|interfaces]]. Polymorphism can be implemented with a combination of [[Go_Structs_Embedded_Fields#Overview|struct field embedding]] and [[Go_Interfaces#Overview|interfaces]].


=<span id='Method'></span>Methods=
=<span id='Method'></span>Methods=
 
{{Internal|Go_Methods#Overview|Go Methods}}
A '''method''' of a type is a function that was associated with the type, by declaring the type to be a [[#Receiver_Type|receiver type]] for that function, with the special syntax described below. If a method name starts with an uppercase character it is automatically [[Go_Language#Exported_Identifiers|exported]].
 
=Receiver Type=
 
A '''receiver type''' for a function is a type that is associated with the function with dedicated syntax:
 
<syntaxhighlight lang='go'>
func (<receiver_type_parameter_name> <receiver_type|receiver_type_pointer>) function_name(...) ... {
  ...
}
</syntaxhighlight>
 
The receiver type syntax is the logical equivalent of the <code>[[Python_Language_OOP#The_self_Parameter|self]]</code> function parameter in Python and the implicit method parameter <code>this</code> in Java.
 
Declaring a receiver type turns the function into a [[#Method|method]] of the type. Depending on whether we want to be able to modify the receiver type instance in the method or not, the receiver type can be declared as a [[#Pointer_Receiver_Type|pointer receiver type]] or a [[#Value_Receiver_Type|value receiver type]].
 
<span id='Implicit_Parameter'></span>Regardless of whether we use a pointer or a value receiver type, the function gets an '''implicit parameter''', which is a pointer to the instance in case of a pointer receiver type, and a value of the type in case of a value recover type:
 
<syntaxhighlight lang='go'>
func function_name(<implicit_parameter> <receiver_type|receiver_type_pointer>, ...) ... {
  ...
}
</syntaxhighlight>
 
==Value Receiver Type==
The syntax to declare a value receiver type for a function is:
<syntaxhighlight lang='go'>
func (<receiver_type_parameter_name> <receiver_type>) <function_name>(...) ... {
  ...
}
</syntaxhighlight>
Example:
<syntaxhighlight lang='go'>
type Color struct {
  color string
}
 
func (c Color) SomeMethod() string {
  return "dark " + c.color
}
</syntaxhighlight>
 
In the example above, <code>Color</code> is the receiver type, and <code>c</code> is the variable that refers to the particular receiver type value the method was invoked on. The invocation target is passed as '''value''' inside the function.
 
The method is invoked on the receiver type instances with the usual dot notation syntax:
<syntaxhighlight lang='go'>
color := Color{"blue"}
result := color.SomeMethod() // the method will return "dark blue"
</syntaxhighlight>
 
The invocation copies the <code>color</code> value into in the <code>c</code> variable inside the function, making <code>c</code> an [[#Implicit_Parameter|implicit parameter]] of the method. There is no formal <code>c</code> function parameter in the signature, yet the method body has access to <code>c</code>, which behaves similarly to any other function parameter.
 
Because the receiver instance is passed by value, hence a copy of that value is passed inside the function, the method cannot modify the invocation target instance. A [[#Pointer_Receiver_Type|pointer receiver type]] must be used if instead to modify the target.
 
==Pointer Receiver Type==
The syntax to declare a pointer receiver type for a function is:
 
<syntaxhighlight lang='go'>
func (<receiver_type_parameter_name> *<receiver_type>) <function_name>(...) ... {
  ...
}
</syntaxhighlight>
 
Example:
<syntaxhighlight lang='go'>
func (c *Color) SomeOtherMethod() {
  c.color = "dark " + c.color
  // (*c).color = "dark" + (*c).color is an equivalent, longer syntax. The compiler can handle the simpler syntax
}
 
...
c := Color{"blue"}
c.SomeOtherMethod() // equivalent with (&c). c.SomeOtherMethod()
fmt.Println(c) // will print "{dark blue}"
</syntaxhighlight>
 
Note that the Go compiler knows how to use the dot notation with both values and pointers, so in the above example, <code>(*c).color</code> and <code>c.color</code> are equivalent. In other words, there is no need to dereference the pointer inside the method. The compiler knows to dereference the pointer automatically. The reverse is also true, there is no need to reference the instance variable when used to invoke a method on it. Even though <code>(&c).Darken()</code> would work in the example above, there is no need for it, <code>c.Darken()</code> works equally well because the compiler knows what we mean.
 
The simpler syntax should be preferred.
 
===Mixing Value and Pointer Receiver Types===
The dot notation works with both values and pointers. The compiler knows how to transparently handle these two cases.
 
When using pointer receivers, it is good programming practice to have all method use pointer receivers, or none of them use pointer receivers. If we mix value and pointer receivers, the IDE would detect that as static check warning: "Struct Color has methods on both value and pointer receivers. Such usage is not recommended by the Go Documentation."
 
=<span id='Structs_as_Receiver_Type='></span>Structs as Receiver Types=
 
It is very common to use [[Go_Structs#Overview|structs]] as [[#Receiver_Type|receiver types]]. You can take a struct, define it as a type, associate methods with that type, by declaring the type as receiver type on functions, and you would get what you normally think as a class in other programming languages.
 
=Functions as Receiver Types=
 
<font color=darkkhaki>TODO, example here: https://go.dev/blog/error-handling-and-go#simplifying-repetitive-error-handling</font>


=Encapsulation=
=Encapsulation=


Encapsulation in this context is giving access to data, but intermediated by the public methods that are defined with the data. This way, access to data is controlled.
Encapsulation in this context is giving access to private data, such as package-internal data or private struct fields, via public methods. This way, access to data is controlled.
 
Encapsulation can be implemented with [[Go_Language_Modularization#Encapsulation|package]] data.


Encapsulation can also be implemented with structs, where the [[Go_Structs#Encapsulation_and_Private_Fields|"private" fields]] are declared using identifiers that start with lower cap letters, so they are not visible outside the package. Controlled access to them is given via accessor and mutator methods, which are functions that have been declared to use the struct as a receiver type:
Encapsulation can be implemented with [[Go_Language_Modularization#Encapsulation|package]] data and with structs, where the [[Go_Structs#Encapsulation_and_Private_Fields|"private" fields]] are declared using identifiers that start with lower cap letters, so they are not visible outside the package. Controlled access to them is given via accessor and mutator methods, or [[Go_Style#Getters_and_Setters|getters and setters]], which are functions that have been declared to use the struct as a receiver type:


<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
Line 128: Line 35:
}
}


func (c *Color) GetColor() string {
func (c *Color) Color() string {
return (*c).color
return (*c).color
}
}
Line 140: Line 47:
var c Color
var c Color
c.Init("blue")
c.Init("blue")
fmt.Println(c.GetColor()) // prints blue
fmt.Println(c.Color()) // prints blue
c.SetColor("red")
c.SetColor("red")
fmt.Println(c.GetColor()) // prints red
fmt.Println(c.Color()) // prints red
</syntaxhighlight>
</syntaxhighlight>
=Polymorphism=
Polymorphism is available in Go, but it is implemented a bit differently than in other object-oriented languages, in that it does not imply [[Go_Language_Object_Oriented_Programming#Inheritance|inheritance]], which is not available in Go. Polymorphism in Go is implemented using [[Go_Interfaces#Overview|interfaces]].
For a generic discussion on polymorphism, see: {{Internal|Object-Oriented_Programming#Polymorphism|Object-Oriented Programming &#124; Polymorphism}}
=<span id='Interface'></span>Interfaces=
=<span id='Interface'></span>Interfaces=
{{Internal|Go_Interfaces#Internal|Go Interfaces}}
{{Internal|Go_Interfaces#Internal|Go Interfaces}}
=Inheritance=


=Overriding=
==Go Does Not Have Type Inheritance==
=Inheritance=
 
=TO DEPLETE=
Go does not have type inheritance in the same sense a fully featured object-oriented programming language has.


<font color=darkkhaki>
In Java or Python, a type can be at the same time other type. For example, a Dog instance is at the same type an Animal instance, where "Dog" and "Animal" are two distinct types in the type system, and "Dog" inherits "Animal". The language syntax supports this model.


Read and merge into document:
In Go, one instance is of one type, and one type alone. The instance cannot be at the same time an ancestor type and a descendant type.


* https://go.dev/doc/faq#Is_Go_an_object-oriented_language
==Behavior Inheritance==


</font>
Go supports inheritance of behavior, by allowing interface inheritance. <font color=darkkhaki>Explain this better.</font>

Latest revision as of 02:26, 4 November 2024

External

Internal

Overview

Go is an object-oriented language, but the object orientation programming model is relatively simple, compared with other object-oriented languages. It has been said about Go that is "weakly" object-oriented. Go does not use the term "class", there is no class keyword in the language. However, Go allows associating data with methods, which what a class in other programming languages really is.

Go provides syntax to associate an arbitrary local type with arbitrary functions, turning them into methods of that type. The local type can be a type alias, a struct or a function. In this context, "local" means a type that is defined in the same package as the function. That is why a programmer cannot add new methods to built-in types.

The most common pattern for implementing object-orientation is to use a struct as data encapsulation mechanism, optionally declaring fields as unexported, thus preventing direct access to them from outside the package, and then associating the struct with functions, by declaring the struct type as the receiver type for those functions. The functions become methods of the type. This construct is the closest equivalent to a class in other programming languages. The standard dot notation is then used to call a method on the instance of the receiver type.

Go does NOT offer inheritance, overriding and polymorphism in the language syntax the same way other OOP languages do. Type inheritance is NOT available. Behavior inheritance can be implemented with interfaces. Polymorphism can be implemented with a combination of struct field embedding and interfaces.

Methods

Go Methods

Encapsulation

Encapsulation in this context is giving access to private data, such as package-internal data or private struct fields, via public methods. This way, access to data is controlled.

Encapsulation can be implemented with package data and with structs, where the "private" fields are declared using identifiers that start with lower cap letters, so they are not visible outside the package. Controlled access to them is given via accessor and mutator methods, or getters and setters, which are functions that have been declared to use the struct as a receiver type:

type Color struct {
	color string
}

func (c *Color) Init(color string) {
	c.SetColor(color)
}

func (c *Color) Color() string {
	return (*c).color
}

func (c *Color) SetColor(color string) {
	(*c).color = color
}

...

var c Color
c.Init("blue")
fmt.Println(c.Color()) // prints blue
c.SetColor("red")
fmt.Println(c.Color()) // prints red

Interfaces

Go Interfaces

Inheritance

Go Does Not Have Type Inheritance

Go does not have type inheritance in the same sense a fully featured object-oriented programming language has.

In Java or Python, a type can be at the same time other type. For example, a Dog instance is at the same type an Animal instance, where "Dog" and "Animal" are two distinct types in the type system, and "Dog" inherits "Animal". The language syntax supports this model.

In Go, one instance is of one type, and one type alone. The instance cannot be at the same time an ancestor type and a descendant type.

Behavior Inheritance

Go supports inheritance of behavior, by allowing interface inheritance. Explain this better.