为基本数据类型添加成员方法

在 Go 语言中,你可以给任意类型(包括基本类型,但不包括指针类型)添加成员方法,但是如果是基本类型的话,需要借助 type 关键字对类型进行再定义,例如:

type Integer int

func (a Integer) Equal(b Integer) bool {
    return a == b
}

注意,这个时候 Integer 已经是一个新的类型了,这与 type Integer = int 不同,后者只是为 int 类型设置一个别名。

在这个例子中,我们定义了一个新类型 Integer,它和 int 没有本质不同,只是它为内置的 int 类型增加了个新方法 Equal()。 这样一来,就可以让基本类型的整型像一个普通的类一样使用:

func main()  {
    var a Integer = 2
    if a.Equal(2) {
        fmt.Println(a, "is equal to 2")
    }
}

这有点类似 Java 中的装箱功能(boxing),即将基本类型转化为对应的对象类型,这个工作可以自动完成,也可以调用 valueOf 方法手动完成,只不过在 Go 语言中,我们可以通过上面这种自定义方式轻松实现基本类型与面向对象类型的转换,我们还可以为自定义的 Integer 类添加其它成员方法:

func (a Integer) LessThan(b Integer) bool {
    return a < b
}

func (a Integer) MoreThan(b Integer) bool {
    return a > b
}

func (a *Integer) increase(i Integer) {
    *a = *a + i
}

func (a *Integer) decrease(i Integer) {
    *a = *a - i
}

然后我们修改调用代码如下:

var a Integer = 1
var b Integer = 2

if a.Equal(b) {
    fmt.Printf("%d 等于 %d\n", a, b)
} else if a.LessThan(b) {
    fmt.Printf("%d 小于 %d\n", a, b)
} else {
    fmt.Printf("%d 大于 %d\n", a, b)
}

a.Increase(b)
fmt.Println(a)
a.Decrease(b)
fmt.Println(a)

上述代码的打印结果是:

1 小于 2
3
1

在 Go 语言中,面向对象的神秘面纱被剥得一干二净,没有隐藏的 this 指针(也没有 PHP 中的 selfparent 之类的关键字),没有隐式执行的构造函数和析构函数,方法和属性的可见性不是通过 privateprotectedpublic 之类的关键字来实现,这些传统面向对象编程包含的隐晦术语在 Go 语言中都被显式代码所替代,一切都是所见即所得,所写即所得。

如果是在 PHP 中,对应的实现如下(以 PHP7 代码为例,支持声明参数类型和返回值类型):

class Integer
{
    private $val;

    public function __construct($val)
    {
        $this->val = $val;
    }

    public function equal(int $i):bool
    {
        return $this->val == $i ? true : false;
    }

    public function less_than(int $i):bool
    {
        return $this->val < $i ? true : false;
    }

    public function more_than(int $i):bool
    {
        return $this->val > $i ? true : false;
    }

    protected function increase(int $i):void
    {
        $this->val += $i;
    }

    protected function decrease(int $i):void
    {
        $this->val -= $i;
    }
}

对于这段 PHP 代码,初学者要理解其背后的机制,还要做一番解释,比如 $this 从何而来,__construct 函数做什么用,privatepublicprotected 关键字声明的属性和方法有什么区别。

Go 语言中的面向对象最为直观,无需额外的学习和理解成本,只有在你需要修改对象的时候,才必须用指针(引用传递),否则将所属类型声明为字面量即可(值传递),而在 PHP、Java 之类传统面向对象编程实践中,所有方法里面都使用隐藏的 this 指针(静态方法除外)作为当前对象实例的引用。

另外,需要声明的是,在 Go 语言中,当我们将成员方法所属的类型声明为指针类型时,比如 increase 方法:

func (a *Integer) increase(i Integer) {
    *a = *a + i
}

严格来说,该方法并不属于 Integer 类,而是属于指向 Integer 的指针类型,所以,归属于 Integer 的成员方法只是 Integer 类型下所有可用成员方法的子集,归属于 *Integer 的成员方法才是 Integer 类完整可用方法的集合,我们在调用方法时,之所以可以直接在 a 实例上调用 increase 方法,是因为 Go 语言底层会自动将 a 转化为对应的指针类型 &a,所以真正调用的代码是 (&a).increase(b),这一点需要大家知晓。

总结下来就是一个自定义数据类型的方法集合中仅会包含它的所有「值方法」,而该类型对应的指针类型包含的方法集合才囊括了该类型的所有方法,包括所有「值方法」和「指针方法」。

介绍完构造函数、可见性、this 指针在 Go 语言面向对象编程中的实现后,下一篇学院君将给大家介绍 Go 语言中类的继承和方法重写是如何实现的。

上一篇: 类的定义、初始化和成员方法

下一篇: 通过组合实现类的继承和方法重写