1

Go语言中的面向对象

 2 years ago
source link: http://kangkona.github.io/oo-in-golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go语言中的面向对象

8/Jun 2015

最近在思考Go语言中面向对象实现,感觉最初的设计者真是掐准了软件工程的命脉,优雅与实用恰到好处的结合,使得这门语言于平凡处见深刻。

下面来剖析一下其中的一些设计点。

golang中没有class关键字,却引入了type,二者不是简单的替换那么简单,type表达的涵义远比class要广。 主流的面向对象语言(C++, Java)不太强调类与类型的区别,本着一切皆对象的原则,类被设计成了一个对象生成器。这些语言中的类型是以类为基础的,即通过类来定义类型,类是这类语言的根基。与之不同,golang中更强调类型,你在这门语言中根本看不到类的影子。实现上述传统语言的class只是type功能的一部分:

  type Mutex struct {
      state int32
      sema  uint32
  }

此外,type还可以扩展已经定义的类型:

    type Num int32
    func (num Num) IsBigger(otherNum Num) bool {
        return num > otherNum
    }

这种灵活的定义方式可以很好地提高程序的可扩展性,通过重命名原有类型,也可以做到一定程序上的解耦。

传统对象型语言由于设计之初追求面向对象的彻底性,使得后来加入函数式对象时不得不Hack一把:C++很鸡贼地重载 () 实现:

class Adder{
   public:
     int operator() (int a, int b) {
        return a+b;
     }
};
int add(int a, int b, Adder& adder) {
    return adder(a,b);
}
add(1, 3, new Adder);

Java则憋了很久才憋出 FunctionalInterface (只有一个抽象方法的接口)可以无缝地与历史包袱兼容:

public interface Displayer {
	void display();
}

//Test class to implement above interface
public class FunctionInterfaceTestImpl {
     public static void main(String[] args) {
     //Old way using anonymous inner class
     Displayer oldWay = new Displayer(){
        public void display(){
           System.out.println("Display from old way");
        }};
     OldWay.display();//outputs: Display from old way
     
     //Using lambda expression
     Displayer newWay = () -> {System.out.println("Display from new Lambda Expression");}
     newWay.display();//outputs : Display from new Lambda Expression
    }
}

而在golang中,借助type的威力,定义函数式类型和定义一般类型并无区别:

type Traveser func(ele interface{})
type Filter func(ele interface{}) bool

方法放在哪里

golang与传统对象式语言的另一个不同是方法并不在类的定义范围之内,而是通过把类作为接收器(receiver)与方法进行绑定:

// Once is an object that will perform exactly one action.
type Once struct {
	m    Mutex
	done uint32
}

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	// Slow-path.
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

看起来仅仅是放置位置的不同,其实是设计理念的不同。将方法放在类定义里面,意味着方法是类不可分割的一部分,类的最小单位就是数据成员和当前定义的所有操作,你要认识这个类,必须一次性认识这个类中定义的所有的东西。相反,先定义类的数据结构,然后像搭积木一样将目前需要的方法一个一个地进行绑定,你便可以根据需求对类进行扩展。传统的类定义是你必须一开始便想好这个类有哪些操作,一旦类定义好了,类就成了你定义的样子,再无其他可能。golang的这种开放式扩展定义方式,使得类更加具有生命力,你不必一开始就设计好一切(往往也很难做到),类会随着你的实现思路逐渐成长为你想要的那个样子。

组合还是继承

继承是面向对象鼓吹的三大特性之一,但经过多年的实践,业界普遍认识到继承带来的弊端:

  • 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
  • 对扩展支持不好,往往以增加系统结构的复杂度为代价
  • 不支持动态继承。在运行时,子类无法选择不同的父类
  • 子类不能改变父类的接口
  • 对具体类的重载,重写会破会里氏替换原则

golang的设计者意识到了继承的这些问题,在语言设计之初便拿掉了继承。其实也不能说golang里面没有继承,只不过继承是用匿名组合实现的,没有传统的的继承关系链(父类和子类完全是不同类型), 同时还能重用父类的方法与成员。

type Base struct{
}

func (b Base)Show(){
   println("Bazinga!")
}

type Child struct{
	Base
}

func main() {
   child := Child{}
   child.Show()
}

这种用Has-A代替Is-A的模拟实现,既解决了一些软件工程问题,同时甩掉了很多困扰程序员的心智包袱。

非侵入式接口

学CS到现在,感觉计算机科学的精髓其实就两个字:abstracttradeoff。 golang中的非侵入式接口便很好地体现了这两点。传统对象式语言里面有一堆与接口相关的东西:抽象类,抽象接口,虚函数,纯虚函数等等。概念虽多,说起来不过是在不同abstrct层面上进行tradeoff而已。 golang的接口很彻底,就是一系列操作定义的集合,根本不允许进行实现,而且也不能定义变量或者常量这些东东:

  type Interface interface {
     // Len is the number of elements in the collection.
     Len() int
     // Less reports whether the element with
     // index i should sort before the element with index j.
     Less(i, j int) bool
     // Swap swaps the elements with indexes i and j.
     Swap(i, j int)
    }

记得有位大学老师把Java中的接口比作资格证书,你要去考资格证书,并达到资格证书中的所有要求,才算具有某些资质。golang的接口则不太一样,只要你能做到资格证书中规定的那些事情,不管你去不去考这个资格证,都认为你具有了资质。其实这种想法在一些动态语言中实现过,且有诗为证:

如果一个人看起来像鸭子,走起来像鸭子,叫起来像鸭子,那么他就是个基佬。

这种非侵入式的设计方式,很大程度上也是为了解耦。接口和类本就是不同的东西:类是为了把数据和代码包装在一起,是为了对内实现;接口则更像是一种契约,是为了对外展示。基于这种抽象层面的接口进行编程,很容易达到设计模式中的依赖倒置,接口隔离以及迪米特法则几个原则。

编程语言的进化固然可以带来一些工程上的好处, 但千万不要忘了那句古训:

There is no silver bullet.

Tags// PL, Go,
More Reading
Newer// 日落北京城
Older// PL Meets AI

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK