Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 14, 2023. It is now read-only.

Commit 763dca0

Browse files
Merge pull request #16
update(oop): add oop_in_go
2 parents 7ebbaf5 + cc7cb04 commit 763dca0

File tree

7 files changed

+196
-3
lines changed

7 files changed

+196
-3
lines changed

‎README.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
* [WSL Windows Linux 子系统](./windows/README.md)(Windows 用户 强烈建议)
1111
* [Brew Unix 包管理工具](./unix/Brew.md)(可选)
12-
* [Git 版本控制工具](./common/git.md)
12+
* [Git 版本控制工具](./git/git.md)
1313
* [Goland 最好的Golang IDE](./jetBrains/commonware.md)
1414
* [Shell 使用基本逻辑](./unix/shell.md)
1515

@@ -18,7 +18,7 @@
1818

1919
### [git 与版本控制](./git)
2020

21-
### [go 语言相关](./golang)
21+
### [Go 语言相关](./golang)
2222

2323
### [jetBrains ](./jetBrains)
2424

@@ -28,4 +28,6 @@
2828

2929
### [公共 杂项](./common)
3030

31+
### [面向对象](./common/oop)
32+
3133

‎common/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88

99
### [WEB 开发中的前后端分离](./networkKnowledge.md)
1010

11-
### [面向对象开发思想](./oop.md)
11+
### [面向对象开发思想](./oop)

‎common/oop/README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## 面向对象思想
2+
3+
可以说,每个写业务的程序员都需要掌握面向对象思想。这里给出一些面向对象思想的指引。
4+
5+
## [1. 面向对象思想入门](./oop_started.md)
6+
## [2. 面向对象标准实现](./oop_standard.md)
7+
## [3. Go 如何实现面向对象](./oop_in_go.md)
8+
## [4. 常用面向对象设计模式](./oop_design_pattern.md)
9+
10+
oop 并不是灵丹妙药(silver bullet),在合适的时候使用它就好。
11+
12+
我也曾在某个地方看到,说,封装、继承、多态很早就被提出了,并不是面向对象独有的。
13+
函数式编程等思想,都可以一试,适合的就是最好的。不要过度迷恋一种思想。

‎common/oop/oop_design_pattern.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# 面向对象设计模式

