Go语言的面向对象模型初探

Go语言的面向对象模型与主流OO语言差异很大,本文通过对比Go与C++的三个差异来介绍Go的面向对象模型及其设计思想。

一:可见性控制粒度是包

Go用首写字母的大小写来控制类、类成员、函数的可见性, 可见性控制的粒度是包。下面是Go和C++对Person的实现:

Go:

type Person struct {

name string  //首字母小写,包外不可见

Age  int    //首字母大写,包外可见

}

C++:

struct Person{

private: std::string name;  //类外不可见

public: int age;        //类外可见

};

要理解Go的做法,首先我们要问为什么要控制可见性?是为了隐藏实现细节。那么为什么要隐藏实现细节?是为了尽可能地减少对客户代码的影响。说到底是为了让客户能更容易地重用代码,所以可见性控制粒度应该与重用粒度相一致。Go认为重用粒度是包,因此只控制包的可见性。当然从完美角度看,这种做法会增加包内类之间的耦合,但是它也避免了新增友元特性以支持包内类之间的更密切的关联,这使得语言特性保持简洁, 而简洁正是Go语言的追求目标。

二:没有继承,只有组合

Go没有继承,只有组合。类功能复用可以通过匿名组合实现。下面是Go和C++对Teacher的实现:

Go:

type Teacher struct{

Person

school string

}

C++:

struct Teacher:Person{

private: std::string school;

};

继承曾经被认为是OO最重要的特性。随着OO实践的深入,社区才逐渐认识到继承的弊端。实际上,当我们深入研究继承,就会发现它同时干了两件事情:

1、复用实现。

2、IS-A语义。

对于第一点,使用组合远比继承要更优秀,因为组合是黑盒复用,继承是白盒复用,复杂的继承树大大加重了程序员的心智负担。

对于第二点,IS-A语义的威力只有当我们基于接口进行编程(把IS-A理解为接口)时,才能充分地发挥。但是接口本质上是一种抽象,而这种抽象依赖于client,也就是说如果用继承,我们被迫要在实现类的时候对client的使用做适当地预测,否则就很难实现ISP,DIP这些设计原则。

既然继承做了两件事,而且做的都不好,Go就把继承拆分为两个更加单一的特性:匿名组合、Interface。通过匿名组合来复用实现,通过Interface支持基于接口的编程。

三:类型安全的鸭子类型

在第二节我们提到Go没有继承,Go也没有虚函数,它通过Interface实现IS-A语义来支持基于接口的编程,下面用Go和C++分别实现鸭子、野鸭子、打飞鸟的示例:

Go:

type Duck struct {//鸭子

location Location //鸭子当前位置

}

func (duck *Duck) GetLocation() Location {//获取鸭子当前所处位置

return duck.location

}

type WildDuck struct {//野鸭子

Duck

}

func (wildDuck *WildDuck) Fly() {//飞走

}

type Flyer interface {//飞鸟

Fly()

GetLocation() Location

}

func ShotFlyer(location Location, flyer Flyer) {//打飞鸟

if location != flyer.GetLocation() { //没打中飞走了

flyer.Fly()

}

}

func TestShotFlyer() {

flyer := new(WildDuck)

ShotFlyer(Location{1, 2, 3}, flyer)

}

C++:

struct Flyer{//飞鸟

virtual void Fly()=0;

virtual const Location& GetLocation()=0;

};

struct Duck{//鸭子

void const Location& GetDuckLocation()

private: Location location;

};

struct WildDuck:Duck, Flyer{//野鸭子

private: virtual void Fly(){}

private: virtual const Location& GetLocation(){return GetDuckLocation();}

};

void ShotFlyer(const Location& location, Flyer &flyer) {//打野鸭子

if (location != flyer.GetLocation()) {//没打中飞走了

flyer.Fly()

}

}

void TestShotFlyer() {

Flyer* flyer := new WildDuck()

ShotFlyer(Location(1,2,3), flyer)

}

我们先来看Go的实现,WildDuck通过匿名组合Duck来复用Duck的GetLocation方法,为了能让ShotFlyer基于Flyer接口编程,WildDuck并不需要继承Flyer,只要实现了Flyer的所有方法,就能让编译器认为它就是Flyer,Flyer与WildDuck之间是松耦合的关系。

再看C++实现,WildDuck需要继承Flyer接口让编译器认为它就是Flyer,但是WildDuck不能直接复用Duck来实现Flyer的GetLocation方法,因为在编译器看来Duck不是Flyer,那么它就不能实现Flyer的方法,所以WildDuck只能自己实现虚函数GetLocation,通过GetLocation调用Duck的GetDuckLocation来复用Duck的获取当前位置功能。

