Go语言中的面向对象

前言

如果说最纯粹的面向对象语言,我觉得是Java无疑。而且Java语言的面向对象也是很直观,很容易理解的。class是基础,其他都是要写在class里的。

最近学习了Go语言,有了一些对比和思考。虽然我还没有完全领悟Go语言“Less is more”的编程哲学,思考的方式还是习惯从Java的角度出发,但是我还是深深的喜欢上了这门语言。

这篇文章仅是我学习过程中的一些想法,欢迎留言探讨,批评指正。

封装

Java中的封装

Java语言中,封装是自然而来的,也是强制的。你所写的代码,都要属于某个类,某个class文件。类的属性封装了数据,方法则是对这些数据的操作。通过private和public来控制数据的可访问性。

每个类(java文件),自然的就是一个对象的模板。

Go中的封装

Go语言并不是完全面向对象的。其实Go语言中并没有类和对象的概念。

首先,Go语言是完全可以写成面向过程风格的。Go语言中有很多的function是不属于任何对象的。(以前我写过一些ABAP语言,ABAP是从面向过程转为支持面向对象的语言,所以也是有类似的function的)。

然后,Go语言中,封装有包范围的封装和结构体范围的封装。

在Java语言中,我们组织程序的方式一般是通过project-package-class。每个class,对应一个文件,文件名和class名相同。其实我觉得这样组织是很清晰也很直观的。

在Go语言中,只有一个package的概念。package就是一个文件夹。在这个文件夹下的所有文件,都是属于这个package的。这些文件可以任意起名字,只要在文件头加上package名字

package handler

那么这个文件就是属于这个package的。在package内部所有的变量是互相可见的,是不可以重复的。

你可以这样理解:文件夹(package)就是你封装的一个单元(比如你想封装一个Handler处理一些问题)。里边其实只有一个文件,但是为了管理方便,你把它拆成了好几个文件(FileHandler、ImageHandler、HTTPHandler、CommonUtils),但其实这些文件写成一个和写成几个,他们之间的变量都是互相可见的。

如果变量是大写字母开头命名,那么对包外可见。如果是小写则包外不可见。

其实一开始我是很不习惯这种封装方式的,因为写Java的时候是难以想象一个文件里的变量在另一个文件里也可见的。

Go中的另外一种封装,就是结构体struct。没错,类似C语言中的struct,我们把一些变量用一个struct封装在一起。

type Dog struct {
	Name string
	Age  int64
	Sex  int
}

我们还可以给struct添加方法,做法就是把一个function指定给某个struct。

func (dog *Dog) bark() {
	fmt.Println("wangwang")
} 

这时候看起来是不是很有面向对象的感觉了?起码我们有对象(struct)和方法(绑定到struct的function)了,是不是?具体的Go语法不在这里过多探讨。

继承

封装只是基础,为继承和多态提供可能。继承和多态才是面向对象最有意思也最有用的地方。

Java中的继承

Java语言中,继承通过extends关键字实现。有非常清晰的父类和子类的概念以及继承关系。Java不支持多继承。

Go中的继承

Go语言中其实并没有继承。看到这里你可能会说:什么鬼?面向对象语言里没有继承?好吧其实一开始我也是懵逼的。但是Go中确实只是提供了一种伪继承,通过embedding实现的“伪”继承。

type father struct {
   Name string
   Age  int
}

type son struct {
   father
   hobby string
}

type son2 struct {
   someFather father
   hobby      string
}

  

如上代码所示,在son中声明一个匿名的father类型结构体,那么son伪继承了father,而son2则仅仅是把father作为一个属性使用。

son中可以直接使用father中的Name、Age等属性,不需要写成son.father.Name,直接写成son.Name即可。如果father有方法,也遵循同理。

但为什么说是伪继承呢?

在Java的继承原则上,子类继承了父类,不光是子类可以复用父类的代码,而且子类是可以当做父类来使用的。参见面向对象六大原则之一的里氏替换原则。即在需要用到父类的地方,我用了一个子类,应该是可以正常工作的。

然而Go中的这种embedding,son和father完全是两个类型,如果在需要用father的地方直接放上一个son,编译是不通过的。

关于Go语言中的这种伪继承,我还踩过一个深坑,以后会分享出来。

看起来Go语言中的继承是不是更像一种提供了语法糖的has-a的关系,并不是is-a的关系。说到这里,可能有的人会说Go语言这是搞什么,没有继承还怎么愉快的玩耍。又有的人可能觉得:没错,就是要干掉继承,组合优于继承。

其实关于继承或是组合的问题,我查了很多说法,目前我个人认同如下观点:

