接口vs抽象类

Go中有接口的概念而c++中有抽象类的概念,这两者有什么具体的区别呢?

1 接口vs抽象类

1.1 实现一个有Run方法的接口

先来看Go的接口的一个例子

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 Run interface{
run()
}

type Car struct{}

type Human struct{}

func (c Car) run() {
fmt.Println("car run");
}

func (h Human) run() {
fmt.Println("human run");
}

func run(r Run) {
r.run();
}


func main() {
var c = Car{};
var h = Human{};
run(c);
run(h);
}

1.1 实现一个有Sound方法和s(sound声音)属性的抽象类

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

typedef std::string Sound;
class Animal{
public:
Sound s; // 动物的声音
Animal() = default;
virtual void sound() = 0;
};

class Cat : public Animal {
public:
Cat() {
s = "meow";
};
virtual void sound() {
std::cout << s << s << s; // 叫3声
}
};

class Dog : public Animal {
public:
Dog() {
s = "wang";
}
virtual void sound() {
std::cout << s; // 叫1声
}
};
void sound(Animal* s) {
s->sound();
}


int main () {
Animal* c = new Cat;
Animal* d = new Dog;
sound(c);
sound(d);
}

1.3 一个小问题

提问: 假如1.1用抽象类来实现的话,接口应该叫什么?

1.4 一点思考

假设: A为接口/抽象类, B C为实现了接口/继承了抽象类的数据类型

  • 接口更多描述的是 B is like A, C is like A, 是对B C类型的一个行为的抽象,所以接口中没有属性只有方法(不懂Java不过Java中貌似可以有静态成员)

  • 而抽象类更多描述的是 B is A, C is A, 是对B C的一个对类型的复用/抽象,描述了类型本质的属性和方法

人可以跑,车可以跑,跑可以作为人和车的一个接口,描述了人和车的跑的行为

1
2
3
type Run interface {
run()
}

猫和狗都有独自的声音,都可以发出声音

1
2
3
4
5
class Animal {
public:
Sound s;
void sound();
}

人和车的相同本质并不是都会跑,而是都具有跑的行为罢了

猫和狗相同本质的是都为动物

不要为了复用代码而随意制造抽象类,比如因为车和人都可以跑,就抽象出一个不知道是什么的抽象类

2 组合vs继承

2.1 组合

组合是通过成员实例引用不同的类型来实现抽象

例子

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
class Animal {
Animal(Sound* s_) : s(s_) {}
Sound* s; // 用Sound对象组成
void sound() {
s->sound();
}
};

class Sound {
public:
voice v;
virtual void sound() = 0;
}

class CatSound : public Sound{
CatSound(voice v_): v(v_) {}
virtual void sound() {
std::cout << v << v << v;
};
};
class DogSound : public Sound{
DogSound(voice v_): v(v_) {}
virtual void sound() {
std::cout << v;
};
};

int main() {
Animal a0(new CatSound);
Animal a1(new DogSound);
}

2.2 继承

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
class Animal {
Animal() {}
voice v;
virtual void sound() = 0;
};

class Cat : public Animal{
Cat(voice v_) : v(v_) {}
virtual void sound() {
std::cout << v << v << v;
};
};
class Dog : public Animal{
Dog(voice v_) : v(v_) {}
virtual void sound() {
std::cout << v;
};
};

int main() {
voice cat_voice;
voice dog_voice;
Animal* a0 = new Cat(cat_voice);
Animal* a1 = new Dog(dog_voice);
}

2.3 对比

2.2继承中现在子类依赖父类[耦合], 导致父类的修改引起子类爆炸

2.1组合中则不会有这种问题,因为Animal与Sound是耦合度很低的[保持接口不变的情况下]