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方法留给多态来实现。
完美。
————————————————
版权声明:本文为CSDN博主「力软快速开发平台」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42831704/article/details/90632566
Go语言中的代码重用 - 继承还是组合?相关推荐
- 一起来学C++:C++中的代码重用
目录 14.1 包含对象成员的类 14.1.1 valarray类简介 14.1.2 Student类的设计 14.1.3 Student类示例 1.初始化被包含的对象 2.使用被包含对象的接口 3. ...
- 【C++ Primer】第十四章 C++中的代码重用
序:C++的一个主要目标是促进代码重用,其中包含公有继承.包含.使用私有或保护继承 一,包含对象成员的类 1)valarray类简介 #include <valarray> ...
- 番外篇--C++中的代码重用
实现代码重用的一些方法(这里并不是全部): 包含(组合.层次化):类包含另一个类的对象 使用私有继承或保护继承 以上两种方法都用于实现has-a关系,常用第一种方法 多重继承可以使多个基类派生出一个新 ...
- cf16-1代码什么意思_CF中的代码重用-第一部分
cf16-1代码什么意思 -posted by davidjmedlock: -由 davidjmedlock 发布 : Well, in about 7 hours I'm going to be ...
- 《C++ Primer Plus(第六版)》(30)(第十四章 C++中的代码重用 编程题答案)
14.7 编程练习 1.Wine类有一个string类对象成员(参见第4章)和一个Pair对象(参见本章):其中前者用来存储葡萄酒的名称,而后者有2个valarry<int>对象(参见本章 ...
- C++ Primer Plus (第六版)编程练习记录(chapter14 C++中的代码重用)
1.Wine类有一个string类对象成员(参见第4章)和一个Pair对象(参见本章):其中前者用于存储葡萄酒的名称,而后者有2个valarray对象(参见本章),这两个valarray对象分别保存了 ...
- C++中的代码重用(1)
目录 1.包含对象成员的类 2.私有继承 3.各类继承方式: 1.包含对象成员的类 //class.h#pragma once#ifndef _CLASS_H_ #define _CLASS_H_#i ...
- c语言中数与数之间空格如何打代码,C语言中代码输出的最后一个数不要空格之前的数之间都要空格怎么打...
C语言中五个数比大小,输出最大数如何写代码 #include#defineSIZE5main(){intx[SIZE],i,max;printf("Enter5integers:\n&quo ...
- java 重用性_提高Java代码重用性的三个方法
三种修改现有代码提高其可重用性的方法,它们分别是:改写类的实例方法,把参数类型改成接口,选择最简单的参数接口类型. 措施一:改写类的实例方法 通过类继承实现代码重用不是精确的代码重用技术,因此它并不是 ...
最新文章
- php中的static,php中的static
- PyQt4布局管理——绝对定位方式
- 理解 pkg-config 工具
- 如何在linux环境下安装yaf
- ConcurrentHashMap的源码分析-put方法第四个阶段
- Springboot注册Listener
- vl_sift函数用法
- 一个牛逼的Coder是这样诞生的
- zencart手工备份mysql数据库_MySQL数据库镜像 / 实时备份Zen Cart数据库
- php_steam,Steam 帐户使用 - Steam Support
- 电科 | 传感器及其应用技术
- iOS 应用内付费(IAP)开发步骤
- Lumerical官方案例、FDTD时域有限差分法仿真学习(六)——等离子体超材料吸收器(Plasmonic metamaterial absorber)
- 一文读懂微软转型秘诀
- Python moviepy 快速视频剪辑编辑神器
- ai换脸明显_为什么我们应该真正害怕AI:对明显和非明显危险的分析
- python用循环打出阶梯图形,matplotlib阶梯图的实现(step())
- IPD+CMMI+Scrum一体化研发管理解决方案之CMMI
- google翻译的用法 使用translate.google.com翻译整个网页内容
- Java eclipse安装全过程