‎common/oop/oop_in_go.md‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# 谈谈Go与面向对象
2+
3+
本文面向已经会 Go 的基础语法并基本掌握一门面向对象语言的读者。
4+
5+
先抛个问题:Go 是不是面向对象语言?
6+
7+
官方的回答是:「Yes and no」。
8+
9+
Go 语言可以做到绝大多数面向对象语言的特性,但它不是一门「标准」的面向对象语言,它没有「type hierarchy」。
10+
11+
一开始,我觉得它以自己的奇怪甚至近乎「妖魔」的方式与面向对象打了个擦边球;
12+
后来,我反而觉得完美面向对象就应该是这样灵活的,现在的所谓的「标准面向对象」,反而是一种不完美的实现。
13+
14+
下面来谈谈 Go 如何实现面向对象。因为 Java 是比较「规整」的面向对象实现,所以下文会多次与 Java 进行对比。
15+
16+
## 封装
17+
18+
封装指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
19+
20+
这个应该不用多讲,Go 使用大小写控制可访问性(包外),大写代表导出(public),小写代表私有(private)。
21+
22+
方法暴露,使用 `receiver` ,类型、变量、方法的可见性规则都是用大小写。
23+
24+
```go
25+
type Person struct {
26+
Name string // 大写,导出,包外可见,public
27+
age int // 小写,私有,仅包内可见,private
28+
}
29+
30+
// Speak 暴露公有方法
31+
func (p *Person) Speak() {
32+
fmt.Println("Hello, my name is", p.Name)
33+
}
34+
35+
// SetAge GetAge 方法略
36+
```
37+
38+
为类增加方法,Go 与 Java 最大的不同是,Java 的方法是在类内的,而 Go 在类外,有点像 struct 上贴了一个个狗皮膏药的感觉。
39+
起初可能会不习惯,但这让方法的添加变得更灵活,receiver 的设计更使得所有类都能拥有方法。
40+
41+
Go 的 receiver 的设计更符合底层的面向对象的思想:为某一类事物,附加一些行为。
42+
43+
Go 的任何非内置的「定义类型」,都能拥有方法;而 Java,只有类才能拥有方法,或者说,想在一个东西上施加操作,必须定义一个类,显得臃肿。
44+
45+
## 继承
46+
47+
我对继承的理解是,子类拥有父类的所有属性和方法。
48+
49+
Go 的继承使用类似组合的方式实现,与标准面向对象实现最大的区别在于,基类变量不能引用子类变量。
50+
51+
```go
52+
type Man struct {
53+
Person // 内嵌,继承
54+
otherField string // 组合
55+
}
56+
57+
man := Man{"bird"}
58+
_ = man.Person.Name
59+
_ = man.Name // 省略 Person 匿名字段
60+
man.Speak()
61+
```
62+
63+
如上,在 Man 中加入一个只有类型没有名字的 Person 属性,就是内嵌。Go 的一大特性就是,内嵌字段可以被省略。详见代码。
64+
65+
所以,Man 可以省略 Person,**直接访问 Person 的所有属性并调用其方法**。所以看起来,就像继承一样。
66+
67+
总结一下就是,结构体内嵌匿名变量就是继承,无名就是组合。
68+
69+
和 Java 的区别在于, `var p person = Man{}` 是不可行的。父类变量无法引用子类对象(但这并不意味着 Go 没有多态)。
70+
所以 Go 的继承是不满足「里氏替换」原则的。
71+
72+
我觉得这个特性挺好,逼迫开发者少使用继承,多面向抽象编程。
73+
74+
如何实现对方法的重写(Override),即子类覆盖父类的同签名方法,以实现不同表现?
75+
76+
稍微岔开一下,Go 没有「重载」,即同函数名,函数签名却不同。所以这个问题其实是,Go 如何覆盖父类同名方法?
77+
78+
这时候就体现 Go 的设计哲学了, less is more。你都不需要知道什么是重写:
79+
80+
没有那么多的术语和要记的东西,也不需要新的关键字,一切都是自然而然:
81+
子类想用父类的,直接继承了不用管;子类想拥有不同的表现形式(行为),那就自己定义一个。
82+
83+
怎么自己定义一个方法呢,很简单,定义一个以子类作为 receiver 的方法。正如前面的继承,没有新增任何关键字。
84+
85+
简单理一下逻辑,如果调用方法的时候,子类本身拥有该方法,那就直接调用;如果没有,就看看父类有没有,一层层找上去。
86+
87+
如何实现多继承?想继承谁,内嵌什么类型就行。
88+
89+
至此,Go 非常优雅且简单地实现了继承。
90+
91+
这里还有个面向对象的原则,「使用组合而不是继承」,通俗继承的坏处很多,比如父类改了子类就会被动跟着改动。
92+
所以 Go 使用了组合的方式来实现了继承,是不是天生规避了一些继承的缺点?
93+
以及继承会暴露父类的实现细节,这个问题 Go 也存在。
94+
95+
## 多态
96+
97+
Go 的多态是用 interface 实现的, interface 是 Go 语言的灵魂之一,为静态的 Go 语言增添了动态性。
98+
99+
我理解的接口,是一种「约定」,接口的方法,约定了一系列操作,某样东西能完成这个操作,它就实现了这个接口。
100+
101+
下面这段代码演示了许多面向对象的内容,其中,末尾的 `AllSpeak` 函数是多态的展示。
102+
103+
```go
104+
type Speaker interface {
105+
Speak()
106+
}
107+
type Person struct {
108+
Name string
109+
}
110+
func (p Person) Speak() {
111+
fmt.Println("Hello, my name is", p.Name)
112+
}
113+
type Dog struct {
114+
Name string
115+
}
116+
117+
type SingleDog struct {
118+
Dog
119+
}
120+
121+
func (d Dog) Speak() {
122+
fmt.Println("Wang Wang, my name is", d.Name)
123+
}
124+
func TestInf(t *testing.T) {
125+
126+
var s Speaker
127+
p := Person{"Tom"}
128+
s = p // 接口变量能指向实现了该接口的对象
129+
s.Speak()
130+
131+
person := Person{"Tom"}
132+
dog := Dog{"Jerry"}
133+
singleDog := SingleDog{Dog{Name: "SingleDog"}}
134+
speakers := []Speaker{person, dog, singleDog}
135+
AllSpeak(speakers)
136+
}
137+
138+
// AllSpeak 多态演示
139+
func AllSpeak(s []Speaker) {
140+
for _, v := range s {
141+
v.Speak()
142+
}
143+
```
144+
145+
Go 语言的接口是非常灵活的,不需要显示声明,Java 需要 `implements`,而 Go 是 DuckType。
146+
147+
什么是 DuckType?鸭子会嘎嘎叫,所以会嘎嘎叫的就能当成鸭子。
148+
149+
翻译成编程语言,接口是一个约定,遵循了这个约定,就实现了接口。这个遵循,只要某个类型的方法签名是某接口定义的方法签名的超集就行。
150+
151+
所以,一定记住,在编码层面,方法先于接口,即先有了方法,才有了是否实现该接口的判断。而不是 Java 的先明确实现什么接口,挖好坑,再去填。完全相反。
152+
153+
如果实现多态?接口类型的变量,可以引用实现了该接口的任何类型的变量。
154+
155+
`AllSpeak` 函数中,形参是 `Speaker` 接口的切片,实参却是 Person、Dog、SingleDog 类型。
156+
为什么能调用成功呢?因为 `Person``Dog` 都拥有 `Speak()` 方法,满足了 `Speaker` 接口定义的所有函数的签名。
157+
158+
那为什么 SingleDog 类型也能传参成功,是不是可以理解为,SingleDog 继承了 Dog 的接口?似乎变得复杂起来了。
159+
160+
Go 没有那么多术语。紧抓两点:1编码时,先看方法,再看接口 2某类型拥有的方法是某接口方法的超集,就实现了该接口
161+
162+
所以,是 SingleDog 先「继承」了Dog的 `Speak()` 方法,而后该方法刚好又满足了 `Speaker()` 的约定,自然就实现了该接口。
163+
164+
许多语言的接口与继承,对于开发者而言其实是迷惑的,可能经常分不清到底该用哪个,而 Go 不会。
165+
许多面向对象语言的继承,是被滥用的,而 Go,压根没开这个门,你只能用接口。
166+
它以一种非常灵活的方式,实现了多态,并且接口的设计,对「依赖反转(面向抽象编程)」天然友好。
167+
168+
interface 真的是个好东西,当你不知道怎么设计更优雅的时候,它总能给你带来惊喜。
169+
170+

‎common/oop/oop_standard.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## 面向对象标准实现
2+
3+
这里应该是一个标准的面向对象实现教程,如以 Java 为例。
4+
5+
这是一个漫长的过程,网上的资料也非常多,更推荐阅读广泛的资料、线下教学、切身实践来体会。
6+
7+
所以这里只贴些优质资源。
File renamed without changes.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /