-
Notifications
You must be signed in to change notification settings - Fork 0
/
objects.tex
368 lines (280 loc) · 10.7 KB
/
objects.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
\cleardoublepage
\phantomsection
\chapter{Objects}
\begin{quote}
\textit{Program to an interface, not an implementation.} -- Design Patterns by Gang of Four
\end{quote}
In the chapter on functions, we have also learned about methods. A
method is a function associated with a type, more specifically a
concrete type. As you know, an object is an instance of a type. In
general, methods define the behavior of an object.
Interfaces\index{interface} in Go provide a formal way to specify the
behavior of an object. In layman's terms, Interface is like a
blueprint which describes an object. So, Interface is considered as
an abstract type, commonly referred to as interface type.
Small interfaces with one or two methods are common in Go.
Here is a \texttt{Geometry} interface which defines two methods:
\begin{lstlisting}[numbers=none]
type Geometry interface {
Area() float64
Perimeter() float64
}
\end{lstlisting}
If any type satisfy this interface - that is define these two methods
which returns float64 - then, we can say that type is implementing
this interface. One difference with many other languages with
interface support and Go is that, in Go implementing an interface
happens implicitly. So, no need to explicitly declare a type is
implementing a particular interface.
To understand this idea, consider this \texttt{Rectangle} struct type:
\begin{lstlisting}[numbers=none]
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2*(r.width + r.height)
}
\end{lstlisting}
As you can see above, the above Rectangle type has two methods named
Area and Perimeter which returns float64. So, we can say Rectangle is
implementing the \texttt{Geometry} interface. To elaborate the
example further, we will create one more implementation:
\begin{lstlisting}[numbers=none]
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.Radius
}
\end{lstlisting}
Now we have two separate implementations for the \texttt{Geometry}
interface. So, if anywhere the \texttt{Geometry} interface type is
expected, you can use any of these implementations.
Let's define a function which accepts a \texttt{Geometry} type and
prints area and perimeter.
\begin{lstlisting}[numbers=none]
func Measure(g Geometry) {
fmt.Println("Area:", g.Area())
fmt.Println("Perimeter:", g.Perimeter())
}
\end{lstlisting}
When you call the above function, you can pass the argument as an
object of \texttt{Geometry} interface type. Since both
\texttt{Rectangle} and \texttt{Circle} satisfy that interface, you can
use either one of them.
Here is a code which call \texttt{Measure} function with
\texttt{Rectangle} and \texttt{Circle} objects:
\begin{lstlisting}[numbers=none]
r := Rectangle{Width: 2.5, Height: 4.0}
c := Circle{Radius: 6.5}
Measure(r)
Measure(c)
\end{lstlisting}
\section{Type with Multiple Interfaces}
In Go, a type can implement more than one interface. If a type has
methods that satisfy different interfaces, we can say that that type
is implementing those interfaces.
Consider this interface:
\begin{lstlisting}[numbers=none]
type Stringer interface {
String() string
}
\end{lstlisting}
In previous section, there was a Rectangle type declared with two
methods. In the same package, if you declare one more method like
below, it makes that type implementing Stringer interface in addition
to the Geometry interface.
\begin{lstlisting}[numbers=none]
func (r Rectangle) String() string {
return fmt.Sprintf("Rectangle %vx%v", r.Width * r.Height)
}
\end{lstlisting}
Now the \texttt{Rectangle} type conforms to both \texttt{Geometry}
interface and \texttt{Stringer} interface.
\section{Empty Interface}
The empty interface\index{interface!empty} is the interface type that
has no methods. Normally the empty interface will be used in the
literal form: \texttt{interface{}}. All types satisfy empty interface.
A function which accepts empty interface, can receive any type as the
argument. Here is an example:
\begin{lstlisting}[numbers=none]
func blackHole(v interface{}) {
}
blackHole(1)
blackHole("Hello")
blackHole(struct{})
\end{lstlisting}
In the above code, the \texttt{blackHole} functions accepts an empty
interface. So, when you are calling the function, any type of argument
can be passed.
The \texttt{Println} function in the \texttt{fmt} package is variadic
function which accepts empty interfaces. This is how the function
signature looks like:
\begin{lstlisting}[numbers=none]
func Println(a ...interface{}) (n int, err error) {
\end{lstlisting}
Since the \texttt{Println} accepts empty interfaces, you could pass
any type arguments like this:
\begin{lstlisting}[numbers=none]
fmt.Println(1, "Hello", struct{})
\end{lstlisting}
\section{Pointer Receiver}
In the chapter on Functions, you have seen that the methods can use a
pointer receiver\index{method!pointer receiver}. Also we understood
that the pointer receivers are required when the object attributes
need be to modified or when passing large size data.
Consider the implementation of \texttt{Stringer} interface here:
\begin{lstlisting}[numbers=none]
type Temperature struct {
Value float64
Location string
}
func (t *Temperature) String() string {
o := fmt.Sprintf("Temp: %.2f Loc: %s", t.Value, t.Location)
return o
}
\end{lstlisting}
In the above example, the \texttt{String} method is implemented using
a pointer receiver. Now if you define a function which accepts the
\texttt{fmt.Stringer} interface, and want the \texttt{Temperature}
object, it should be a pointer to \texttt{Temperature}.
\begin{lstlisting}[numbers=none]
func cityTemperature(v fmt.Stringer) {
fmt.Println(v.String())
}
func main() {
v := Temperature{35.6, "Bangalore"}
cityTemperature(&v)
}
\end{lstlisting}
As you can see, the \texttt{cityTemperature} function is called with a
pointer. If you modify the above code and pass normal value, you will
get an error. The below code will produce an error as pointer is not
passed.
\begin{lstlisting}[numbers=none]
func main() {
v := Temperature{35.6, "Bangalore"}
cityTemperature(v)
}
\end{lstlisting}
The error message will be something like this:
\begin{lstlisting}[numbers=none]
cannot use v (type Temperature) as type fmt.Stringer in argument to
cityTemperature: Temperature does not implement fmt.Stringer (String
method has pointer receiver)
\end{lstlisting}
\section{Type Assertions}
In some cases, you may want to access the underlying concrete value
from the interface value. Let's say you define a function which
accepts an interface value and want access attribute of the concrete
value. Consider this example:
\begin{lstlisting}[numbers=none]
type Geometry interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2*(r.width + r.height)
}
func Measure(g Geometry) {
fmt.Println("Area:", g.Area())
fmt.Println("Perimeter:", g.Perimeter())
}
\end{lstlisting}
In the above example, if you want to print the width and and height
from the \texttt{Measure} function, you can use type assertions.
Type assertion\index{type!assertion} gives the underlying concrete
value of an interface type. In the above example, you can access the
rectangle object like this:
\begin{lstlisting}[numbers=none]
r := g.(Rectangle)
fmt.Println("Width:", r.Width)
fmt.Println("Height:", r.Height)
\end{lstlisting}
If the assertion fail, it will trigger a panic.
Type assertion has an alternate syntax where it will not panic if
assertion fail, but gives one more return value of boolean type. The
second return value will be \texttt{true} if assertion succeeds
otherwise it will give \texttt{false}.
\begin{lstlisting}[numbers=none]
r, ok := g.(Rectangle)
if ok {
fmt.Println("Width:", r.Width)
fmt.Println("Height:", r.Height)
}
\end{lstlisting}
If there are many types that need to be asserted like this, Go
provides a type switches which is explained in the next section.
\section{Type Switches}
As you have seen in the previous section, type assertions gives access
to the underlying value. But if there any many assertions need to be
made, there will be lots \texttt{if} blocks. To avoid this, Go
provides type switches\index{switch!type}.
\begin{lstlisting}[numbers=none]
switch v := g.(type) {
case Rectangle:
fmt.Println("Width:", v.Width)
fmt.Println("Height:", v.Height)
case Circle:
fmt.Println("Width:", v.Radius)
case default:
fmt.Println("Unknown:")
}
\end{lstlisting}
In the above example, type assertion is used with switch cases. Based
on the type of \texttt{g}, the case is executed.
Note that the \textit{fallthrough} statement does not work in type
switch.
\section{Exercises}
\textbf{Exercise 1:} Using the \texttt{Marshaller} interface, make the marshalled output of the
\texttt{Person} object given here all in upper case.
\begin{lstlisting}[numbers=none]
type Person struct {
Name string
Place string
}
\end{lstlisting}
\textbf{Solution:}
\lstinputlisting[numbers=none]{code/interfaces/marshal.go}
\subsection{Additional Exercises}
Answers to these additional exercises are given in the Appendix A.
\textbf{Problem 1:} Implement the built-in \texttt{error} interface for a custom data type.
This is how the \texttt{error} interface is defined:
\begin{lstlisting}[numbers=none]
type error interface {
Error() string
}
\end{lstlisting}
\section*{Summary}
This chapter explained the concept of interfaces and their uses. Interfaces are
an important concept in Go. Understanding interfaces and using them properly
makes the design robust. The chapter covered the empty interface, pointer
receivers, and type assertions and type switches.
Brief summary of key concepts introduced in this chapter:
\begin{itemize}
\item An interface is a set of methods that a type must implement. A type that
implements an interface can be used anywhere an interface is expected. This
allows for greater flexibility and reusability in Go code.
\item A pointer receiver is a method that takes a pointer to a struct as its receiver.
Pointer receivers are often used to modify the state of a struct.
\item A type assertion is a way of checking the type of a value at runtime. Type
assertions can be used to ensure that a value is of a certain type before
using it.
\item A type switch is a control flow statement that allows for different code to be
executed based on the type of a value. Type switches can be used to make code
more robust and easier to read.
\end{itemize}