从上面比较可以看出,Go的实现比C++的更加优雅,这种优雅是由于接口与实现的松耦合带来的。松耦合可以让接口与实现相对独立地演进;可以各自通过组合实现功能复用;也可以在实现具体类之后,无需修改具体类就能新增抽象接口以应对不同的应用场景(这个正是人解决问题的常用方式,先具体再抽象)。

时间: 2024-08-10 04:05:29

Go语言的面向对象模型初探的相关文章

C++面向对象模型初探

C++面向对象模型初探 1. 基础知识 C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类.从计算机的角度,程序依然由数据段和代码段构成. C++编译器如何完成面向对象理论到计算机程序的转化? 换句话:C++编译器是如何管理类.对象.类和对象之间的关系 具体的说:具体对象调用类写的方法,那,c++编译器是如何区分,是那个具体的类,调用这个方法那? 思考一下程序结果 #include "iostream" using namespa

【C/C++学院】(7)C++面向对象模型初探专题

1. 基础知识 C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类.从计算机的角度,程序依然由数据段和代码段构成. #include "iostream" using namespace std; class C1 { public: int i; //4 int j; //4 int k; //4 protected: private: }; //12 class C2 { public: int i; //4 int j; /

PowerDesigner(八)-面向对象模型(用例图,序列图,类图,生成Java源代码及Java源代码生成类图)(转)

面向对象模型 面向对象模型是利用UML(统一建模语言)的图形来描述系统结构的模型,它从不同角度实现系统的工作状态.这些图形有助于用户,管理人员,系统分析人员,开发人员,测试人员和其他人员之间进行信息交流.这里主要介绍用例图,序列图和类图.   1.面向对象模型OOM 面向对象模型是利用UML的图形描述系统结构的模型,可以利用PowerDesigner的面向对象模型进行创建.PowerDesigner支持UML的下列图形. 用例图(User Case Diagram):通常用来定义系统的高层次草图

脚本语言,面向对象语言,面向并发语言-杂谈

我 15:40 我看python写的是初学者的语言 这个有几层含义呢 是这个语言不够强大么? 永神 15:41 嗯 我 15:41 还是说这个语言简单易学? 永神 15:41 比较简单 应该是脚本类的语言 肯定不如JAVA C++强大 一般程序员,拿来作为一个辅助的技能 而不是专门作为一个职业 我 15:42 那java这些算什么语言啊 高级程序开发语言? 永神 15:42 JAVA啊,面向对象语言,是一个体系 高级程序语言指的是对计算机来说高级 我 15:43 那linux的shell呢?和p

C++ 空类及类的大小(C++面向对象模型有提及)

初学者在学习面向对象的程序设计语言时,或多或少的都些疑问,我们写的代码与最终生编译成的代码却 大相径庭,我们并不知道编译器在后台做了什么工作.这些都是由于我们仅停留在语言层的原因,所谓语言层就是教会我们一些基本的语法法则,但不会告诉我们为什么这么做?今天和大家谈的一点感悟就是我在学习编程过程中的一点经验,是编译器这方面的一个具体功能. 首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配一块地址. 那我们先看看一个例子: #include<iostream.h> class a {

C++面向对象模型

1. 基础知识 C++编译器怎样完毕面向对象理论到计算机程序的转化? 换句话:C++编译器是怎样管理类.对象.类和对象之间的关系 详细的说:详细对象调用类写的方法,那,c++编译器是怎样区分,是那个详细的类.调用这种方法那? 思考一下程序结果 #include "iostream" using namespace std; class C1 { public: int i; //4 int j; //4 int k; //4 protected: private: }; //sizeo

C语言与内存模型初探

#include<stdio.h> #include<string.h> int main(){ long long int a = 2<<30; char string[] = "Hello China1!"; char string2[] = "Hello China2!"; if(0==strcmp(string,string2)) { printf(string); printf("\n"); } el

R语言:关系网络初探

社会网络分析(Social Network Analysis,SNA)逐步成为数据挖掘领域的又一新宠.SNA的本质是利用各样本间的关系(故也成为关系网络)来分析整体样本的群落现象,并分析出样本点在群落形成的作用以及群落间的关系.利用R语言中的igraph包实现SNA. 用R语言建立关系网络 (1) 原始数据准备 from<-c("a","a","e","b","b","c",&qu

C语言栈调用机制初探

学习linux离不开c语言,也离不开汇编,二者之间的相互调用在源代码中几乎随处可见.所以必须清楚地理解c语言背后的汇编结果才能更好地读懂linux中相关的代码.否则会有很多疑惑,比如在head.s中会看到调用main函数,在调用之前会看到几次压栈行为,在<linux内核完全注释>一书中会看到这几句汇编后面的注释说是为main函数的参数进行压栈,可是查看main的代码发现main函数根本不需要任何参数,这里为什么会有几次压入参数的动作呢?再比如fork函数中会看到有众多参数,但在调用这时却没有看