Object Oriented Programming In Golang(1) Struct And Methods
Struct can encapsulates data and action together, which is one of the main characters of OOP. Encapsulation provides three benifits:
- The clients cannot directly modify the object’s variables, one need inspect fewer statements to understand the possible values of those variables;
- Hiding implementation details prevents clients from depending on things might change, which give designer the greater freedom to evolve the implememtation without breaking the API compatiblity.
- It pevents clients from setting an object’s variables arbitrarily. The variables can only be alters by method of the object, so the internal invariants can be maintained.
The following is a IntSet struct designed according to the Encapsulation method:
// An IntSet is a set of small no-negative integers
// Its zero value represent the empty set
type IntSet struct {
words []uint64
}
// Has report whether the set contains the no-negative value x
func (s *IntSet) Has(x int) bool {
word, bit := x/64, x%64
return word < len(s.words) && s.words[word]&(1<<bit) != 0
}
// Add adds the no-negative value x to the set
func (s *IntSet) Add(x int) {
word, bit := x/64, x%64
for word >= len(s.words) {
s.words = append(s.words, 0)
}
s.words[word] |= (1 << bit)
}
// AddAll adds a sequces of no-negative values to the set
func (s *IntSet) AddAll(xs ...int) {
for _, x := range xs {
s.Add(x)
}
}
// Remove removes the no-negative value x from the set
func (s *IntSet) Remove(x int) {
word, bit := x/64, x%64
if word >= len(s.words) {
return
}
s.words[word] &= ^(1 << bit)
}
// UnionWith sets s to the union of s and t
func (s *IntSet) UnionWith(t *IntSet) {
for i, tword := range t.words {
if i < len(s.words) {
s.words[i] |= tword
} else {
s.words = append(s.words, tword)
}
}
}
// IntersectWith sets s to the intersect of s and t
func (s *IntSet) IntersectWith(t *IntSet) {
for i, tword := range t.words {
if i < len(s.words) {
s.words[i] &= tword
}
}
}
// DiffercenceWith set s to diffecence if s and t
func (s *IntSet) DifferenceWith(t *IntSet) {
scopy := s.Copy()
scopy.IntersectWith(t)
for _, item := range scopy.Items() {
s.Remove(item)
}
}
// Items return set items as a slice
func (s *IntSet) Items() []int {
var res []int
for i, word := range s.words {
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0 {
res = append(res, 64*i+j)
}
}
}
return res
}
// Len return the number of elements
func (s *IntSet) Len() int {
res := 0
for _, word := range s.words {
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0 {
res += 1
}
}
}
return res
}
// Clear clear all elements from the set
func (s *IntSet) Clear() {
for i := range s.words {
s.words[i] &= 0
}
}
// Copy return a copy of the set
func (s *IntSet) Copy() *IntSet {
t := &IntSet{}
t.UnionWith(s)
return t
}
// String returns the set as a string of form "{1, 2, 3}"
func (s *IntSet) String() string {
var buf bytes.Buffer
buf.WriteByte('{')
for i, word := range s.words {
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0 {
if buf.Len() > len("{") {
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%d", 64*i+j)
}
}
}
buf.WriteByte('}')
return buf.String()
}
Use struct and methods, you can implement Encapsulation as other languages. Now let’s inspect the code snnipet above.
type IntSet struct {
words []uint64
}
We can write the struct as:
type IntSet []uint64
Now IntSet is a type alias of []uint64, we can add all the mothods(Add, Remove…) on it.
But it lost the Encapsulation. Now client can change it directly! We say that life is a balance. When you design a API, it is your duty to decide Encapsulation or NoEncapsulation. Have a nice day! :)