面向对象编程

the underlying logic

  • 这本书的 23 个经典的设计模式,基本上就是说了两个面向对象的核心理念:
    • “Program to an ‘interface’, not an ‘implementation’.”
      • 使用者不需要知道数据类型、结构、算法的细节。
      • 使用者不需要知道实现细节,只需要知道提供的接口。
      • 利于抽象、封装、动态绑定、多态。符合面向对象的特质和理念。
    • “Favor ‘object composition’ over ‘class inheritance’.”
      • 继承需要给子类暴露一些父类的设计和实现细节。
      • 父类实现的改变会造成子类也需要改变。
      • 我们以为继承主要是为了代码重用,但实际上在子类中需要重新实现很多父类的方法。

代码需要通过对象来达到抽象的效果,导致了相当厚重的“代码粘合层”。 通过对象来达到抽象结果,把代码分散在不同的类里面,然后,要让它们执行起来,就需要把这些类粘合起来。

原型

基于原型(Prototype)的编程其实也是面向对象编程的一种方式。没有 class 化的,直接使用对象。又叫,基于实例的编程

 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
//Define human class
var Person = function (fullName, email) {
  this.fullName = fullName;
  this.email = email;
  
  this.speak = function(){
    console.log("I speak English!");
  };
  this.introduction = function(){
    console.log("Hi, I am " + this.fullName);
  };
}

//Define Student class
var Student = function(fullName, email, school, courses) {
  Person.call(this, fullName, email);
  // Initialize our Student properties
  this.school = school;
  this.courses = courses;
  // override the "introduction" method
  this.introduction= function(){
  console.log("Hi, I am " + this.fullName +
        ". I am a student of " + this.school +
        ", I study "+ this.courses +".");
  };
  // Add a "exams" method
  this.takeExams = function(){
    console.log("This is my exams time!");
  };
};

// Create a Student.prototype object that inherits
// from Person.prototype.
Student.prototype = Object.create(Person.prototype);
// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;

var student = new Student("Hao Chen",
              "haoel@hotmail.com",
              "XYZ University",
              "Computer Science");
student.introduction();
student.speak();
student.takeExams();
// Check that instanceof works correctly
console.log(student instanceof Person);  // true
console.log(student instanceof Student); // true
  • 一种委托的方式。在使用委托的基于原型的语言中,运行时语言可以“仅仅通过序列的指针找到匹配”这样的方式来定位属性或者寻找正确的数据。所有这些创建行为、共享的行为需要的是委托指针;
    • 这里就说明了委托的方式,跟golang的委托也差不多; 只不过一个是对象(实例)的委托,一个是类的委托。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

type Widget struct {
    X, Y int
}

type Label struct {
    Widget        // Embedding (delegation)
    Text   string // Aggregation
    X int         // Override 
}

func (label Label) Paint() {
  // [0xc4200141e0] - Label.Paint("State")
    fmt.Printf("[%p] - Label.Paint(%q)\n", 
      &label, label.Text)
}

/*
由上面可知:
    我们声明了一个 Widget,其有 X和Y;
    然后用它来声明一个 Label,直接把 Widget 委托进去;
    然后再给 Label 声明并实现了一个 Paint() 方法。
*/
  • 原型编程看起来提倡程序员关注一系列对象实例的行为,而之后才关心如何将这些对象划分到最近的使用方式相似的原型对象,而不是分成类;
    • 这个要打一个问号,好像没有把它们分成类。

delegation/embed

go

  • Go语中的委托和接口多态的编程方式,其实是:
    • 面向对象和原型编程综合的玩法;
delegation + Polymorphism
 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
package main

import(
	"fmt"
)

type Widget struct {
	X, Y int
}

type Label struct {
	Widget        // Embedding (delegation)
	Text   string // Aggregation
	X int         // Override
}

func (label Label) Paint() {
	// [0xc4200141e0] - Label.Paint("State")
	fmt.Printf("[%p] - Label.Paint(%q)\n",
		&label, label.Text)
}

type Button struct {
	Label // Embedding (delegation)
}

func NewButton(x, y int, text string) Button {
	return Button{Label{Widget{x, y}, text, x}}
}
func (button Button) Paint() { // Override
	fmt.Printf("[%p] - Button.Paint(%q)\n",
		&button, button.Text)
}
func (button Button) Click() {
	fmt.Printf("[%p] - Button.Click()\n", &button)
}

