Go Object Oriented Design

Go hastypes and valuesrather than classes and objects. So can a language without classes or objects be object-oriented?

While Go may not fit the typical mold of an OOP language, it does  provide many of the same features, albeit in a slightly different way:

  • methodson any typewe define, with no boxing or unboxing
  • automatic message delegation viaembedding
  • polymorphism viainterfaces
  • namespacing viaexports

There is no inheritance in Go, so leave those is-a relationships at the door. To write Go, we need to think about OO design in terms of composition.

"Use of classical inheritance is always optional; every problem that it solves can be solved another way." - Sandi Metz

Composition by Example

Having recently read through Practical Object-Oriented Programming in Ruby, I decided to translate the examples to Go.

Chapter 6 presents a simple problem. A mechanic needs to know which  spare parts to bring on a bike trip, depending on which bikes have been  rented. The problem can be solved using classical inheritance, where MountainBike and RoadBike are specializations of a Bicycle base class. Chapter 8 reworks the same example touse composition instead. I‘m quite happy with how well this version translated to Go. Let‘s take a look.

Packages

package main 
import "fmt"

Packages provide a namespace. The main() function of the main package is where a program begins. The fmt package provides string formatting functions.

Types

type Part struct {     
    Name        string     
    Description string     
    NeedsSpare  bool 
}

We define a new type named Part. This structure is very much like a C struct.

type Parts []Part

The Parts type is a slice of Part values. Slices are variable-length arrays, which are more common in Go than their fixed-length brethren.

Methods

We can declare methods on any user-defined type, so Parts can haveall the behavior of slices, plus our own custom behavior.

func (parts Parts) Spares() (spares Parts) {     
    for _, part := range parts {         
        if part.NeedsSpare {             
            spares = append(spares, part)         
        }
    }
    return spares 
}

A method declaration in Go is just like a function, except it has anexplicit receiver, declared immediately after func. This function also takes advantage of named return variables, pre-initializing spares for us.

The method body is fairly straightforward. We iterate over parts, ignoring the index position (_), filtering the parts to return. The append builtin may need to allocate and return a larger slice, as we didn‘t preallocate its capacity.

This code isn‘t nearly as elegant as select in Ruby. A functional filter is possible in Go, but it isn‘t builtin.

Embedding

type Bicycle struct {     
    Size string     
    Parts 
}

Bicycle is composed of a Size and Parts. By not specifying a named field for Parts, we make use of embedding. This provides automatic delegation with no further ceremony, eg. bike.Spares() and bike.Parts.Spares() are equivalent.

If we were to add a Spares() method to Bicycle, it would take precedence, but we could still reference the embedded Parts.Spares(). This may feel like inheritance, butembedding does not provide polymorphism.The receiver for methods on Parts will always be of type Parts, even when delegated through Bicycle.

Patterns used with classical inheritance, like the template method  pattern, aren‘t suitable for embedding. It‘s far better to think in  terms of composition & delegation, as we have here.

Composite Literals

var (
     RoadBikeParts = Parts{
         {"chain", "10-speed", true}, 
         {"tire_size", "23", true},
         {"tape_color", "red", true},
     }
     MountainBikeParts = Parts{
         {"chain", "10-speed", true},
         {"tire_size", "2.1", true},
         {"front_shock", "Manitou", false},
         {"rear_shock", "Fox", true},
     }
     RecumbentBikeParts = Parts{
         {"chain", "9-speed", true},
         {"tire_size", "28", true},
         {"flag", "tall and orange", true},
     }
 )

Go provides a nice syntax for initializing values, called composite literals. Being able to initialize a struct with an array-like syntax made the PartsFactory from the Ruby example feel unnecessary.

