Go语言中的代码重用 - 继承还是组合?
故事要从我在一个项目中,想要假装的专业一点而遇到的一个陷阱说起。
代码重用
在这个项目中,我们已经有了类似如下的代码:
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语言中的代码重用 - 继承还是组合?的更多相关文章
- 《C++ Primer Plus》读书笔记之十二—C++中的代码重用
第14章 C++中的代码重用 1.C++代码重用方法:公有继承.使用本身是另一个类的对象的类成员(这种方法称为包含.组合或层次化).私有或保护继承.类模板等. 2.模板特性意味着声明对象时,必须指定具 ...
- C++ primer plus读书笔记——第14章 C++中的代码重用
第14章 C++中的代码重用 1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现).获得接口是is-a关系的组成部分.而使用组合,类可以获得实现,但不能获得接口. ...
- C++进阶--代码复用 继承vs组合
//############################################################################ /* * 代码复用: 继承 vs 组合 * ...
- 第16讲——C++中的代码重用
C++的一个主要目标是促进代码重用.除了我们之前学的公有继承,我们在这一讲将介绍另一种代码重用的方法——类模板.
- c语言中一种典型的排列组合算法
c语言中的全排列算法和组合数算法在实际问题中应用非常之广,但算法有许许多多,而我个人认为方法不必记太多,最好只记熟一种即可,一招鲜亦可吃遍天 全排列: #include<stdio.h> ...
- 《C++ Primer Plus》第14章 C++中的代码重用 学习笔记
C++提供了集中重用代码的手段.第13章介绍的共有继承能够建立is-a关系,这样派生类可以重用基类的代码.私有继承和保护继承也使得能够重用基类的代码,单建立的是has-a关系.使用私有继承时,积累的公 ...
- 52. 模版和设计元素——Lotus Notes的代码重用
不论是理论上还是实用上,代码重用都是编程的一个重要议题.可以从两个角度来讨论代码重用. 一是逻辑上代码以怎样的方式被重用.既可以通过面向对象的思想普及以来耳熟能详的继承的方式.比如先建了一个车的基类, ...
- Go语言中的面向对象
前言 如果说最纯粹的面向对象语言,我觉得是Java无疑.而且Java语言的面向对象也是很直观,很容易理解的.class是基础,其他都是要写在class里的. 最近学习了Go语言,有了一些对比和思考.虽 ...
- OO的片段,继承与组合,继承的优点与目的,虚机制在构造函数中不工作
摘自C++编程思想: ------------------------------ 继承与组合:接口的重用 ------------------------------- 继承和组合都允许由已存在的类 ...
随机推荐
- 数据库系统学习(六)-SQL语言基本操作
第六讲 SQL语言概述 基本命名操作 关系代数是集合的思想 关系演算是逻辑的思想(数学公式) SQL-86,SQL-89,SQL-92,SQL-99,SQL-2003,2008...发展过程标准 SQ ...
- python各种类型转换
python各种类型转换 学习了:https://blog.csdn.net/shanliangliuxing/article/details/7920400 https://blog.csdn.ne ...
- IntelliJ IDEA 默认需要进行maven的设置
IntelliJ IDEA 默认需要进行maven的设置 需要指定maven的地址,指定settings.xml的地址: 可以默认的在user/.m2/下面放一个settings.xml文件: 学习: ...
- leetcode 刷题之路 68 Gas Station
There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. You ...
- js 日期 (10 + '').length == 10 ? '0' + 10 : 10;
js 日期 (10 + '').length == 10 ? '0' + 10 : 10;
- iOS多线程编程(四)------ GCD(Grand Central Dispatch)
一.简单介绍 是基于C语言开发的一套多线程开发机制.也是眼下苹果官方推荐的多线程开发方法.用起来也最简单.仅仅是它基于C语言开发,并不像NSOperation是面向对象的开发.而是全然面向过程的.假设 ...
- 第一次OllyDbg逆向记录(分析思路和注意点&其他文章)
OllyDbg 操作菜单栏.工具栏.快捷键 C++调用加强 目录 OllyDbg 操作菜单栏.工具栏.快捷键 1 一. 载入观察 1 1.静态载入观察: 1 2.OD动态观察 ...
- sanic官方文档解析之websocket(网络套接字)和handle decorators(处理程序装饰器)
1,websocket(网络套接字) 在websocket上Sanic提供了一种简单使用的抽象化,来设置websocket(网络套接字) from sanic import Sanic from sa ...
- 一条数据的HBase之旅,简明HBase入门教程-开篇
常见的HBase新手问题: 什么样的数据适合用HBase来存储? 既然HBase也是一个数据库,能否用它将现有系统中昂贵的Oracle替换掉? 存放于HBase中的数据记录,为何不直接存放于HDFS之 ...
- QT实现FTP服务器(一)
QListenSocket类的实现: #include "QListenSocket.h" #include <QTcpSocket> #include <QDe ...