Learn to model objects and behaviors with Go series: An example with dish washing (Part 1)
This is a series of story for programmers who want to start object and behavior modelling with Go. I wish you have a great journey.
Why it is about dish washing???
My girlfriend is really good at cooking, and in contrast, I’m only good at washing dishes, and of course hand washing. Spending time after the meal to wash dishes, pans and bowls makes me feel relax.
It’s not only the work of hands, but my brain also tries to work out the most effective solution for washing.
While you may not agree with that statement, please follow my story to see how I turn the dish washing in to an object and behavior modelling, with the help of my favorite language, Go.
Prerequisites
Unfortunately, this story will require you to have some basic knowledge of Go:
- Package https://golang.org/doc/effective_go.html#package-names
- For loop https://golang.org/doc/effective_go.html#for
- Slices https://golang.org/doc/effective_go.html#slices
It won’t take you more than 5 minutes to go through these concepts, which is equal to the time I rest on the dinner table before going to wash the dishes.
The simplest model
- Objects and behaviors
- Dish object: encapsulates attributes of the dish:
- The dirty state of the dish.
Dish should have the behaviors:
- Clean itself. - Sponge object: encapsulates attributes of the sponge (you can not wash without sponge!!):
- Color of the sponge.
- Number of times we can use sponge to wash until no more liquid.
Sponge should have the behaviors:
- Wash the dish.
- Top up liquid. - LiquidDispenser object: one of my housemate does not want to use dish washing liquid, and I told him to stay away from the sink. It has attributes:
- The amount of liquid it stores.
LiquidDispenser should have the behaviors:
- Shows the amount of liquid left.
- Dispenses an amount of liquid.
2. Implementation using Go
dish.go
sponge.go
liquiddispenser.go
For people who are not familiar with Go, let me explain the syntax.
Constant
Sponge has maximum of the times it can be used to wash with liquid topped up. And it’s naturally to store it as a constant.
Struct
In Go, we use struct
as the class
for the object we want to model.
struct
can have fields (attributes) or methods (member functions)
Go does have the concept for exported and non-exported identifiers which is similar to private and public in some way.
From https://golang.org/ref/spec#Exported_identifiersAn identifier may be exported to permit access to it from another package. An identifier is exported if both:- the first character of the identifier's name is a Unicode upper case letter (Unicode class "Lu")- the identifier is declared in the package block or it is a field name or method name.All other identifiers are not exported.
Example from our dish washing code:
- Non-capital letter
amount
specifies thatamount
is the non-exported attribute. - Capital letter
Color
specifies thatColor
is exported attribute. - Non-capital letter
clean
specifies theclean()
is the non-exported method. - Capital letter
IsClean
specifies thatIsClean()
is the exported-method. - Non-capital letter
spongeMaxLiquid
specifies thatspongeMaxLiquid
is the non-exported constant. - Captial letter
Yellow
specifies thatYellow
is the exported constant.
Pointer vs Value receivers, Pointer vs Value methods
To describe a behavior of a model, or a type in Go, we use methods.
From dish.go
example, we can see there are two types of receiver, Dish
(value receiver) and *Dish
(pointer receiver)
- The methods that we can call with receiver
Dish
are methods declare with typeDish
(IsClean()
) - The methods that we can call with receiver
*Dish
are methods declare with typeDish
and type*Dish
(IsClean()
andclean()
)
What is the different from pointer and value receiver?
- Value methods can be invoked on pointer and value receivers, but pointer methods can only be invoked on pointer receivers.
- Pointer methods can modify the receiver, while invoking value methods would cause the method to receive a copy of the value, so any modifications would be discarded.
So we will use value methods for read-only or non-modification behavior, and use pointer methods for modification behavior.
IsClean()
only displays the clean state of the dish.clean()
modifies the internal statedirty
of the dish.
Hence, when designing model (type) that will require internal state modification, we usually create and use pointer receiver even for value methods. It’s help to signal the user of our model not to create value type object.
The main function
Go program’s entry point is the main()
function
Now we can run the program
// in the package dishwashing_part1/
$ go build -o main
$ ./main
Before wash, is dish 1 clean? = false
After wash, is dish 1 clean? = true
Before wash, is dish 2 clean? = false
After wash, is dish 2 clean? = true
Before wash, is dish 3 clean? = false
After wash, is dish 3 clean? = true
Before wash, is dish 4 clean? = false
After wash, is dish 4 clean? = true
Before wash, is dish 5 clean? = false
After wash, is dish 5 clean? = true
Before wash, is dish 6 clean? = false
After wash, is dish 6 clean? = true
Before wash, is dish 7 clean? = false
After wash, is dish 7 clean? = true
Before wash, is dish 8 clean? = false
After wash, is dish 8 clean? = true
Before wash, is dish 9 clean? = false
After wash, is dish 9 clean? = true
Before wash, is dish 10 clean? = false
After wash, is dish 10 clean? = true
So it is, a simple Go program that models the operation of washing dishes.
You can find the source code here
Let’s recall our purpose: object and behavior modelling! And the model is the simplest model we can think about.
Although the program seems to do well, there are a lot of improvements can be done on the models and their behaviors. We will discuss more on part 2 of this series.
Thanks for reading!