type ListBox struct {
	Widget          // Embedding (delegation)
	Texts  []string // Aggregation
	Index  int      // Aggregation
}
func (listBox ListBox) Paint() {
	fmt.Printf("[%p] - ListBox.Paint(%q)\n",
		&listBox, listBox.Texts)
}
func (listBox ListBox) Click() {
	fmt.Printf("[%p] - ListBox.Click()\n", &listBox)
}

type Painter interface {
	Paint()
}

type Clicker interface {
	Click()
}

func main(){

	label := Label{Widget{10, 10}, "State", 100}

	// X=100, Y=10, Text=State, Widget.X=10
	fmt.Printf("X=%d, Y=%d, Text=%s Widget.X=%d\n",
		label.X, label.Y, label.Text,
		label.Widget.X)
	fmt.Println()
	// {Widget:{X:10 Y:10} Text:State X:100}
	// {{10 10} State 100}
	fmt.Printf("%+v\n%v\n", label, label)

	label.Paint()


	button1 := Button{Label{Widget{10, 70}, "OK", 10}}
	button2 := NewButton(50, 70, "Cancel")
	listBox := ListBox{Widget{10, 40},
		[]string{"AL", "AK", "AZ", "AR"}, 0}

	fmt.Println()
	//[0xc4200142d0] - Label.Paint("State")
	//[0xc420014300] - ListBox.Paint(["AL" "AK" "AZ" "AR"])
	//[0xc420014330] - Button.Paint("OK")
	//[0xc420014360] - Button.Paint("Cancel")
	for _, painter := range []Painter{label, listBox, button1, button2} { // ------import----------
		painter.Paint()
	}

	fmt.Println()
	//[0xc420014450] - ListBox.Click()
	//[0xc420014480] - Button.Click()
	//[0xc4200144b0] - Button.Click()
	for _, widget := range []interface{}{label, listBox, button1, button2} { // ------import----------
		if clicker, ok := widget.(Clicker); ok {
			clicker.Click()
		}
	}
}
IoC
step one
 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
package main

import (
	"errors"
	"fmt"
)

type IntSet struct {
	data map[int]bool
}

func NewIntSet() IntSet {
	return IntSet{make(map[int]bool)}
}

func (set *IntSet) Add(x int) {
	set.data[x] = true
}

func (set *IntSet) Delete(x int) {
	delete(set.data, x)
}

func (set *IntSet) Contains(x int) bool {
	return set.data[x]
}

func main(){
	example2()
}

func example2(){
	ints := NewUndoableIntSet()
	for _, i := range []int{1, 3, 5, 7} {
		ints.Add(i)
		fmt.Println(ints)
	}
	for _, i := range []int{1, 2, 3, 4, 5, 6, 7} {
		fmt.Println(i, ints.Contains(i), " ")
		ints.Delete(i)
		fmt.Println(ints)
	}
	fmt.Println()
	for {
		if err := ints.Undo(); err != nil {
			break
		}
		fmt.Println(ints)
	}
}

// -----------------------------------------------------

type UndoableIntSet struct { // Poor style
	IntSet    // Embedding (delegation)
	functions []func()
}

func NewUndoableIntSet() UndoableIntSet {
	return UndoableIntSet{NewIntSet(), nil}
}

func (set *UndoableIntSet) Add(x int) { // Override
	if !set.Contains(x) {
		set.data[x] = true
		set.functions = append(set.functions, func() { set.Delete(x) })
	} else {
		set.functions = append(set.functions, nil)
	}
}

func (set *UndoableIntSet) Delete(x int) { // Override
	if set.Contains(x) {
		delete(set.data, x)
		set.functions = append(set.functions, func() { set.Add(x) })
	} else {
		set.functions = append(set.functions, nil)
	}
}

func (set *UndoableIntSet) Undo() error {
	if len(set.functions) == 0 {
		return errors.New("No functions to undo")
	}
	index := len(set.functions) - 1
	if function := set.functions[index]; function != nil {
		function()
		set.functions[index] = nil // Free closure for garbage collection
	}
	set.functions = set.functions[:index]
	return nil
}
step two
  • 泛型编程、函数式编程、IoC 等范式:
    • 这是不是和最一开始的 C++ 的泛型编程很像?
    • 也和 map、reduce、filter 这样的只关心控制流程,不关心业务逻辑的做法很像?
    • 而且,一开始用一个UndoableIntSet来包装IntSet类,到反过来在IntSet里依赖Undo类,这就是控制反转IoC(inversion of control)
 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
package main

import (
	"errors"
	"fmt"
)

func main(){
	//example1()

	fmt.Println("------------------------------------\n------------------------------")

	//example2()
	example3()
}

func example3(){
	ints := NewIntSet()
	for _, i := range []int{1, 3, 5, 7} {
		ints.Add(i)
		fmt.Println(ints)
	}
	for _, i := range []int{1, 2, 3, 4, 5, 6, 7} {
		fmt.Println(i, ints.Contains(i), " ")
		ints.Delete(i)
		fmt.Println(ints)
	}
	fmt.Println()
	for {
		if err := ints.Undo(); err != nil {
			break
		}
		fmt.Println(ints)
	}
}

type Undo []func()

func (undo *Undo) Add(function func()) {
	*undo = append(*undo, function)
}

func (undo *Undo) Undo() error {
	functions := *undo
	if len(functions) == 0 {
		return errors.New("No functions to undo")
	}
	index := len(functions) - 1
	if function := functions[index]; function != nil {
		function()
		functions[index] = nil // Free closure for garbage collection
	}
	*undo = functions[:index]
	return nil
}

type IntSet struct {
	data map[int]bool
	undo Undo
}

func NewIntSet() IntSet {
	return IntSet{data: make(map[int]bool)}
}

func (set *IntSet) Add(x int) {
	if !set.Contains(x) {
		set.data[x] = true
		set.undo.Add(func() { set.Delete(x) })
	} else {
		set.undo.Add(nil)
	}
}

func (set *IntSet) Delete(x int) {
	if set.Contains(x) {
		delete(set.data, x)
		set.undo.Add(func() { set.Add(x) })
	} else {
		set.undo.Add(nil)
	}
}

func (set *IntSet) Undo() error {
	return set.undo.Undo()
}

func (set *IntSet) Contains(x int) bool {
	return set.data[x]
}
/*
我们再次看到,Go 语言的 Undo 接口把 Undo 的流程给抽象出来,
而要怎么 Undo 的事交给了业务代码来维护(通过注册一个 Undo 的方法)。
这样在 Undo 的时候,就可以回调这个方法来做与业务相关的 Undo 操作了。
*/

Interface patterns

go

compared three patterns in golang

 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
type Country struct {
	Name string
}
func (c Country) ToString() string {
	return "Country = " + c.Name
}

type City struct {
	Name string
}
func (c City) ToString() string{
	return "City = " + c.Name
}

type Stringable interface {
	ToString() string
}
func PrintStr(p Stringable) {
	fmt.Println(p.ToString())
}

func main(){
	d1 := Country {"USA"}
	d2 := City{"Los Angeles"}
	PrintStr(d1)
	PrintStr(d2)
}
example

这里的IntSet的String method:func (set *IntSet) String() string {,就是很像前面的interface patterns

 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
package main

import (
	"fmt"
	"sort"
	"strings"
)

type IntSet struct {
	data map[int]bool
}
func NewIntSet() *IntSet {
	return &IntSet{make(map[int]bool)}
}
func (set *IntSet) Add(x int) {
	set.data[x] = true
}
func (set *IntSet) Delete(x int) {
	delete(set.data, x)
}
func (set *IntSet) Contains(x int) bool {
	return set.data[x]
}
// Satisfies fmt.Stringer interface
func (set *IntSet) String() string {
	if len(set.data) == 0 {
		return "{}"
	}
	ints := make([]int, 0, len(set.data))
	for i := range set.data {
		ints = append(ints, i) }
	sort.Ints(ints)
	parts := make([]string, 0, len(ints))
	for _, i := range ints {
		parts = append(parts, fmt.Sprint(i))
	}
	return "{" + strings.Join(parts, ",") + "}"
}
//An Integer Set with Add(), Delete(), Contains(), String() method

func main() {
	ints := NewIntSet()
	for _, i := range []int{1, 3, 5, 7} {
		ints.Add(i)
		fmt.Println(ints)
	}
	for _, i := range []int{1, 2, 3, 4, 5, 6, 7} {
		fmt.Print(i, ints.Contains(i), " ")
		ints.Delete(i)
		fmt.Println(ints)
	}
}

附录

7 Easy functional programming techniques in Go

https://medium.com/@geisonfgfg/functional-go-bc116f4c96a4

Learning Functional Programming in Go