分析Go语言接口的设计原则

目录
  • 一、前言
  • 二、开闭原则
  • 三、依赖倒置原则
    • 3.1、什么是依赖倒置原则  
    • 3.2、一个耦合度极高的模块关系设计
    • 3.3、面向抽象层依赖倒转

一、前言

go的interface写起来更自由, 无需显示的实现, 只要实现了与interfece所包含的所有函数签名的相同的方法即可。让编码更灵活, 易扩展。

如何理解go语言中的interface呢?

1. interface是方法声明的集合

2.接口的方法与实现接口的类型方法格式一致

3.接口中所有方法均被实现

4. interface可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值

特别说明两点:

  • interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface
  • 方法不能重载,如eat(), eat(s string)不能同时存在

那么作为interface数据类型,他存在的意义在哪呢? 实际上是为了满足一些面向对象的编程思想。我们知道,软件设计的最高目标就是高内聚,低耦合。那么其中有一个设计原则叫开闭原则。什么是开闭原则

二、开闭原则

在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。

看重点: 对于扩展是开放的, 对于修改是封闭的.

举个例子: 银行每天要办理不同的业务, 存款, 转账, 取款等. 如果直接是实体来实现如下

package bank

import "fmt"

type Banker struct {

}

func (b *Banker) Save() {
    fmt.Println("存钱")
}

func (b *Banker) Transfer() {
    fmt.Println("转账")
}

func (b *Banker) Get() {
    fmt.Println("取钱")
}

有个人要来存钱取钱转账了

package main

import "aaa/bank"

func main() {
    var b = bank.Banker{}
    b.Save()
    b.Get()
    b.Transfer()
}

那么随着业务越来越多, 越来越大. 我又要新增加一些业务, 比如基金, 股票. 然后越来越多,越来越大. 导致Banker这个模块越来越臃肿

这样的设计会导致,当我们去给Banker添加新的业务的时候,会直接修改原有的Banker代码,那么Banker模块的功能会越来越多,出现问题的几率也就越来越大,假如此时Banker已经有99个业务了,现在我们要添加第100个业务,可能由于一次的不小心,导致之前99个业务也一起崩溃,因为所有的业务都在一个Banker类里,他们的耦合度太高,Banker的职责也不够单一,代码的维护成本随着业务的复杂正比成倍增大。

我们使用开闭原则, 使用interface将banker模块抽象出来. 然后根据这个抽象的模块, 去实现save, get, transfer.....

那么依然可以搞定程序的需求。 然后,当我们想要给Banker添加额外功能的时候,之前我们是直接修改Banker的内容,现在我们可以单独定义一个股票Banker(实现股票方法),到这个系统中。 而且股票Banker的实现成功或者失败都不会影响之前的稳定系统,他很单一,而且独立。

所以以上,当我们给一个系统添加一个功能的时候,不是通过修改代码,而是通过增添代码来完成,那么就是开闭原则的核心思想了。所以要想满足上面的要求,是一定需要interface来提供一层抽象的接口的。

golang代码实现如下:

package bank

import "fmt"

// 对银行的业务进行抽象
type Business interface {
    doBussiness()
}

// 存钱业务
type SaveBussiness struct {
}

func (b *SaveBussiness) doBussiness() {
    fmt.Sprintf("存钱")
}

//取钱业务
type GetBussiness struct {
}

func (g *GetBussiness) doBussiness() {
    fmt.Println("取钱")
}

// 转账业务
type TransferBusi struct {

}

func (t *TransferBusi) doBussiness() {
    fmt.Sprintf("转账")
}

然后我今天去了银行, 我们封装一个银行, 银行有各种各样的能力.

package main

import (
    "aaa/bank"
    "fmt"
)

// 这有一个银行, 银行可以办理业务
func Bank(b bank.Business) {
    fmt.Println("办理业务: ", b.DoBussiness())
}

func main() {
    // 办理具体的业务
    Bank(&bank.SaveBussiness{})
    Bank(&bank.GetBussiness{})
    Bank(&bank.TransferBusi{})
}

这样, 当银行增加业务类型, 比如股票的时候, 只需要扩展业务接口就可以了, 不会对原来的接口进行修改

再看开闭原则定义:开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。简单的说就是在修改需求的时候,应该尽量通过扩展来实现变化,而不是通过修改已有代码来实现变化。

接口的意义:

