故事要从我在一个项目中,想要假装的专业一点而遇到的一个陷阱说起。

代码重用

在这个项目中,我们已经有了类似如下的代码:

package main

import (
"fmt"
) func main() {
user := &User{name: "Chris"}
user.sayHi()
} type User struct {
name string
} func (u *User) sayHi() {
u.sayName()
u.sayType()
} func (u *User) sayName() {
fmt.Printf("I am %s.", u.name)
} func (u *User) sayType() {
fmt.Println("I am a user.")
}
I am Chris.I am a user.

然后我接到的新需求是这样的,我需要开发一种新的用户,它和当前这种用户有一些相同的行为。当然,最主要的是也有很多不同的行为。作为一名老司机,我当然知道,这些不同的地方才是我需要重点关注并且实现的。

为了区分这两种用户,我们就叫他们普通用户和文艺用户吧。

因为我们已经有了普通用户的实现代码了,作为一个资深(误)Java工程师,我想通过继承这个普通用户来实现代码的复用。然而悲伤辣么大,我发现在Go语言中是不支持继承的。

嵌入类型

好吧,只要思想不滑坡,办法总比困难多。我发现在Go中有一种叫做Embedding的东西。在网上的一些文章中,他们说这就是Go中实现继承的方式。可是看起来,这更像是Java中的组合,至少语法上像,是不?

package main

import (
"fmt"
) func main() {
artisticUser := &ArtisticUser{User: &User{name: "Chris"}}
artisticUser.sayName()
artisticUser.sayType()
} type User struct {
name string
} func (u *User) sayHi() {
u.sayName()
u.sayType()
} func (u *User) sayName() {
fmt.Printf("I am %s.", u.name)
} func (u *User) sayType() {
fmt.Println("I am a user.")
} type ArtisticUser struct {
*User
} func (u *ArtisticUser) sayType() {
fmt.Println("I am an artistic user.")
}
I am Chris.I am an artistic user.

干得漂亮!这样我就可以复用User的sayName方法,只要把sayType方法用我自己的逻辑实现就好了。这正是我想要的。

继承?组合?

但是,少侠请留步!我们试一下sayHi方法看看会发生什么?

package main

import (
"fmt"
) func main() {
artisticUser := &ArtisticUser{User: &User{name: "Chris"}}
artisticUser.sayHi()
} type User struct {
name string
} func (u *User) sayHi() {
u.sayName()
u.sayType()
} func (u *User) sayName() {
fmt.Printf("I am %s.", u.name)
} func (u *User) sayType() {
fmt.Println("I am a user.")
} type ArtisticUser struct {
*User
} func (a *ArtisticUser) sayType() {
fmt.Println("I am an artistic user.")
}
I am Chris.I am a user.

这不科学!在Java里,子类总是会调用自己的方法的(已经override了父类的方法)。除非子类没有覆盖父类的方法,才会使用从父类继承来的方法。

在这个例子中,我override了(其实Go中没有这个概念)sayType方法,但是当我们在sayHi中调用它时,却没有调用这个override方法,而是用了父类的原始方法。

实际上,类型嵌入不是继承。它只是某种形式上的语法糖而已。在面向对象编程中,子类应该是可以被当做父类来使用的。在里氏替换原则中,子类应该能在任何需要的地方替换掉父类。(注意一点,我们这里一开始尝试覆盖父类的非抽象方法已经违背了里氏替换原则)。

但是在上边的例子中,ArtisticUser和User是两种不同的类型。且不能替换使用。

package main

import (
"fmt"
) func main() {
user := &User{name: "Chris"}
artisticUser := &ArtisticUser{User: user}
fmt.Printf("user's type is: %T\n", user)
fmt.Printf("artisticUser's type is: %T\n", artisticUser)
acceptUser(user)
//acceptUser(artisticUser)
} type User struct {
name string
} func (u *User) sayHi() {
u.sayName()
u.sayType()
} func (u *User) sayName() {
fmt.Printf("I am %s.", u.name)
} func (u *User) sayType() {
fmt.Println("I am a user.")
} type ArtisticUser struct {
*User
} func (a *ArtisticUser) sayType() {
fmt.Println("I am an artistic user.")
} func acceptUser(u *User) { }
user's type is: *main.User
artisticUser's type is: *main.ArtisticUser

如果你尝试去掉注释掉的那一行,你会得到一个build错误:

cannot use artisticUser (type *ArtisticUser) as type *User in argument to acceptUser

要我说,嵌入类型既不是继承,也不是组合,只是跟它们都有点像。

多态性

那么回到我的问题。事实上我一开始就不该尝试继承。即使Go提供了继承机制,覆盖一个父类的非抽象方法也将破坏里氏替换原则。我一开始想要试试继承其实是一种偷懒的行为,因为我并不想重构已有的那么一大坨代码。

但是我们不应该害怕重构。你看,就算我想试着逃避重构,还是掉进别的沟里了。

如果能重来,我要选李白。。。呸,如果能让我重构已有代码的话,也许我可以试试接口。在Go语言中,接口非常灵活,是实现多态的手段。

package main

import (
"fmt"
) func main() {
user := &User{name: "Chris"}
user.ISubUser = &NormalUser{}
user.sayHi()
user.ISubUser = &ArtisticUser{}
user.sayHi()
} type ISubUser interface {
sayType()
} type User struct {
name string
ISubUser
} func (u *User) sayHi() {
u.sayName()
u.sayType()
} func (u *User) sayName() {
fmt.Printf("I am %s.", u.name)
} type NormalUser struct { } func (n *NormalUser) sayType() {
fmt.Println("I am a normal user.")
} type ArtisticUser struct { } func (a *ArtisticUser) sayType() {
fmt.Println("I am an artistic user.")
}
I am Chris.I am a normal user.
I am Chris.I am a artistic user.

这样我就重用了sayName和sayHi方法,并且把sayType方法留给多态来实现。

完美。

Go语言中的代码重用 - 继承还是组合?的更多相关文章

  1. 《C++ Primer Plus》读书笔记之十二—C++中的代码重用

    第14章 C++中的代码重用 1.C++代码重用方法:公有继承.使用本身是另一个类的对象的类成员(这种方法称为包含.组合或层次化).私有或保护继承.类模板等. 2.模板特性意味着声明对象时,必须指定具 ...

  2. C++ primer plus读书笔记——第14章 C++中的代码重用

    第14章 C++中的代码重用 1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现).获得接口是is-a关系的组成部分.而使用组合,类可以获得实现,但不能获得接口. ...

  3. C++进阶--代码复用 继承vs组合

    //############################################################################ /* * 代码复用: 继承 vs 组合 * ...

  4. 第16讲——C++中的代码重用

    C++的一个主要目标是促进代码重用.除了我们之前学的公有继承,我们在这一讲将介绍另一种代码重用的方法——类模板.

  5. c语言中一种典型的排列组合算法

    c语言中的全排列算法和组合数算法在实际问题中应用非常之广,但算法有许许多多,而我个人认为方法不必记太多,最好只记熟一种即可,一招鲜亦可吃遍天 全排列: #include<stdio.h> ...

  6. 《C++ Primer Plus》第14章 C++中的代码重用 学习笔记

    C++提供了集中重用代码的手段.第13章介绍的共有继承能够建立is-a关系,这样派生类可以重用基类的代码.私有继承和保护继承也使得能够重用基类的代码,单建立的是has-a关系.使用私有继承时,积累的公 ...

  7. 52. 模版和设计元素——Lotus Notes的代码重用

    不论是理论上还是实用上,代码重用都是编程的一个重要议题.可以从两个角度来讨论代码重用. 一是逻辑上代码以怎样的方式被重用.既可以通过面向对象的思想普及以来耳熟能详的继承的方式.比如先建了一个车的基类, ...

  8. Go语言中的面向对象

    前言 如果说最纯粹的面向对象语言,我觉得是Java无疑.而且Java语言的面向对象也是很直观,很容易理解的.class是基础,其他都是要写在class里的. 最近学习了Go语言,有了一些对比和思考.虽 ...

  9. OO的片段,继承与组合,继承的优点与目的,虚机制在构造函数中不工作

    摘自C++编程思想: ------------------------------ 继承与组合:接口的重用 ------------------------------- 继承和组合都允许由已存在的类 ...

随机推荐

  1. PHP网站渗透中的奇技淫巧:检查相等时的漏洞

    PHP是现在网站中最为常用的后端语言之一,是一种类型系统 动态.弱类型的面向对象式编程语言.可以嵌入HTML文本中,是目前最流行的web后端语言之一,并且可以和Web Server 如apache和n ...

  2. qt动画入门

    Qt-4.6新增了Animation Framework(动画框架),让我们可以方便的写一些生动的程序. 不必像曾经的版本号一样,全部的控件都枯燥的呆在伟大光荣的QLayout里,或许它们可以唱个歌, ...

  3. Go与C语言的互操作 cgo

    http://tonybai.com/2012/09/26/interoperability-between-go-and-c/ // foo.h int count; void foo(); //f ...

  4. cs6 mac 破解方法

    Photoshop CS6 重点功能: 1.Photoshop CS6 包含Photoshop CS6和Photoshop CS6 Extended中所有功能,快去试一试3D图像编辑和Photosho ...

  5. Dell服务器相关操作

    1. Raid相关: # 删除已有的Raid配置 MegaCli64 -CfgLdDel -LALL -aALL # 获取设备ID和槽号 MegaCli64 -PDList -aALL|egrep ' ...

  6. linux 输入子系统(4) intput_dev 接口描述

    Name struct input_dev - represents an input device Synopsis struct input_dev { const char * name; // ...

  7. Android Camera系统深入理解

    1. Android Camera系统架构 http://blog.csdn.net/myarrow/article/details/8489674

  8. ZOJ 3874 Permutation Graph 分治NTT

    Permutation Graph Time Limit: 2 Seconds      Memory Limit: 65536 KB Edward has a permutation {a1, a2 ...

  9. debian var目录

    1 /usr和/var /usr,只读数据. /var,可变数据. 2 /var/lib和/var/cache /var/lib,保存应用或者系统可变的状态信息,真的只是状态信息,比如/var/lib ...

  10. Scaling with Microservices and Vertical Decomposition

    Scaling with Microservices and Vertical Decomposition – dev.otto.de https://dev.otto.de/2014/07/29/s ...