func main() {
     roadBike := Bicycle{Size: "L", Parts: RoadBikeParts}
     mountainBike := Bicycle{Size: "L", Parts: MountainBikeParts}
     recumbentBike := Bicycle{Size: "L", Parts: RecumbentBikeParts}

Composite literals can also use a field: value syntax, in which all fields are optional.

The short declaration operator (:=) usestype inferenceto initialize roadBike, etc. with the Bicycle type.

Output

    fmt.Println(roadBike.Spares())
    fmt.Println(mountainBike.Spares())
    fmt.Println(recumbentBike.Spares())

We print the the result of calling Spares in the default format:

 [{chain 10-speed true} {tire_size 23 true} {tape_color red true}]
 [{chain 10-speed true} {tire_size 2.1 true} {rear_shock Fox true}]
 [{chain 9-speed true} {tire_size 28 true} {flag tall and orange true}]

Combining Parts

     comboParts := Parts{}
     comboParts = append(comboParts, mountainBike.Parts...)
     comboParts = append(comboParts, roadBike.Parts...)
     comboParts = append(comboParts, recumbentBike.Parts...)
     fmt.Println(len(comboParts), comboParts[9:])
     fmt.Println(comboParts.Spares())
 }

Parts behaves like a slice. Getting the length, slicing the slice, or combining multiple slices all works as usual.

It would seem that the equivalent solution in Ruby is to subclass  Array, but unfortunately Ruby "misplaces" the spares method when two  Parts are concatenated (update: Steve Klabnik goes into detail).

"...in a perfect object-oriented language this solution would be  exactly correct. Unfortunately, the Ruby language has not quite achieved  perfection..." - Sandi Metz

Interfaces

Polymorphism in Go is provided by interfaces. They aresatisfied implicitly, unlike Java or C#, so interfaces can be defined for code we don‘t own.

Compared to duck typing,interfaces are statically checkedand documented through their declaration, rather than through writing a series of respond_to? tests.

"It is impossible to create an abstraction unknowingly or by accident; in statically typed languages defining an interface is always intentional." - Sandi Metz

For a simple example, let‘s say we didn‘t want to print the NeedsSpare flag of Part. We could write a String method as such:

 func (part Part) String() string {
     return fmt.Sprintf("%s: %s", part.Name, part.Description)
 }

Then the calls to Println above would output this instead:

 [chain: 10-speed tire_size: 23 tape_color: red]
 [chain: 10-speed tire_size: 2.1 rear_shock: Fox]
 [chain: 9-speed tire_size: 28 flag: tall and orange]

This works because we have satisfied the Stringer interface, which the fmt package makes use of. It is defined as:

 type Stringer interface {
     String() string
 }

Interface types can be used in the same places as other types.  Variables and arguments can take a Stringer, which accepts anything that  implements the String() string method signature.

Exports

Go uses packages for namespacing. Exported identifiers begin with a  capital letter. To make an identifier internal to a package, we start it  with a lowercase letter:

 type Part struct {
     name        string
     description string
     needsSpare  bool
 }

Then we could export setter and getter methods:

 func (part Part) Name() string {
     return part.name
 }

 func (part *Part) SetName(name string) {
     part.name = name
 }

It‘s easy to determine what is using the public API vs. internal  fields or methods. Just look at the case of the identifier (eg. part.Name() vs. part.name).

Notice that we don‘t prefix getters with Get (eg. GetName).  Getters aren‘t strictly necessary either, especially with strings. When  the need arises, we can always change the Name field to use a custom  type that satisfies the Stringer interface.

Finding Some Privacy

Internal names (lowerCase) can be accessed from anywhere  in the same package, even if the package contains multiple structs  across multiple files. If you find this unsettling, packages can also be  as small as you need.

It is good practice to use the (more stable) public API when possible, even from within the same class in classical languages. Go‘s use of capitalization makes it easy to see where this is the case.

For Great GOOD

Composition, embedding and interfaces provide powerful tools for Object-Oriented Design in Go.

While Idiomatic Go requires a change in thinking, I am pleasantly  surprised with how simple and concise Go code can be when playing to its  strengths.

Comment on Go+, Hacker News, reddit, LinkedIn, or the Go Nuts mailing list.

"So few people realize that classes and inheritance are mostly  historic accidents and not real needs for good OOD." - Javier Guerra via  Go+

"This is excellent stuff. It helps who looks for correspondences at  least to get started. Some people will inevitably look for OO analogies  when learning Go (like I did!), but the official docs avoid them like  the plague." - Rodrigo Moraes via Go+