现在interface已经基本了解,那么接口的意义最终在哪里呢,想必现在你已经有了一个初步的认知,实际上接口的最大的意义就是实现多态的思想,就是我们可以根据interface类型来设计API接口,那么这种API接口的适应能力不仅能适应当下所实现的全部模块,也适应未来实现的模块来进行调用。 调用未来可能就是接口的最大意义所在吧,这也是为什么架构师那么值钱,因为良好的架构师是可以针对interface设计一套框架,在未来许多年却依然适用。

三、依赖倒置原则

3.1、什么是依赖倒置原则  

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

3.2、一个耦合度极高的模块关系设计

张三驾驶奔驰, 张三驾驶宝马, 张三驾驶丰田.

李四驾驶宝马, 李四驾驶奔驰, 李四驾驶丰田

package yldz

import "fmt"

// 奔驰车
type Benz struct {

}

func (b *Benz) run() string{
    return fmt.Sprintf("奔驰启动")
}
// 宝马
type BM struct {

}

func (b *BM) run() string{
    return fmt.Sprintf("宝马启动")
}
//丰田
type FT struct {

}

func (t *FT) run() string{
    return fmt.Sprintf("丰田启动")
}
//====驾车人,张三
type Zhangsan struct {

}

func (t *Zhangsan) DriverBenz(b *Benz) {
    fmt.Println("张三驾驶", b.run())
}

func (t *Zhangsan) DriverBM(b *BM) {
    fmt.Println("张三驾驶", b.run())
}

func (t *Zhangsan) DriverFT(b *FT) {
    fmt.Println("张三驾驶", b.run())
}

// 驾车人----李四.......
package main

import "aaa/yldz"

func main() {
    z := yldz.Zhangsan{}
    z.DriverBenz(&yldz.Benz{})
    z.DriverBM(&yldz.BM{})
    z.DriverFT(&yldz.FT{})
}

我们来看上面的代码和图中每个模块之间的依赖关系,实际上并没有用到任何的interface接口层的代码,显然最后我们的两个业务 张三开奔驰, 李四开宝马,程序中也都实现了。但是这种设计的问题就在于,小规模没什么问题,但是一旦程序需要扩展,比如我现在要增加一个凯迪拉克汽车 或者 司机王五, 那么模块和模块的依赖关系将成指数级递增,想蜘蛛网一样越来越难维护和捋顺。

3.3、面向抽象层依赖倒转

如上图所示,我们在设计一个系统的时候,将模块分为3个层次,抽象层、实现层、业务逻辑层。

  • 将抽象层的模块和接口定义出来,这里就需要了interface接口的设计,
  • 我们依照抽象层,依次实现每个实现层的模块,在我们写实现层代码的时候,实际上我们只需要参考对应的抽象层实现就好了,实现每个模块,也和其他的实现的模块没有关系,这样也符合了上面介绍的开闭原则。这样实现起来每个模块只依赖对象的接口,而和其他模块没关系,依赖关系单一。系统容易扩展和维护。
  • 业务逻辑层也是一样,只需要参考抽象层的接口来实现业务就好了,抽象层暴露出来的接口就是我们业务层可以使用的方法,然后可以通过多态的方向,接口指针指向哪个实现模块,调用了就是具体的实现方法,这样我们业务逻辑层也是依赖抽象成编程。

看看具体的实现

package yldz

import "fmt"

type Car interface {
    Run() string
}

type Driver interface {
    // 接口变量肚子里有一个指针, 所以接口变量不需要使用指针.
    Driver(car Car)
}

// 奔驰车
type Benz struct {

}

func (b *Benz) Run() string{
    return fmt.Sprintf("奔驰启动")
}

// 宝马车
type BM struct {

}

func (b *BM) Run() string{
    return fmt.Sprintf("宝马启动")
}

// 丰田车
type FT struct {

}

func (t *FT) Run() string{
    return fmt.Sprintf("丰田启动")
}

// ====张三
type Zhangsan struct {

}

func (t *Zhangsan) Driver(car Car) {
    fmt.Println("驾驶",car.Run())
}
func main() {

    benz := yldz.Benz{}
    zs := yldz.Zhangsan{}
    zs.Driver(&benz)

    ft := yldz.FT{}
    zs.Driver(&ft)
}

以上就是分析Go语言接口的设计原则的详细内容,更多关于Go 接口的资料请关注我们其它相关文章!

(0)

