Is Go An Object Oriented Language?
To truly understand what it means to be ‘object-oriented’ you need to look back at the origination of the concept. The first object oriented language,simula,emerged in the 1960s. It introduced objects,classes,inheritance and subclasses,virtual methods,coroutines,and a lot more. Perhaps most importantly,it introduced a paradigm shift of thinking of data and logic as completely independent. While you many not be familiar with Simula,you are no doubt familiar with languages that refer to it as their inspiration including Java,C++,C# & Smalltalk,which in turn have been the inspiration for Objective C,Python,Ruby,Javascript,Scala,PHP,Perl… a veritable list of nearly all popular languages in use today. This shift in thinking has taken over,so much so that most programmers alive today have never written code any other way. Since a standard definition doesn’t exist,for the purpose of our discussion we will provide one. Rather than structure programs as code and data,an object-oriented system integrates the two using the concept of an “object”. An object is an abstract data type that has state (data) and behavior (code). Perhaps as a consequence of the initial implementation having inheritance and polymorphism,a feature virtually all of the derivatives also adopted,definitions of object oriented programming typically also include those features as requirements. We will look at how Go does objects,polymorphism and inheritance and allow you to make your own conclusion. Objects In GoGo doesn’t have a something called ‘object’,but ‘object’ is only a word that connotes a meaning. It’s the meaning that matters,not the term itself. While Go doesn’t have a type called ‘object’ it does have a type that matches the same definition of a data structure that integrates both code and behavior. In Go this is called a ‘struct’. A ‘struct’ is a kind of type which contains named fields and methods. Let’s use an example to illustrate this: 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()) } There’s a lot we could talk about here. It’s probably best to walk through the code line by line and explain what is happening. The first block is defining a new type called a ‘rect’. This is a struct type. The struct has two fields,both of which are type int. The next block is defining a method bound to this struct. This is accomplished by defining a function and attaching (binding) it to a rect. Technically,in our example it is really attached to a pointer to a rect. While the method is bound to the type,Go requires us to have a value of that type to make the call,even if the value is the zero value for that type (in the case of a struct the zero value is nil). The final block is our main function. The first line creates a value of type rect. There are other syntaxes we could use to do this,but this is the most idiomatic way. The second line prints to the output the result of calling the area function on our rect ‘r’. To me this feels very much like an object. I am able to create a structured data type and then define methods that interact with that specific data. What haven’t we done? In most object oriented languages we would be using the ‘class’ keyword to define our objects. When using inheritance it is a good practice to define interfaces for those classes. In doing so we would be defining an inheritance hierarchy tree (in the case of single inheritance). An additional thing worth noting is that in Go any named type can have methods,not only structs. For example I can define a new type ‘Counter’ which is of type int and define methods on it. See an example athttp://play.golang.org/p/LGB-2j707c Inheritance And PolymorphismThere are a few different approaches to defining the relationships between objects. While they differ quite a bit from each other,all share a common purpose as a mechanism for code reuse.
Single & Multiple InheritanceInheritance is when an object is based on another object,using the same implementation. Two different implementations of inheritance exist. The fundamental distinction between them is whether an object can inherit from a single object or from multiple objects. This is a seemingly small distinction,but with large implications. The hierarchy in single inheritance is a tree,while in multiple inheritance it is a lattice. Single inheritance languages include PHP,C#,Java and Ruby. Multiple inheritance languages include Perl,Python and C++.Subtyping (Polymorphism)In some languages subtyping and inheritance are so interwoven that this may seem redundant to the previous section if your particular perspective comes from a language where they are tightly coupled. Subtyping establishes an is-a relationship,while inheritance only reuses implementation. Subtyping defines a semantic relationship between two (or more) objects. Inheritance only defines a syntactic relationship.Object CompositionObject composition is where one object is defined by including other objects. Rather than inheriting from them,the object contains them. Unlike the is-a relationship of subtyping,object composition defines a has-a relationship.Inheritance In GoGo is intentionally designed without any inheritance at all. This does not mean that objects (struct values) do not have relationships,instead the Go authors have chosen to use a alternative mechanism to connote relationships. To many encountering Go for the first time this decision may appear as if it cripples Go. In reality it is one of the nicest properties of Go and it resolves decade old issues and arguments around inheritance.Inheritance Is Best Left OutThe following really drives home this point. It comes from a JavaWorld article titledwhy extends is evil:
Polymorphism & Composition In GoInstead of inheritance Go strictly follows thecomposition over inheritance principle. Go accomplishes this through both subtyping (is-a) and object composition (has-a) relationships between structs and interfaces.Object Composition In GoThe mechanism Go uses to implement the principle of object composition is called embedded types. Go permits you to embed a struct within a struct giving them a has-a relationship.An good example of this would be the relationship between a Person and an Address. 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("I’m at",p.Address.Number,p.Address.Street,p.Address.City,p.Address.State,p.Address.Zip) } func main() { p := Person{ Name: "Steve",Address: Address{ Number: "13",Street: "Main",City: "Gotham",State: "NY",Zip: "01313",},} p.Talk() p.Location() } Output> Hi,my name is Steve > I’m at 13 Main Gotham NY 01313 http://play.golang.org/p/LigPIVT2mf Important things to realize from this example is that Address remains a distinct entity,while existing within the Person. In the main function we demonstrate that you can set the p.Address field to an address,or simply set the fields by accessing them via dot notation. Pseudo Subtyping In GoAUTHORS NOTE:In the first version of this post it made the incorrect claim that Go supports the is-a relationship via Anonymous fields. In reality Anonymous fields appear to be an is-a relationship by exposing embedded methods and properties as if they existed on the outer struct. This falls short of being an is-a relationship for reasons now provided below. Go does have support for is-a relationships via interfaces,covered below. The current version of this post refers to Anonymous fields as a pseudo is-a relationship because it looks and behaves in some ways like subtyping,but isn’t. The pseudo is-a relationship works in a similar and intuitive way. To extend our example above. Let’s use the following statement. A Person can talk. A Citizen is a Person therefore a citizen can Talk. This code depends on and adds to the code in the example above. 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() } http://play.golang.org/p/eCEpLkQPR3We accomplish this pseudo is-a relationship in go using what is called an Anonymous field. In our example Person is an anonymous field of Citizen. Only the type is given,not the field name. It assumes all of the properties and methods of a Person and is free to use them or promote it’s own. Promoting Methods Of Anonymous FieldsAn example of this would be that citizens Talk just as People do,but in a different way. For this we simply define Talk for *Citizen,and run the same main function as defined above. Now instead of calling *Person.Talk(),*Citizen.Talk() will be called instead. func (c *Citizen) Talk() { fmt.Println("Hello,c.Name,"and I'm from",c.Country) } > Hello,my name is Steve and I'm from America > Steve is a citizen of Americahttp://play.golang.org/p/jafbVPv5H9 Why Anonymous Fields Are Not Proper SubtypingThere are two distinct reasons why this cannot be considered proper subtyping.1. The Anonymous Fields Are Still Accessible As If They Were Embedded.This isn’t necessarily a bad thing. One of the issues with multiple inheritance is that languages are often non obvious and sometimes even ambiguous as to which methods are used when identical methods exist on more than one parent class. With Go you can always access the individual methods through a property which has the same name as the type. In reality when you are using Anonymous fields,Go is creating an accessor with the same name as your type. In our example from above the following code would work: func main() { c := Citizen{} c.Name = "Steve" c.Country = "America" c.Talk() // <- Notice both are accessible c.Person.Talk() // <- Notice both are accessible c.Nationality() } Output2. True Subtyping Becomes The Parent http://play.golang.org/p/lvEjaMQ25D As expected,this fails. In our code a Citizen isn’t a Person,even though they share many of the same properties,they are viewed as distinct types. However if we add an interface called Human and use that as the input for our SpeakTo function it will all work as intended. 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) } > Hi,my name is Dave > Hi,my name is Stevehttp://play.golang.org/p/ifcP2mAOnf There are two critical points to make about subtyping in Go:
Go,Object-Oriented Programming Without Objects Or InheritanceAs we have demonstrated here,the fundamental concepts of object orientation are alive and well in Go in spite of some terminology differences. The terminology differences are essential as the mechanisms used are in fact different from most object oriented languages.Go utilizes structs as the union of data and logic. Through composition,has-a relationships can be established between Structs to minimize code repetition while staying clear of the brittle mess that is inheritance. Go uses interfaces to establish is-a relationships between types without unnecessary and counteractive declarations. Welcome to the new ‘object’-less OO programming model. DiscussionJoin the discussion onhacker newsandReddit - GolangFurther Reading
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |