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

代码重用

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

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

    原文:https://klionsec.github.io/2017/11/23/phpsec/#menu After compile install php 5.3.1, I can not fin ...

  2. 消息列队 php 基于redis 实现

    说明 消息列队 基于PHP 实现. 之前 用python 的 flower 实现了 列队. 今天这里我们用的是 PHP 来实现: 在实际的业务环境中 PHP 用的多些: PHP 实现列队 最重要的是用 ...

  3. Intel CPU Microarchitecture

    http://en.wikipedia.org/wiki/Intel_Tick_Tock Atom Roadmap[16]   Fabrication process Microarchitectur ...

  4. HDU 2108 Shape of HDU (判断是不是凸多边形 叉乘)

    Shape of HDU Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tota ...

  5. long long , __int64 范围

    VC的64位整数 分别叫做__int64与unsigned __int64,其范 围分别是[-2^63, 2^63)与[0,2^64),即-9223372036854775808~9223372036 ...

  6. PHP中的多行字符串传递给JavaScript方法两则

    PHP和JavaScript都是初学.近期有这么个需求: 例如说有一个PHP的多行字符串: $a = <<<EOF thy38 csdn blog EOF; 传递给JavaScrip ...

  7. 几个简单的程序看PHP的垃圾回收机制

    每一种计算机语言都有自己的自动垃圾回收机制,让程序员不必过分关心程序内存分配,php也不例外,但是在面向对象编程(OOP)编程中,有些对象需要显式的销毁,防止程序执行内存溢出. 一.PHP 垃圾回收机 ...

  8. Tomcat 80端口 配置及域名访问步骤

    一.修改端口tomcat默认监听端口是8080,我们如果想不带端口的直接访问项目,就必须监听80 端口: service.xml 以下代码段 <Connector port="8080 ...

  9. python day - 19 抽象类 接口类 多态 封装

    一. 抽象类接口类即制定一个规范 特点: 1.不可被实例化. 2.规范子类当中必须事先某个方法. 3.在python中有原生实现抽象类的方法,但没有原生实现接口类的方法. 例题:制定一个规范就是,子类 ...

  10. https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2

    https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2