继承VS组合

  继承 组合

优点

创建子类的对象时,无须创建父类的对象 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
子类能自动继承父类的接口 具有较好的可扩展性
  支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
  整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点

子类不能改变父类的接口 整体类不能自动获得和局部类同样的接口
破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 创建整体类的对象时,需要创建所有局部类的对象
不支持动态继承。在运行时,子类无法选择不同的父类  
支持扩展,但是往往以增加系统结构的复杂度为代价  

那么什么时候用继承,什么时候用组合呢?

  1. 除非考虑使用多态,否则优先使用组合。
  2. 要实现类似”多重继承“的设计的时候,使用组合。
  3. 要考虑多态又要考虑实现“多重继承”的时候,使用组合+接口。

多态

我认为多态是面向对象编程中最重要的部分。

By the way,方法重载也是多态的一种。但是Go语言中是不支持方法重载的。

两种语言都支持方法重写(Go中的伪继承,son如果重写了father中的方法,默认是会使用son的方法的)。

不过要注意的是,在Java中重写父类的非抽象方法,已经违背了里氏替换原则。而Go语言中是没有抽象方法一说的。

Go中的多态采用和JavaScript一样的鸭式辩型:如果一只鸟走路像鸭子,叫起来像鸭子,那么它就是一只鸭子。

在Java中,我们要显式的使用implements关键字,声明一个类实现了某个接口,才能将这个类当做这个接口的一个实现来使用。在Go中,没有implements关键字。只要一个struct实现了某个接口规定的所有方法,就认为它实现了这个接口。

type Animal interface {
	bark()
}

type Dog struct {
	Name string
	Age  int64
	Sex  int
}

func (dog *Dog) bark() {
	fmt.Println("wangwang")
}

  

如上代码,Dog实现了Animal接口,无需任何显式声明。

让我们先从一个简单的多态开始。猫和狗都是动物,猫叫起来是miaomiao的,狗叫起来是wagnwang的。

Java代码:

import java.io.*;
class test
{
	public static void main (String[] args) throws java.lang.Exception
	{
		Animal animal;
		animal= new Cat();
		animal.shout();
		animal = new Dog();
		animal.shout();
	}
}

abstract class Animal{
    abstract void shout();
}

class Cat extends Animal{
    public void shout(){
        System.out.println("miaomiao");
    }
}

class Dog extends Animal{
    public void shout(){
        System.out.println("wangwang");
    }
}

  

输出如下:

miaomiao
wangwang

  

但是我们在继承的部分已经说过了,Go的继承是伪继承,“子类”和“父类”并不是同一种类型。如果我们尝试通过继承来实现多态,是行不通的。

Go代码:

package main

import (
   "fmt"
)

func main() {
   var animal Animal
   animal = &Cat{}
   animal.shout()
   animal = &Dog{}
   animal.shout()
}

type Animal struct {
}

type Cat struct {
   //伪继承
   Animal
}

type Dog struct {
   //伪继承
   Animal
}

func (a *Animal) shout() {
   //Go has no abstract method
}

func (c *Cat) shout() {
   fmt.Println("miaomiao")
}

func (d *Dog) shout() {
   fmt.Println("wangwang")
}

  

  

上边的代码是编译报错的。输出如下:

# command-line-arguments
dome/demo.Go:9:9: cannot use Cat literal (type *Cat) as type Animal in assignment
dome/demo.Go:11:9: cannot use Dog literal (type *Dog) as type Animal in assignment

  

其实就算是在Java里,如果不考虑代码复用,我们也是首先推荐接口而不是抽象类的。那么我们把上边的实现改进一下。

Java代码:

import java.io.*;
class test
{
	public static void main (String[] args) throws java.lang.Exception
	{
		Animal animal;
		animal= new Cat();
		animal.shout();
		animal = new Dog();
		animal.shout();
	}
}

interface Animal{
    void shout();
}

class Cat implements Animal{
    public void shout(){
        System.out.println("miaomiao");
    }
}

class Dog implements Animal{
    public void shout(){
        System.out.println("wangwang");
    }
}

  

输出如下:

miaomiao
wangwang

  

Go里边的接口是鸭式辩型,代码如下:

package main

import (
	"fmt"
)

func main() {
	var animal Animal
	animal = &Cat{}
	animal.shout()
	animal = &Dog{}
	animal.shout()
}

type Animal interface {
	shout()
}

type Cat struct {
}

type Dog struct {
}

func (c *Cat) shout() {
	fmt.Println("miaomiao")
}

func (d *Dog) shout() {
	fmt.Println("wangwang")
}

  

