OOP with golang

OOP with golang

  • Target
    • Goals
    • Non-goals
  • What is OOP
    • Encapsulation
    • Composition, inheritance, and delegation
    • Polymorphism
  • Doctrines behind design patterns
  • Structs Instead of Classes
  • Composition Instead of Inheritance
  • Polymorphism using an interface
  • Dilemma of inheritance
  • Conclusion

Target

Goals

  1. golang struct
  2. golang interface
  3. comparison to c++ & java
  4. discussion about design patterns

Non-goals

  1. introduction to golang. (only focused on struct and interface)
  2. advocate golang or belittle java or c++. (I do NOT have a preference)
  3. provide a source of truth. (mostly comes from my own experience and understanding)

What is OOP

Object-oriented programming (OOP) is a programming paradigm based on the concept of “objects”, which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).

A feature of objects is that an object’s own procedures can access and often modify the data fields of itself (objects have a notion of this or self). In OOP, computer programs are designed by making them out of objects that interact with one another. OOP languages are diverse, but the most popular ones are class-based, meaning that objects are instances of classes, which also determine their types.

Encapsulation

Encapsulation is an object-oriented programming concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse. Data encapsulation led to the important OOP concept of data hiding.

Composition, inheritance, and delegation

  1. Objects can contain other objects in their instance variables; this is known as object composition. Object composition is used to represent "has-a" relationships.
  2. Languages that support classes almost always support inheritance. This allows classes to be arranged in a hierarchy that represents "is-a" relationships
  3. The doctrine of composition over inheritance advocates implementing has-a relationships using composition instead of inheritance
  4. Delegation is another language feature that can be used as an alternative to inheritance.

Polymorphism

Subtyping – a form of polymorphism – is when calling code can be agnostic as to which class in the supported hierarchy it is operating on – the parent class or one of its descendants. Meanwhile, the same operation name among objects in an inheritance hierarchy may behave differently.
This is another type of abstraction that simplifies code external to the class hierarchy and enables strong separation of concerns.

Sounds familiar ? interfacing is a more powerful tool for this purpose

Doctrines behind design patterns

  • “Program to an interface, not an implementation.” (Gang of Four 1995:18)
  • Composition over inheritance: “Favor ‘object composition’ over ‘class inheritance’.” (Gang of Four 1995:20)

I think this is why golang provides powerful interface mechanism and convenient embedded composition and no inheritance.

Structs Instead of Classes

c++:

class Fish
{
   public:
      Fish(std::string name) {
      	this.name = name;
      };
      void swim();
   private:
   	  std::string name;
};
void Fish::swim()
{
    std::cout << name << " swims" << std::endl;
}

java:

class Fish { 
  private String name;
  public Fish(String name) {
  	this.name = name;
  }
  public void swim() {
  	System.out.println(name + " swims");
  }
}

golang:

type Fish struct {  
    name string
}

func (f *Fish) Swim() {  
    fmt.Printf("%s swims\n", f.name)
}

func NewFish(name string) *Fish {
	return &Fish{name: name} 
}

features:

  1. no constructor
  2. no default this pointer
  3. no access qualifier (public, private)
  4. no fancy stuff, easy to read

Composition Instead of Inheritance

inheritance with java

public class Animal {
	public void walk() {
		System.out.println("walking")
	}
	public void jump() {
		System.out.println("jumping")
	}
}

public class Dog extends Animal{
	@override
	public void jump() {
		System.out.println("dog jumping")
	}
	public void bark() {
		System.out.println("barking")
	}

	public static void main() {
		Dog dog = new Dog();
		dog.bark(); // subclass method
		dog.walk(); // parent class method
		dog.jump(); // subclass method
	}
}

composition with Golang

type Animal struct {  
}

func (animal *Animal) Walk() {  
    fmt.Printfln("walking")
}

func (animal *Animal) Jump() {  
    fmt.Printfln("jumping")
}

func NewAnimal() *Animal {
	return &Animal{}
}

type Dog struct {  
	*Animal // embedded variable, can be referenced as  Dog.Animal
}

func (dog *Dog) Bark() {  
    fmt.Printfln("barking")
}

func (dog *Dog) Jump() {  
    fmt.Printfln("dog jumping")
}

func NewDog() *Dog {
	return &Dog{
		&Animal{}
	}
}

func main() {
	dog = NewDog()
	dog.Bark()
	dog.Walk() // equals to Dog.Animal.Walk
	dog.Jump() // override *Animal.Jump
}

variable embedding and method look-up make delegate-pattern very common in golang programs.

embedded variables can be interfaces, which makes it more flexible

type Dog struct {  
	Animaler
}

func (dog *Dog) Bark() {  
    fmt.Printfln("barking")
}

func (dog *Dog) Jump() {  
    fmt.Printfln("dog jumping")
}

func NewDog(animaler Animaler) *Dog {
	return &Dog{
		Animaler
	}
}

Polymorphism using an interface

So, embedded anonymous fields make it easy to mock inheritance. But what about polymorphism ?
in java, we can do this:

Animal animal = new Dog();
animal.Jump(); // output: dog jumping

but in golang, this is not going to work

Animal *animal = NewDog() // error, type not match

polymorphism is super simple with interface because interfaces are duck-typed, which means a type implements an interface if it provides definitions for all the methods declared in the interface

type Animaler interface {  
    func Walk()
    func Jump()
}

Animaler animal1 = NewAnimal()
animal1.Jump() // output jumping

Animaler animal2 = NewDog()
animal2.Jump() // output dog jumping

duck-typed interface makes it unnecessary to write adapter classes in languages like java, which implemented interfaces must be declared.

Dilemma of inheritance

Inheritance implies is-a relationship, but sometimes, one can be many things or play many roles, for example, an intern is both an employee and a student, so in order to maintain the is-a relationship, we have to use multiple inheritance.

public class Intern extends Employee, Student

but we all know multiple inheritance is bad !
one of the solutions would make Employee and Student abstract classes with no data, which is just like interfaces !

Conclusion

golang has a set of features to encourage you to write the best OO programs

  • no inheritance to avoid complex hierarchy
  • embedded variables and method look-up for easy code reuse (delegate pattern)
  • duck-typed interfaces to reduce adapter classes in most cases
  • conforms to the doctrines of design patterns, which is best practice in most cases

你可能感兴趣的:(golang)