相关推荐

  • Django 自动生成api接口文档教程

    最近在写测试平台,需要实现一个节点服务器的api,正好在用django,准备使用djangorestframework插件实现. 需求 实现一个接口,在调用时,通过传递的参数,直接运行对应项目的自动化测试 环境 Python3.6 ,PyCharm,W7 项目结构 功能实现 流程 我们要做的就是实现以上流程 安装 pip install djangorestframework pip install markdown pip install django-filter # Filtering s

  • Go 语言中的空接口(推荐)

    在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 <Go编程时光>,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长. 我的在线博客:http://golang.iswbm.com 我的 Github:github.com/iswbm/GolangCodingTime 1. 什么是空接口? 空接口是特殊形式的接口类型,普通的接口都有方法,而空接口没有定义任何方法口,也因此

  • django API 中接口的互相调用实例

    我就废话不多说了,还是直接上代码吧! url = "http://%s:%s/api-token-auth/" % (ip, port) query_args = { "username": username, "password": password } resp = requests.post(url=url, data=query_args) token = json.loads(resp.text)["token"]

  • python Django编写接口并用Jmeter测试的方法

    一.环境准备 python3.6.7 Pycharm 二.创建项目 我这里是在Django项目中新建了个APP,目录结构如下图所示: 那么怎么在已有的Django项目中新建APP并进行配置呢: 2.1.新建app a.可以在终端输入命令:python manage.py startapp myapp(这里myapp是指你自己app的名称),如下图所示: b.也可以在pycharm中找到Tools-->Run manage.py Task, 在弹出的命令框中输入:startapp myapp(这里

  • Django如何开发简单的查询接口详解

    前言 Django处理json也是一把好手,有时候在工作中各个部门都会提供自己的相关接口,但是信息也只是单方的信息,这时候需要运维将各个部门的信息进行集成,统一出一个查询接口或页面,方便其他部门同事使用,接下来就介绍一下Django如果操作json. 首先介绍一下通过url获取json的方法: import urllib2 我们的需求是做一个集成的信息查询系统,包括简单的IP信息查询(省份运营商等),以及设备信息(如果是登记在资产管理库中的),还有IP归属(是否是客户源站IP),以及是否为其他部

  • 使用Django开发简单接口实现文章增删改查

    1.一些准备工作  安装django pip install django 创建django项目 进入项目代码存放目录执行命令: django-admin.py startproject blog_demo 进入blog_demo,运行命令: python3.6 manage.py runserver 9000 在浏览器地址栏打开:http://127.0.0.1:9000/ 如果出现以下画面,则说明服务器正在运行 创建博客应用(app) django中每一个app可以看作是一个模块,以app为

  • 浅谈Go语言中的结构体struct & 接口Interface & 反射

    结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struct类型理解为类,可以定义方法,和函数定义有些许区别: struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User var user1 *User = &User{} var user2 *User = new(User) struct使用 下面示例中user1和

  • golang中interface接口的深度解析

    一 接口介绍 如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度.Go语言在编程哲学上是变革派,而不是改良派.这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口.Go语言的编程哲学因为有接口而趋于完美.C++,Java 使用"侵入式"接口,主要表现在实现

  • Go语言接口定义与用法示例

    本文实例讲述了Go语言接口定义与用法.分享给大家供大家参考,具体如下: 在Go中,接口interface其实和其他语言的接口意思也没什么区别.interface理解其为一种类型的规范或者约定.一种类型是不是"实现"了一个接口呢?就看这种类型是不是实现了接口中定义的所有方法. 1. 接口的定义和使用. 比如 复制代码 代码如下: type I interface{     Get() int     Put(int) } 这段话就定义了一个接口,它包含两个函数Get和Put 好了,我的一

  • 分析Go语言接口的设计原则

    目录 一.前言 二.开闭原则 三.依赖倒置原则 3.1.什么是依赖倒置原则 3.2.一个耦合度极高的模块关系设计 3.3.面向抽象层依赖倒转 一.前言 go的interface写起来更自由, 无需显示的实现, 只要实现了与interfece所包含的所有函数签名的相同的方法即可.让编码更灵活, 易扩展. 如何理解go语言中的interface呢? 1. interface是方法声明的集合 2.接口的方法与实现接口的类型方法格式一致 3.接口中所有方法均被实现 4. interface可以作为一种数

  • java面向对象设计原则之迪米特法则分析详解

    目录 概念 使用 拓展 概念 迪米特法则解决类与类之间耦合度问题,如果类A调用了B类的某一个方法,则这两个类就形成了一种紧耦合的方式,当B类这个方法发生变化时,一定会影响A类的执行结果.迪米特法则要求每一个类尽可能少的与其他类发生关系,也就是尽可能少的让其他类发生变化时,对其代码的执行结果产生的影响降到最低. 典型情况:A类调用B类的方法,B类和C类是一种关联关系,如果A类通过B类所持有的C类对象直接调用C类的方法,则A类和C类同时拥有强耦合的关系.代码如下: public class B {

  • go语言接口用法实例分析

    本文实例讲述了go语言接口用法.分享给大家供大家参考.具体分析如下: 首先定义一个接口: 复制代码 代码如下: type I interface{     Get() int     Put(int)   } 这段话就定义了一个接口,它包含两个函数Get和Put 好了,我的一个接口实现了这个接口: 复制代码 代码如下: type S stuct {val int} func (this *S) Get int {     return this.val } func (this *S)Put(v

  • java面向对象设计原则之接口隔离原则示例详解

    目录 概念 实现 拓展 概念 小接口原则,即每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分.如下图所示定义了一个接口,包含了5个方法,实现类A用到了3个方法.实现类B用到了3个方法,类图如下: 类A没有方法4.方法5,却要实现它:类B没有方法2.方法3,但还是要实现这两个方法,不符合接口隔离原则.改造为将其拆分为三个接口,实现方式改为下图所示,符合接口隔离原则: 实现 面向对象机制中一个类可以实现多个接口,通过多重继承分离,通过接口多继承(实现)来实现客户的需求,代码更加清

  • C#实现六大设计原则之接口隔离原则

    接口隔离原则(ISP)定义: 客户端不应该依赖它不需要的接口:一个类对另一个类的依赖应该建立在最小的接口上. 问题由来: 类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法. 解决方案: 将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系.也就是采用接口隔离原则. 举例来说明接口隔离原则: 类A依赖接口I中的方法1.方法2.方法3,类B是对类A依赖的实现. 类C依赖接口I中的方法1.方法4.

  • C#面向对象设计原则之接口隔离原则

    接口隔离原则(ISP) 定义:使用多个专门的接口比使用单一的总接口要好.即不要把鸡蛋都放到一个篮子里.好处:比较灵活.方便,不想实现的或不用实现的可以不实现.解释说明:大部分人都喜欢用一个接口把需要用到的方法全部声明出来,但是ISP建议我们使用多个专门的接口比使用单一的总接口要好,也就是一个接口里的方法多的话,实现起来不是很方便. 示例1: using System; using System.Collections.Generic; using System.Linq; using Syste

  • C语言容易被忽视的函数设计原则基础

    目录 一.函数设计原则 二.总结 一.函数设计原则 函数从意义上应该是一个独立的功能模块 函数名要在一定程度上反映函数的功能 函数参数名要能够体现参数的意义 尽量避免在函数中使用全局变量 当函数参数不应该在函数体内部被修改时,应加上 const 声明 如果参数是指针,且仅作输入参数,则应加上 const 声明,如下: 不能省略返回值的类型 如果函数没有返回值,那么应声明为 void 类型对参数进行有效性检查 对于指针参数的检查尤为重要 不要返回指向“栈内存”的指针 栈内存在函数体结束时被自动释放

  • 浅谈java中OO的概念和设计原则(必看)

    一.OO(面向对象)的设计基础 面向对象(OO):就是基于对象概念,以对象为中心,以类和继承为构造机制,充分利用接口和多态提供灵活性,来认识.理解.刻划客观世界和设计.构建相应的软件系统.面向对象的特征:虽然各种面向对象编程语言相互有别,但都能看到它们对面向对象基本特征的支持, 即 "抽象.封装.继承.多态" : – 抽象,先不考虑细节 – 封装,隐藏内部实现 – 继承,复用现有代码 – 多态,改写对象行为 面向对象设计模式:是"好的面向对象设计",所谓"

  • 深入浅析JavaScript的API设计原则

    一.接口的流畅性 好的接口是流畅易懂的,他主要体现如下几个方面: 1.简单 操作某个元素的css属性,下面是原生的方法: document.querySelectorAll('#id').style.color = 'red'; 封装之后 function a(selector, color) { document.querySelectorAll(selector)[].style.color = color } a('#a', 'red'); 从几十个字母长长的一行到简简单单的一个函数调用,

  • 10个Java程序员熟悉的面向对象设计原则

    面向对象设计原则是OOPS编程的核心, 但我见过的大多数Java程序员热心于像Singleton (单例) . Decorator(装饰器).Observer(观察者) 等设计模式,而没有把足够多的注意力放在学习面向对象的分析和设计上面.学习面向对象编程像"抽象"."封装"."多态"."继承" 等基础知识是重要的,但同时为了创建简洁.模块化的设计,了解这些设计原则也同等重要.我经常看到不同经验水平的java程序员,他们有的不知

随机推荐