输出如下:

miaomiao
wangwang

  

看起来很棒对不对。那我们为什么不直接都用接口呢?还要继承和抽象类干什么?这里我们来捋一捋一个老生常谈的问题:接口和抽象类的区别。

这里引用了知乎用户chao wang的观点。感兴趣的请前往他的回答。

abstract class的核心在于,我知道一类物体的部分行为(和属性),但是不清楚另一部分的行为(和属性),所以我不能自己实例化(不知道的这部分)。如我们的例子,abstract class是Animal,那么我们可以定义他们胎生,恒定体温,run()等共同的行为,但是具体到“叫”这个行为时,得留着让非abstract的狗和猫等等子类具体实现。

interface的核心在于,我只知道这个物体能干什么,具体是什么不需要遵从类的继承关系。如果我们定一个Shouter interface,狗有狗的叫法,猫有猫的叫法,只要能叫的对象都可以有shout()方法,只要这个对象实现了Shouter接口,我们就能把它当shouter使用,让它叫。

所以abstract class和interface是不能互相替代的,interface不能定义(它只做了声明)共同的行为,事实上它也不能定义“非常量”的变量。而abstract class只是一种分类的抽象,它不能横跨类别来描述一类行为,它使得针对“别的分类方式”的抽象变得无法实现(所以需要接口来帮忙)。

考虑这样一个需求:猫和狗都会跑,并且它们跑起来没什么区别。我们并不想在Cat类和Dog类里边都实现一遍同样的run方法。所以我们引入一个父类:四足动物Quadruped

Java代码:

import java.io.*;
class test
{
	public static void main (String[] args) throws java.lang.Exception
	{
		Animal animal;
		animal= new Cat();
		animal.shout();
		animal.run();
		animal = new Dog();
		animal.shout();
		animal.run();
	}
}

interface Animal{
    void shout();
    void run();
}

abstract class Quadruped implements Animal{
    abstract public void shout();
    public void run(){
        System.out.println("running with four legs");
    }
}

class Cat extends Quadruped{
    public void shout(){
        System.out.println("miaomiao");
    }
}

class Dog extends Quadruped{
    public void shout(){
        System.out.println("wangwang");
    }
}

  

输出如下:

miaomiao
running with four legs
wangwang
running with four legs

  

Go语言中是没有抽象类的,那我们尝试用Embedding来实现代码复用:

package main

import (
	"fmt"
)

func main() {
	var animal Animal
	animal = &Cat{}
	animal.shout()
	animal.run()
	animal = &Dog{}
	animal.shout()
	animal.run()
}

type Animal interface {
	shout()
	run()
}

type Quadruped struct {
}

type Cat struct {
	Quadruped
}

type Dog struct {
	Quadruped
}

func (q *Quadruped) run() {
	fmt.Println("running with four legs")
}

func (c *Cat) shout() {
	fmt.Println("miaomiao")
}

func (d *Dog) shout() {
	fmt.Println("wangwang")
}

  

输出如下:

miaomiao
running with four legs
wangwang
running with four legs

  

但是由于Go语言并没有抽象类,所以Quadruped是可以被实例化的。但是它并没有shout方法,所以它并不能被当做Animal使用,尴尬。当然我们可以给Quadruped加上shout方法,那么我们如何保证Quadruped类不会被错误的实例化并使用呢?

换句话说,我期望通过对抽象类的非抽象方法的继承来实现代码的复用,通过接口和抽象方法来实现(符合里氏替换原则的)多态,那么如果有一个非抽象的父类出现(其实Java里也很容易出现),很可能会破坏这一规则。

其实Go语言是有它自己的编程逻辑的,我这里也只是通过Java的角度来解读Go语言中如何实现初步的面向对象。关于Go中的类型转换和类型断言,留在以后探讨吧。

如果本文对你有帮助,请点赞鼓励一下吧^_^

  

原文地址:https://www.cnblogs.com/xxnn/p/10552819.html

时间: 2024-11-10 14:44:37

Go语言中的面向对象的相关文章

C语言中的面向对象思想!

