Please enable Javascript to view the contents

瞎谈OOP

 ·  ☕ 6 分钟  ·  🤪 Jacklanda

最近接触了Golang,对她的OOP的实现感到亲切,因为同样是用到了结构体,所以也自觉地想到,Go的面向对象的实现也许是从C迁移来的。同时,笔者通过几个小例子来对C、Python、Go这三门语言的OOP实现进行一个简单的比较。

我们应当明确一个概念:

OOP(Object-Oriented Programming)是一种编程范式,或者说:是一种概念,一种模型

我们经常能听到这样的说法:C语言是一门面向过程的编程语言,CPP、Java、Python是面向对象的编程语言

首先,这句话总体上是没有问题的,因为面向过程的编程范式把程序视为一组函数的顺序执行过程,而C语言因为主要采取程序调用或函数调用的方式来进行程序的流程控制,所以「面向过程」作为它的”标志“是合情理的,这一点对CPP、Java、Python等其他语言亦能够得到类似的解释。

但对于一个编程菜鸟而言,这句话很容易被误解为「使用C语言只能面向过程编程」、「使用XX语言只能面向对象编程」,为了避免像这样的误解,也许在更应该强调编程范式是作为一种概念而存在的,而这种概念是可以借助不同语言的不同特性,以不同的方式去实现的。

在Python里,一切皆对象,包括所有的内置数据类型也都可以视为对象,当然我们也可以自定义对象,而自定义的对象数据类型就是OOP中的类的概念,一个类里既包含了数据,又包含了操作数据的方法,Python通过自定义类,来实现OOP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class lunchFood(object):

    def __init__(self, name, staple_food, drink):
        self.name = name
        self.staple_food = staple_food
        self.drink = drink

    def print_food(self):
        print(f"{self.name}今天的午餐->主食:{self.staple_food},饮料:{self.drink}")

old_man = lunchFood("南门大爷", "凶柿炒蛋盖饭", "北冰洋").print_food()
Lisa = lunchFood("北门大妈", "炸酱面", "豆汁").print_food()

在C里,可以通过给一个结构体定义函数,函数里引入一个该结构体类型的变量通过将结构体(数据)和给结构体绑定的方法简单实现了OOP中的「封装」特性。

在这里,创建了「南门大爷」和「北门大妈」两个同类型的对象,并赋予了这类对象一个printLunch()方法。

 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
#include <stdio.h>
#include <stdlib.h>

typedef struct{
    char* name;
    char* staple_food;
    char* drink;
}Worker;

void printLunch(Worker w, char name[], char staple_food[], char drink[]);

int main(){
    char name1[] = "南门大爷";
    char staple_food1[] = "凶柿炒蛋盖饭";
    char drink1[] = "北冰洋";
    Worker w1;

    char name2[] = "北门大妈";
    char staple_food2[] = "炸酱面";
    char drink2[] = "豆汁";
    Worder w2

    printLunch(w1, name1, staple_food1, drink1);
    printLunch(w2, name2, staple_food2, drink2)

    return 0;
}

void printLunch(Worker w, char name[], char staple_food[], char drink[]){
    w.name = name;
    w.staple_food = staple_food;
    w.drink = drink;
    printf("%s今天的午餐->主食:%s,饮料:%s", w.name, w.staple, w.drink);

    return;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* 接着上面的这个例子,我们还可以通过嵌套结构体,实现OOP中的继承机制*/
typedef struct{
    Worker worker;
    int workHour;
}busyWorker;

/* 对”子类“busyWorker创建对象并调用超类的方法 */
busyWorker sweeper;
char name3[] = "北门扫地大妈";
char staple_food3[] = "螺蛳粉";
char drink3[] = "绿豆爽";
printLunch(sweeper.worker, name3, staple_food3, drink3);

之所以猜测Go的面向对象参考了C的实现方法,并在C的基础上进行了改动,是因为Go的结构体
类型是一种同时包含字段(数据)和方法的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type lunchFood struct{
    name string
    stapleFood string
    drink string
}

func (l lunchFood) printFood(){
    fmt.Printf("%s今天的午餐->主食:%s,饮料:%s\n", l.name, l.stapleFood, l.drink)
    return
}

func main(){
    s1 := lunchFood{name:"南门大爷", stapleFood:"凶柿炒鸡蛋", drink:"北冰洋"}
    s2 := lunchFood{name:"北门大妈", stapleFood:"炸酱面", drink:"豆汁"}
    s1.printFood()
    s2.printFood()
    return
}

在Golang中,我们通过接口来实现多态这一特性。

什么是多态?在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
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
package main

import "fmt"

type Girl interface{
    chasingStar()
    dressUp()
}

type Girls []Girl

type basic struct{
    age    int
    gender string
}

type HotGirl struct{
    inSkirt    bool
    isSexy     bool
    info       basic
}

type CoolGirl struct {
    hasTattoo  bool
    isEarring  bool
    info       basic
}

func (h HotGirl) chasingStar(){
    fmt.Println("火辣的姑娘追的明星是小甜甜")
}

func (h HotGirl) dressUp(){
    fmt.Println("火辣的姑娘爱着短裙")
}

func (c CoolGirl) chasingStar(){
    fmt.Println("酷酷的姑娘追的明星是艾薇儿")
}

func (c CoolGirl) dressUp(){
    fmt.Println("酷酷的姑娘爱穿耳钉")
}

func countGirlsNum(girls Girls) int {
    var num int
        for _, girl := range girls{
            girl.chasingStar()
            girl.dressUp()
            fmt.Println()
            num += 1
        }
    return num
}

func main(){
    girls = Girls{
        HotGirl{inSkirt: true, isSexy: true, info: basic{age: 25, gender: "female"}},
        CoolGirl{hasTattoo: true, isEarring: true, info: basic{age: 22, gender: "female"}},
    }
    num := countGirlsNum(girls)
    fmt.Printf("总共有%d个姑娘", num)

    return
}
综上,我们可以得出这样的结论:
在Golang中:
  • 通过给某一类型定义方法来实现封装;
  • 通过嵌套结构体进行组合来实现继承;
  • 通过给不同类型实现同一接口来实现多态。

引用参考