📜 ⬆️ ⬇️

Is Go the language of OOP?

Object-oriented design is the roman numerals of computing.
- Rob Pike, by Go.

image

I bring to your attention the free translation of the note “Is Go An Object Oriented Language?” By Steve Francia , in which the author clearly describes the features of using the OO paradigm in Go. Immediately I warn you that because of the properties of the original material, most of the text had to be reformulated completely, somewhere to add your own. The check box for the translation did not.

1. Introduction


So what does it mean to be "object-oriented"? Let us turn to the history of the emergence of the concept of the PLO, try to understand.
The first object-oriented language, Simula , appeared on the horizon in the 60s. He introduced the concepts of objects, classes, the concepts of inheritance and descendant classes, virtual methods, coroutines, and more. To the campaign, the most valuable contribution was the data abstraction paradigm.
')
You may not be familiar with Simula, but, no doubt, you know for sure some of the languages ​​for which he became an inspiration - Java, C ++, C # and Smalltalk, which later, in turn, greatly influenced Objective-C, Python , Ruby, Javascript, Scala, PHP, Perl ... the full list contains almost all popular modern languages. This paradigm has become so firmly established in our life that most modern programmers have never thought otherwise.

Since there is no generally accepted definition of OOP, to continue the discussion, we formulate our own.

Instead of explicitly separating code and data in the text of a program, an object-oriented system combines them using the concept of an “object”. An object is an abstract data type, including state (data) and behavior (code).

Since the initial implementation had inheritance and polymorphism mechanisms that were adopted in almost all derived languages, object-oriented programming definitions usually include them as a necessary condition.

Next, we will look at how objects, polymorphism and inheritance are used in Go, after which it will be easier to answer the question posed.

2. Objects?


In Go, there is nothing named object , although it doesn't matter. Let the object type not occur, but there is a type that falls under the definition of an object-oriented approach — data structures that include both state and behavior, they are denoted as struct . A struct is a type containing named fields and methods.

For clarity, I will give an example:
 type rect struct { width int height int } func (r *rect) area() int { return r.width * r.height } func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) } 

The first block defines a new type of rect structural type containing 2 integer fields. The next block defines a method on this structure by defining the area function and attaching it to the type rect . More precisely, in fact, the function is attached to the pointer type to rect .
The last block is the entry point of the program; this is the main function. The first line creates a new instance of rect (the chosen way to create an instance — through a compound literal — is most convenient in Go). The second line is responsible for displaying the results of calling the area function on the value of r .

Personally, it all reminded me a lot of work with objects. I can create a structured data type and define methods for working with some of them.
Is there something else missing? Yes, in most object-oriented languages, classes with support for inheritance are used to describe objects, and it is good practice to define interfaces for these classes and thereby define a class hierarchy tree (in the case of simple inheritance).

2. Inheritance and polymorphism


There are several different approaches to defining the relationship between objects. And although there are some differences, they are all used with almost one purpose - for the sake of code reuse.

2.1. Simple and multiple inheritance
Inheritance is a language mechanism that allows you to describe a new class based on an existing (base) class. There are two types of inheritance, based on the number of base classes. The fundamental difference can be felt only by evaluating the consequences of applying multiple inheritance: the hierarchy of simple inheritance (single inheritance) is a tree, while multiple inheritance generates a grid . Languages ​​with extremely simple support include PHP, C #, Java, and Ruby, and languages ​​with multiple inheritance support include Perl, Python, and C ++.

2.2. Subtype polymorphism
In some languages, the concepts of subtypes and inheritance are so closely intertwined that the difference between them is barely noticeable. In fact, subtypes define semantic relationships between two or more objects, thereby forming an is-a relationship. That is, type A is a subtype of B when specification A follows from specification B and any object (or class) satisfying specification A also satisfies specification B. While simple inheritance only reuses the implementation, thereby providing syntactic sugar, but no more.

It is necessary to clearly distinguish inheritance through implementation and “inheritance” through polymorphism of subtypes, which will be clear from the text below.

2.3. Composition
In composition, one object is determined by including other objects in it, that is, instead of inheritance, it simply contains them. This type of relationship is called has-a and the objects included are subject to the membership rules.

3. Is there any inheritance in Go?


Go as designed was designed without inheritance in the usual sense of the word. It does not mean at all that the objects (structures) have no relationship, just the authors of the language have chosen a different mechanism for designating such. For many beginners to write on Go, this solution may seem to be a serious flaw, however, in reality, this is one of the most pleasant features of the language and it solves many problems, at the same time closing disputes around inheritance - whose age is tens of years once and for all.

4. “Simple inheritance is better to throw out”


Next, I'll provide a snippet from a JavaWorld article - “Why extends is evil” :
The book of the gang of four on design patterns discusses in detail the replacement of inheritance through the implementation (extends) to inheritance through the interfaces (implements).

I once attended a Java user group meeting where James Gosling (creator of Java) was invited to give a talk. During a memorable question and answer session, someone asked him: “If you could redo Java, what would you change?”. “I’d throw out classes,” he replied. After the laughter in the room died down, he explained that the real problem lies not in the classes as a matter of fact, but in the inheritance through the implementation (the relation extends). Inheritance through interfaces (the implements relation) is preferable; inheritance through implementation should be avoided where possible.

5. Object Relations in Go