经常听见别人说面向对象的程序设计,以前也有上过面向对象程序设计这门课.可是不幸的是,这些都是以C++,甚至VC++为基础的.而更加不幸的是,多年以来我一直是一个C的使用者.在学校的时候,我主要做的是硬件上的驱动层,和底层功能层. 在工作以后,又做的是手机上的软件开发,所有这些都是和C离不开的.虽然我不得不说,C++是一门很好的语言,但是它的编译速度,代码效率,编译后的代码大小都限制了它在嵌入式上的应用.(尽管现在的嵌入式CPU越来越快,内存容量变大,我觉得用C++也应该没有什么问题.这使我觉得似

Java语言中的面向对象特性

1.1 面向对象的基本概念  面向对象的基本思想  面向对象是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),其基本思想是使用对象.类.继承.封装.消息等基本概念来进行程序设计.从现实世界中客观存在的事物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式.开发一个软件是为了解决某些问题,这些问题所涉及的业务范围称作该软件的问题域.其应用领域不仅仅是软件,还有计算机体系结构和人工智能等. 1. 对象的基本概念  对象是系统中用来描述客观事物的一个实体

Java语言中的面向对象特性总结

[课前思考]  1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类?  2. 面向对象编程的特性有哪三个?它们各自又有哪些特性?  3. 你知道java语言在面向对象编程方面有何独特的特点吗? 难点:  1. 理解方法重载和方法重写,不要混淆了两者的使用.  2. 类变量和类方法的使用.  3. 接口的使用. 3.1 面向对象技术基础 http://hovertree.com/menu/java/ 3.1.1 面向对象的基本概念  面向对象的基本思想  面向对象是一种新兴的程序设计方

Go语言学习(十一)面向对象编程-类型系统

1.类型系统介绍 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.简洁之处在于,Go语言并没有沿 袭传统面向对象编程中的诸多概念,比如继承.虚函数.构造函数和析构函数.隐藏的 this 指 针等. 一个典型的类型系统通常包含如下基本内容: 基础类型,如 byte . int . bool . float 等: 复合类型,如数组.结构体.指针等: 可以指向任意对象的类型( Any 类型): 面向对象,即所有具备面向对象特征(比如成员方法)的类型: 接口. 因为Java语言自诞生以来被称为最纯

如何在C语言里实现“面向对象编程”

有人认为面向对象是C++/Java这种高级语言的专利,实际不是这样,面向对象作为一种设计方法,是不限制语言的.只能说,用C++/Java这种语法来实现面向对象会更容易.更自然一些. 在本节中,就展示如何在C语言中实现面向对象编程,这是一件吃力的工作.写这些的目的有两个: ① 更好的掌握C++中的class的概念.学习了本章,就知道C程序员的无耐,就知道为什么要发明一个class的概念.为什么要有成员函数等等. ② 为C程序员提供一个参考设计.由在存在某些场合,只允许用C语言来编程,不允许用C++

Cocos2d-x 脚本语言Lua中的面向对象

Cocos2d-x 脚本语言Lua中的面向对象 面向对象不是针对某一门语言,而是一种思想.在面向过程的语言也能够使用面向对象的思想来进行编程. 在Lua中,并没有面向对象的概念存在,没有类的定义和子类的定义.但相同在Lua中能够利用面向对象的思想来实现面向对象的类继承. 一.复制表的方式面向对象 --Lua中的面向对象 --[[ 复制表方式面向对象 參数为一张表.通过遍历这张表取值,赋给一张空表,最后返回新建的表.来达到克隆表 ]] function clone(tab) local ins =

Lua语言基础汇总(12)-- Lua中的面向对象编程

简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 1 2 3 4 5 6 7 8 9 10 11 12 local tb1 = {a = 1, b = 2} local tb2 = {a = 1, b = 2} local tb3 = tb1   if tb1 == tb2 then      print("tb1 == tb2") else      print("tb1 ~= tb2") end   tb3.a = 3 pri

如何理解并学习javascript中的面向对象(OOP) [转]

如果你想让你的javascript代码变得更加优美,性能更加卓越.或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于 jquery的插件).那么,你请务必要学习javascript面向对象,否则你无法更灵活的使用javascript这门语言. 什么事闭包?到底什么是原型?(知道闭包和原型的,就算得上是javascript的高手了.但真正能够理解,并且灵活运用的人并不多)到底该如何学习javascript中的面向对象呢?在javascript这么语言正如日中天,相信不少人正在为

前端开发:面向对象与javascript中的面向对象实现(一)

前端开发:面向对象与javascript中的面向对象实现(一) 前言: 人生在世,这找不到对象是万万不行的.咱们生活中,找不到对象要挨骂,代码里也一样.朋友问我说:“嘿,在干嘛呢......”,我:“找不到对象!”,他:“就你那样也能找得到对象?”.我一脸黑线...... 废话不多说,今天博主要跟大家聊的是<面向对象与javascript中的面向对象实现>”. 面向对象理解: 面向对象是一种对现实世界理解和抽象的方法,是一种先进的程序设计理念,是一种比较抽象的,多形态的设计模式.我们可以这么理