"Seconding +Rodrigo Moraes here, excellent stuff. I think this would  have accelerated my own learning curve, where I‘ve kept looking for  classical OO approaches. Thanks for the writeup." - Levi Cook via Go+  and Twitter

"Great article, really enjoyed it! Well structured and example code  seems to be good. Will be going over this again tomorrow and adding some  of the tricks I wasn‘t aware of to my toolbox." - codygman via HN

"This article was great. I come from the land of C#(and Python, js)  and this really cleared up a lot of questions I had about how OO works  in Go." - TinyGoats via reddit

"Great post! Having also read POODR, it‘s great to see how well Go  fares in terms of concise-ness and expressiveness against a language  like ruby." - Benjamin Moss via LinkedIn

"Yes, indeed, good post" - Alexander Zhaunerchyk via LinkedIn

"Enjoyed this one. Still trying to wrap my head around some of it." - Sammy Moshe via LinkedIn

"A great introduction to the Go language from @nathany : "Go Object Oriented Design" - Pat Shaughnessy via Twitter

"There is an awesome article written by Nathan Youngman which I  highly recommend every beginner should read. It really put many details  into perspective for me." - Alejandro Gaviria via Personal Ramblings

"Your article is a great introduction to Go. I sure could have  benefited from reading it before throwing myself in!" - Andrew  Mackenzie-Ross via mackross.net

时间: 2024-11-10 07:37:42

Go Object Oriented Design的相关文章

面向对象程序设计(Object Oriented Design)

OOD的流程: 需求分析-->系统/程序设计-->实现这个设计-->测试 Class Design 1. Identify classes for the system. 2. Describe attributes and methods in each class. 3. Establish relationships among classes. 4. Create classes. Identity classes and objects A fundamental part o

CSE210 Advanced Object Oriented Programming

CSE210 Advanced Object Oriented ProgrammingCoursework 2019 Release date: 11th, Mar, 2019Deadline: 12:00PM, 23rd, Apr, 2019 1. DescriptionThe objective of the coursework is to develop a practical application for data processing, analysis and content s

MQF Object Oriented Programming

MQF Object Oriented Programming I Fall 2019Hw2 Due 10/1/2019 before midnightSpecificationsRutgers parking garage management system is required to take care Rutgers University Paringneeds. The system can keep track of all the cars parked in your garag

5COSC001W Object Oriented

University of WestminsterSchool of Computer Science & Engineering5COSC001W Object Oriented Programming – Coursework 1 (2019/20)Module leader Barbara VillariniUnit Coursework 1Weighting: 50%Qualifying mark 30%DescriptionObject Oriented Programming and

Java Object Oriented Programming concepts

Introduction This tutorial will help you to understand about Java OOP'S concepts with examples. Let's discuss about what are the features of Object Oriented Programming. Writing object-oriented programs involves creating classes, creating objects fro

Object Oriented Programming python

new concepts of the object oriented programming : class encapsulation inheritance polymorphism the three features of an object are : identity, state and behaviora class is an abstraction which regroup objects who have the same attributes and the same

面向对象编程OOP Object oriented programing

oop是面向对象编程(设计) 面向对象程序设计(英语:Object Oriented Programming,缩写:OOP),指一种程序设计范型,同时也是一种程序开发的方法论.它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性.灵活性和扩展性.基本理论 一项由 Deborah J. Armstrong 进行的长达40年之久的计算机著作调查显示出了一系列面向对象程序设计的基本理论.它们是: 类 类(Class)定义了一件事物的抽象特点.通常来说,类定义了事物的属性和它可以做到的(

CSC72002 Object Oriented Programming

CSC72002 Object Oriented Programming - Assignment 2Weight: 40% of your final markSpecificationsYour task is to complete various exercises in NetBeans, using the Java language, and to submitthese via the MySCU link created for this purpose.Marking cri

what's the problem of Object oriented programming

The problem came from the Object itselft.It can divided into two aspect: 1.Everything is an Object: that's not true,lots of so called object is  not object ,it's just a Wrapper.You dont know what it is  ,but you just need one to let  things go on. Sa