5.1. Type Composition
Instead of the usual inheritance in Go, the composition principle is strictly used instead of inheritance, and relations between structures and interfaces are built on the principles of is-a and has-a. The object composition mechanism used here is represented by inline types, so Go allows you to embed a structure in a structure, creating a has-a relationship. A good example is the relationship between the Person and Address types in the code below:
 type Person struct { Name string Address Address } type Address struct { Number string Street string City string State string Zip string } func (p *Person) Talk() { fmt.Println("Hi, my name is", p.Name) } func (p *Person) Location() { fmt.Println("Im at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip) } func main() { p := Person{Name: "Steve"} p.Address = Address{ Number: "13", Street: "Main" } p.Address.City = "Gotham" p.Address.State = "NY" p.Address.Zip = "01313" p.Talk() p.Location() } 

Result:
 > Hi, my name is Steve > Im at 13 Main Gotham NY 01313 

play.golang.org/p/5TVBDR7AYo

In this example, the important thing is that Address remains a separate entity, being inside Person . The main function demonstrates how to assign the p.Address object to the p.Address object and, referring to its fields through the dot, initialize it.

5.2. Subtype polymorphism imitation

Note by the author. The first version of this article incorrectly explained the implementation of the is-a relationship through anonymous structure fields. In fact, it only resembles the is-a relationship, since the included methods and properties become visible from the outside, as if they existed in the enclosing structure. This approach is not is-a for the reasons described below, however, Go has support for is-a and is achieved through interfaces . The current version of the article refers to anonymous fields as i-a imitation, since they are similar to the mechanism of polymorphism, but no more. //

Imitation of the is-a relationship works in a similar way. Let the person ( Person ) know how to speak. Citizen ( Citizen ) is a person ( Person ), and therefore also able to speak ( Talk ). Expand the previous example with this in mind.
 type Citizen struct { Country string Person //     } func (c *Citizen) Nationality() { fmt.Println(c.Name, "is a citizen of", c.Country) } func main() { c := Citizen{} c.Name = "Steve" c.Country = "America" c.Talk() c.Nationality() } 

Result:
 > Hi, my name is Steve > Steve is a citizen of America 

play.golang.org/p/eCEpLkQPR3

We set up an is-a relationship simulation using an anonymous structure field, in this case, a Person field (only type is indicated) for Citizen . The Citizen type has acquired all the properties and methods of the Person type and has the ability to add or override some of these properties and methods with its own. For example, let the city dwellers respond a little differently:
 func (c *Citizen) Talk() { fmt.Println("Hello, my name is", c.Name, "and Im from", c.Country) } 

Result:
 > Hello, my name is Steve and Im from America > Steve is a citizen of America 

play.golang.org/p/jafbVPv5H9

Note that now in main the *Citizen.Talk() method is called instead of *Person.Talk() .

6. Why anonymous fields do not give polymorphism


There are two reasons.

6.1. Access remains to the individual fields of each of the built-in types.
Well, in fact, this is not so bad, since with multiple “inheritance” it becomes not obvious which method from the parent classes will be called. When using an anonymous field, Go creates an auxiliary field, duplicating the type name, so you can always refer to the individual methods of all anonymous fields, that is, the base classes in our imitation of the inheritance mechanism. Example:
 func main() { c := Citizen{} c.Name = "Steve" c.Country = "America" c.Talk() // <-   c.Person.Talk() // <-   c.Nationality() } 

Result:
 > Hello, my name is Steve and Im from America > Hi, my name is Steve > Steve is a citizen of America 


6.2. Child type does not become ancestor type
If polymorphism were true, an anonymous field would cause an inclusive type to become an inclusive type, but in Go and inclusive and until the end remain separate. It is better to see once, we give an example:
 type A struct { } type B struct { A // B is-a A } func save(A) { // xxx } func main() { b := &B{} save(b) // OOOPS! b IS NOT A } 

Result:
 > prog.go:17: cannot use b (type *B) as type A in function argument > [process exited with non-zero status] 

play.golang.org/p/EmodogIiQU

This example is offered in this commentary with Hacker News. Thanks, Optymizer.

7. Real Subtype Polymorphism


The interfaces in Go are pretty unique in nature. In this section, we will focus on using interfaces to implement polymorphism, which is not their first priority.

As I wrote earlier, polymorphism is an is-a relationship. In Go, each type is separate and nothing can disguise itself as another type just like that, but both types can fit the same interface. Interfaces can be passed as parameters to functions and methods, and this will allow us to establish the present is-a relationship between types.

The fact that the type conforms to the interface is checked automatically by comparing the sets of methods required by the interface and the set of available methods of the type being checked. In other words, such an attitude can be described as “if something can do it, then it can be used here,” that is, there is the principle of duck typing .

Returning to the previous example, we will add a new SpeakTo function and in main we will try to apply it one by one to the Citizen and Person instances.
 func SpeakTo(p *Person) { p.Talk() } func main() { p := &Person{Name: "Dave"} c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"} SpeakTo(p) SpeakTo(c) } 

Result:
 > Running it will result in > prog.go:48: cannot use c (type *Citizen) as type *Person in function argument > [process exited with non-zero status] 

play.golang.org/p/fkKz0FkaEk

As expected, the design is simply wrong. In this case, Citizen not Person , even though they have common properties. However, we will add the Human interface, we will make it the accepted type of the SpeakTo function and now, everything went according to plan.
 type Human interface { Talk() } func SpeakTo(h Human) { h.Talk() } func main() { p := &Person{Name: "Dave"} c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"} SpeakTo(p) SpeakTo(c) } 

Result:
 > Hi, my name is Dave > Hi, my name is Steve 

play.golang.org/p/vs92w57c5-

Summing up, you can make two important points about polymorphism in Go:


8. Results


As you can see in the examples above, the fundamental principles of the object-oriented approach are relevant and are successfully applied in Go. There are differences in terminology, since several different mechanisms are used than in other classical OOP languages. Thus, to combine state and behavior in one entity, structures with specified (attached) methods are used. To denote the has-a relationship between types, composition is used, thereby minimizing the number of repetitions in the code and saving us from the mess with classical inheritance. To establish is-a relationships between types, Go uses interfaces, without unnecessary words and counterproductive descriptions.

So, meet the new model of object-oriented programming - without objects!

~ translation & adaptation by Xlab.
See also: “Going code reuse with an example”
By the way, just in case I will give this quote here:
I can not tell you what to do. C ++ in mind.
- Alan Kay

Source: https://habr.com/ru/post/225907/


All Articles