go语言的面向对象模型

      Go语言的面向对象模型与主流OO语言差别很大,本文经过对比GoC++的三个差别来介绍Go的面向对象模型及其设计思想。程序员

 

一:可见性控制粒度是包编程

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

Goide

type Person struct {函数

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

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

}orm

 

C++对象

struct Person{继承

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

    public int age;        //类外可见

};

 

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

 

二:没有继承,只有组合

Go没有继承,只有组合。类功能复用能够经过匿名组合实现。下面是GoC++Teacher的实现:

Go:

type Teacher struct{

    Person

    school string

}

 

C++:

struct Teacher:Person{

    private: std::string school;

};

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

1、复用实现。

2IS-A语义。

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

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

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

 

三:类型安全的鸭子类型

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

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来复用DuckGetLocation方法,为了能让ShotFlyer基于Flyer接口编程,WildDuck并不须要继承Flyer,只要实现了Flyer的全部方法,就能让编译器认为它就是FlyerFlyerWildDuck之间是松耦合的关系。

再看C++实现,WildDuck须要继承Flyer接口让编译器认为它就是Flyer,可是WildDuck不能直接复用Duck来实现FlyerGetLocation方法,由于在编译器看来Duck不是Flyer,那么它就不能实现Flyer的方法,因此WildDuck只能本身实现虚函数GetLocation,经过GetLocation调用DuckGetDuckLocation来复用Duck的获取当前位置功能。

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

相关文章
相关标签/搜索