interface接口--GO面向对象编程思想

news2024/11/8 13:45:29

一、interface接口

interface 是GO语言的基础特性之一。可以理解为一种类型的规范或者约定。它跟java,C# 不太一样,不需要显示说明实现了某个接口,它没有继承或子类或“implements”关键字,只是通过约定的形式,隐式的实现interface 中的方法即可。因此,Golang 中的 interface 让编码更灵活、易扩展。

如何理解go 语言中的interface ? 只需记住以下三点即可:

  1. interface 是方法声明的集合
  2. 任何类型的对象实现了在interface 接口中声明的全部方法,则表明该类型实现了该接口。
  3. interface 可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。

注意:

a. interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface

b. 方法不能重载,如 eat(), eat(s string) 不能同时存在

package main

import "fmt"

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type ApplePhone struct {
}

func (iPhone ApplePhone) call() {
    fmt.Println("I am Apple Phone, I can call you!")
}

func main() {
    var phone Phone
    phone = new(NokiaPhone)
    phone.call()

    phone = new(ApplePhone)
    phone.call()
}

        上述中体现了interface接口的语法,在main函数中,也体现了多态的特性。
同样一个phone的抽象接口,分别指向不同的实体对象,调用的call()方法,打印的效果不同,那么就是体现出了多态的特性。

二、面向对象中的开闭原则

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

2.1 平铺式的模块设计


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

package main

import "fmt"

//我们要写一个类,Banker银行业务员
type Banker struct {
}

//存款业务
func (this *Banker) Save() {
	fmt.Println( "进行了 存款业务...")
}

//转账业务
func (this *Banker) Transfer() {
	fmt.Println( "进行了 转账业务...")
}

//支付业务
func (this *Banker) Pay() {
	fmt.Println( "进行了 支付业务...")
}

func main() {
	banker := &Banker{}

	banker.Save()
	banker.Transfer()
	banker.Pay()
}


代码很简单,就是一个银行业务员,他可能拥有很多的业务,比如Save()存款、Transfer()转账、Pay()支付等。那么如果这个业务员模块只有这几个方法还好,但是随着我们的程序写的越来越复杂,银行业务员可能就要增加方法,会导致业务员模块越来越臃肿。

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

2.2 开闭原则设计

那么,如果我们拥有接口, interface这个东西,那么我们就可以抽象一层出来,制作一个抽象的Banker模块,然后提供一个抽象的方法。 分别根据这个抽象模块,去实现支付Banker(实现支付方法),转账Banker(实现转账方法)
如下:

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

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

golang代码实现

package main

import "fmt"

//抽象的银行业务员
type AbstractBanker interface{
	DoBusi()	//抽象的处理业务接口
}

//存款的业务员
type SaveBanker struct {
	//AbstractBanker
}

func (sb *SaveBanker) DoBusi() {
	fmt.Println("进行了存款")
}

//转账的业务员
type TransferBanker struct {
	//AbstractBanker
}

func (tb *TransferBanker) DoBusi() {
	fmt.Println("进行了转账")
}

//支付的业务员
type PayBanker struct {
	//AbstractBanker
}

func (pb *PayBanker) DoBusi() {
	fmt.Println("进行了支付")
}


func main() {
	//进行存款
	sb := &SaveBanker{}
	sb.DoBusi()

	//进行转账
	tb := &TransferBanker{}
	tb.DoBusi()
	
	//进行支付
	pb := &PayBanker{}
	pb.DoBusi()

}

当然我们也可以根据AbstractBanker设计一个小框架

//实现架构层(基于抽象层进行业务封装-针对interface接口进行封装)
func BankerBusiness(banker AbstractBanker) {
	//通过接口来向下调用,(多态现象)
	banker.DoBusi()
}

那么main中可以如下实现业务调用:

func main() {
	//进行存款
	BankerBusiness(&SaveBanker{})

	//进行存款
	BankerBusiness(&TransferBanker{})

	//进行存款
	BankerBusiness(&PayBanker{})
}

三、接口的意义

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

四、面向对象的依赖倒转原则

4.1 依赖倒转原则 (面向接口编程)


基本概念:

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节(具体实现),细节(具体实现)应该依赖抽象

依赖倒转(倒置)的中心思想是面向接口编程

依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。

以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。抽象指的是接口或抽象类,细节就是具体的实现类

使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

依赖倒转要做的事:

低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好

变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化

继承时遵循里氏替换原则

4.2 耦合度极高的模块关系设计

package main

import "fmt"

// === > 奔驰汽车 <===
type Benz struct {

}

func (this *Benz) Run() {
	fmt.Println("Benz is running...")
}

// === > 宝马汽车  <===
type BMW struct {

}

func (this *BMW) Run() {
	fmt.Println("BMW is running ...")
}


//===> 司机张三  <===
type Zhang3 struct {
	//...
}

func (zhang3 *Zhang3) DriveBenZ(benz *Benz) {
	fmt.Println("zhang3 Drive Benz")
	benz.Run()
}

func (zhang3 *Zhang3) DriveBMW(bmw *BMW) {
	fmt.Println("zhang3 drive BMW")
	bmw.Run()
}

//===> 司机李四 <===
type Li4 struct {
	//...
}

func (li4 *Li4) DriveBenZ(benz *Benz) {
	fmt.Println("li4 Drive Benz")
	benz.Run()
}

func (li4 *Li4) DriveBMW(bmw *BMW) {
	fmt.Println("li4 drive BMW")
	bmw.Run()
}

func main() {
	//业务1 张3开奔驰
	benz := &Benz{}
	zhang3 := &Zhang3{}
	zhang3.DriveBenZ(benz)

	//业务2 李四开宝马
	bmw := &BMW{}
	li4 := &Li4{}
	li4.DriveBMW(bmw)
}

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

4.3 面向抽象层依赖倒转

如上图所示,如果我们在设计一个系统的时候,将模块分为3个层次,抽象层、实现层、业务逻辑层。那么,我们首先将抽象层的模块和接口定义出来,这里就需要了interface接口的设计,然后我们依照抽象层,依次实现每个实现层的模块,在我们写实现层代码的时候,实际上我们只需要参考对应的抽象层实现就好了,实现每个模块,也和其他的实现的模块没有关系,这样也符合了上面介绍的开闭原则。这样实现起来每个模块只依赖对象的接口,而和其他模块没关系,依赖关系单一。系统容易扩展和维护。

我们在指定业务逻辑也是一样,只需要参考抽象层的接口来业务就好了,抽象层暴露出来的接口就是我们业务层可以使用的方法,然后可以通过多态的线下,接口指针指向哪个实现模块,调用了就是具体的实现方法,这样我们业务逻辑层也是依赖抽象成编程。

我们就将这种的设计原则叫做依赖倒转原则。

来一起看一下修改的代码:

package main

package main

import "fmt"

// ===== >   抽象层  < ========
type Car interface {
	Run()
}

type Driver interface {
	Drive(car Car)
}

// ===== >   实现层  < ========
type BenZ struct {
	//...
}

func (benz * BenZ) Run() {
	fmt.Println("Benz is running...")
}

type Bmw struct {
	//...
}

func (bmw * Bmw) Run() {
	fmt.Println("Bmw is running...")
}

type Zhang_3 struct {
	//...
}

func (zhang3 *Zhang_3) Drive(car Car) {
	fmt.Println("Zhang3 drive car")
	car.Run()
}

type Li_4 struct {
	//...
}

func (li4 *Li_4) Drive(car Car) {
	fmt.Println("li4 drive car")
	car.Run()
}


// ===== >   业务逻辑层  < ========
func main() {
	//张3 开 宝马
	var bmw Car
	bmw = &Bmw{}

	var zhang3 Driver
	zhang3 = &Zhang_3{}

	zhang3.Drive(bmw)

	//李4 开 奔驰
	var benz Car
	benz = &BenZ{}

	var li4 Driver
	li4 = &Li_4{}

	li4.Drive(benz)
}

4.3 依赖倒转小练习

	 
模拟组装2台电脑
--- 抽象层 ---
	有显卡Card  方法display
	有内存Memory 方法storage
    有处理器CPU   方法calculate
--- 实现层层 ---
	有 Intel因特尔公司 、产品有(显卡、内存、CPU)
	有 Kingston 公司, 产品有(内存3)
	有 NVIDIA 公司, 产品有(显卡)
--- 逻辑层 ---
	1. 组装一台Intel系列的电脑,并运行
        2. 组装一台 Intel CPU  Kingston内存 NVIDIA显卡的电脑,并运行
/*
	模拟组装2台电脑
    --- 抽象层 ---
	有显卡Card  方法display
	有内存Memory 方法storage
    有处理器CPU   方法calculate

    --- 实现层层 ---
	有 Intel因特尔公司 、产品有(显卡、内存、CPU)
	有 Kingston 公司, 产品有(内存3)
	有 NVIDIA 公司, 产品有(显卡)

	--- 逻辑层 ---
	1. 组装一台Intel系列的电脑,并运行
    2. 组装一台 Intel CPU  Kingston内存 NVIDIA显卡的电脑,并运行
*/
package main

import "fmt"

//------  抽象层 -----
type Card interface{
	Display()
}

type Memory interface {
	Storage()
}

type CPU interface {
	Calculate()
}

type Computer struct {
	cpu CPU
	mem Memory
	card Card
}

func NewComputer(cpu CPU, mem Memory, card Card) *Computer{
	return &Computer{
		cpu:cpu,
		mem:mem,
		card:card,
	}
}

func (this *Computer) DoWork() {
	this.cpu.Calculate()
	this.mem.Storage()
	this.card.Display()
}

//------  实现层 -----
//intel
type IntelCPU struct {
	CPU	
}

func (this *IntelCPU) Calculate() {
	fmt.Println("Intel CPU 开始计算了...")
}

type IntelMemory struct {
	Memory
}

func (this *IntelMemory) Storage() {
	fmt.Println("Intel Memory 开始存储了...")
}

type IntelCard struct {
	Card
}

func (this *IntelCard) Display() {
	fmt.Println("Intel Card 开始显示了...")
}

//kingston
type KingstonMemory struct {
	Memory
}

func (this *KingstonMemory) Storage() {
	fmt.Println("Kingston memory storage...")
}

//nvidia
type NvidiaCard struct {
	Card
}

func (this *NvidiaCard) Display() {
	fmt.Println("Nvidia card display...")
}



//------  业务逻辑层 -----
func main() {
	//intel系列的电脑
	com1 := NewComputer(&IntelCPU{}, &IntelMemory{}, &IntelCard{})
	com1.DoWork()

	//杂牌子
	com2 := NewComputer(&IntelCPU{}, &KingstonMemory{}, &NvidiaCard{})
	com2.DoWork()
}

参考

依赖倒转原则 (面向接口编程)_诗水人间的博客-CSDN博客_倒转原则.如何使用接口和抽象类连接

面向对象设计的六大原则_iris丶的博客-CSDN博客_面向对象六大设计原则

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/141995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言进阶】自定义类型:结构体,枚举,联合体

目录 1、结构体的声明 1.1 结构体基础知识 1.2 结构体的声明 1.3 特殊的声明 1.4 结构体的自引用 1.5 结构体变量的定义和初始化 1.6 结构体内存对齐 ​编辑1.7 修改默认对齐数 1.8 结构体传参 2. 位段 2.1 什么是位段 2.2 位段的内存分配 2.3 位段的跨平台问…

【owt-server】代码结构及新增一个agent

owt server 官方 5.0 仓库:代码结构 manage console manage api portal sip portal 与agent 并列 agent又有很多种类。 启动脚本 启动一个新的agent 比如streaming-agent streaming-agent )cd ${OWT_HOME}/s

分布式id

分布式id一 什么是分布式系统唯一ID二 分布式系统唯一ID的特点三 分布式系统唯一ID的实现方案3.1 基于UUID3.2 基于数据库自增id3.3 基于数据库集群模式3.4 基于Redis模式3.5 基于雪花算法&#xff08;Snowflake&#xff09;模式3.6 百度&#xff08;uid-generator&#xff09;…

Python爬虫数据到sqlite实例

参考链接:https://blog.csdn.net/qq_45775027/article/details/115319253最近需要使用到爬虫数据库&#xff0c;原文中作者有些没补齐&#xff0c;略作修改之后跑通了。主要修改&#xff1a;1.调整了数据获取的正则表达式&#xff1b;2. 改了一下数据库的table名和定义名字&…

基于Java+SpringBoot+vue+element实现前后端分离牙科诊所管理系统详细设计

基于JavaSpringBootvueelement实现前后端分离牙科诊所管理系统详细设计 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目…

【Linux】虚拟地址空间 --- 虚拟地址、空间布局、内存描述符、写时拷贝、页表…

该吃吃&#xff0c;该喝喝&#xff0c;遇事儿别往心上隔&#x1f60e; 文章目录一、虚拟地址空间1.虚拟地址的引出&#xff08;看不到物理地址&#xff0c;只能看看虚拟地址喽&#xff09;2.虚拟地址空间布局&#xff08;五个段&#xff09;3.感性理解一下虚拟地址空间&#xf…

【C++修炼之路】C++入门(上)

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录一、前言二、第一个 C 程序三、C 关键字(C98)四、命名空间1、命名空间的定义2、命名空间…

C++ Prime课后习题第一章编程

编程一个C程序&#xff0c;它显示您的姓名和地址。#include <iostream>int stonetolb(int); int main() {using namespace std;cout << "zzz ";cout << "闵行"<<endl;return 0; }编写一个程序&#xff0c;要求用户输入一个以long…

3台机器配置hadoop集群_Hadoop+Hbase 分布式集群架构

安装搭建Hadoop1、 配置说明本次集群搭建共三台机器&#xff0c;具体说明下&#xff1a;主机名IP说明nn01192.168.1.51DataNode、NodeManager、ResourceManager、NameNodedn01192.168.1.52DataNode、NodeManager、SecondaryNameNodedn02192.168.1.53DataNode、NodeManager2 、安…

基于浏览器的 PDF 编辑器:RAD PDF for ASP.NET

版本 3.34 改进的 PDF 收藏/投资组合支持和服务器 API 改进 Ω578867473功能更新 为更基本的 PDF 文件损坏/语法错误添加了更正添加了 PdfButtonField.NamedAction 属性添加了 PdfButtonField.IsNamedAction 属性添加 PdfButtonField() 构造函数 - PdfButtonFields 可以由服…

unity-常用组件实操案例

文章目录transform摄像机cameraskybox相机权重&#xff08;depth&#xff09;Audio sourcevideo playertransform 不但控制着组件的旋转、位置、缩放并且还控制着组件间的父子关系 using System; using System.Collections; using System.Collections.Generic; using UnityEn…

不锈钢企业如何利用APS排程软件提升管理效益?

保温杯一般是由陶瓷或不锈钢加上真空层做成的盛水容器&#xff0c;顶部有盖&#xff0c;密封严实&#xff0c;真空绝热层能使装在内部的水等液体延缓散热&#xff0c;以达到保温的目的。保温杯从保温瓶发展而来的&#xff0c;保温原理与保温瓶一样&#xff0c;只是人们为了方便…

Collection

面向对象语言对事物的体现都是以对象的形式&#xff0c;所以为了方便对多个对象的操作&#xff0c;就对对象进行存储&#xff0c;集合就是存储对象最常用的一种方式 数组和集合的不同&#xff1a; 数组长度是固定的&#xff1b;集合长度是可变的。 数组中可以存储基本数据类…

C#在控制台中打印进度条【同步和异步】

使用控制台打印进度条的简单方法。 有现成的IProgress接口进行操作&#xff1a; 实例&#xff1a; var prog new Progress<double>((theV > {Console.WriteLine($"Now the Progress&#xff1a;" COUNT / 10.0 * 100 "%" new string(#, COUN…

社科院与杜兰大学金融管理硕士---授人以鱼不如授人以渔,培养全新金融人才

古人云&#xff1a;“授人以鱼&#xff0c;三餐之需&#xff1b;授人以渔&#xff0c;终身之用”。都说职场入战场&#xff0c;一入职场就如履薄冰。走的每一步可能都影响着自己的职业生涯。在职场无烟的战争中&#xff0c;会慢慢发现差距一点点的被拉开了。金融是现代经济的血…

开发工具(二)基于Source Insight与Samba共享在windows中开发Linux工程

layout: post title: 开发工具&#xff08;二&#xff09;基于Source Insight与Samba共享在windows中开发Linux工程 description: 开发工具&#xff08;二&#xff09;基于Source Insight与Samba共享在windows中开发Linux工程 tag: 开发工具 文章目录资源共享流程说明Source In…

2023,让RFID固定资产管理系统助力企业降本增效

随着企业规模的不断扩大&#xff0c;企业的固定资产管理成为一个关键的问题。因为面临这不断增多的员工、固定资产种类和固定资产数量&#xff0c;企业管理者开始慢慢重视固定资产的管理&#xff0c;纷纷采取不同的方法加强对固定资产的管理。由于管理方法的不同&#xff0c;其…

C++ 智能指针 : auto_ptr 、unique_ptr、 shared_ptr、 weak_ptr

1、智能指针设计初衷&#xff1a; 智能指针实际是类&#xff0c;超过类的作用域后&#xff0c;析构函数会自动回收资源&#xff0c;为程序员管理申请的堆内存&#xff0c;避免内存泄漏 2、C 智能指针种类&#xff1a; auto_ptr &#xff08;C98 的⽅案&#xff0c;C11 已抛…

5G NR标准: 第17章 LTE/NR互通和共存

第17章 LTE/NR互通和共存 新一代移动通信技术的初始部署通常发生在交通密度高、对新服务能力要求高的地区。 然后是逐渐的进一步扩建&#xff0c;根据运营商的策略&#xff0c;扩建的速度或快或慢。 在随后的逐步部署过程中&#xff0c;新技术和传统技术的混合将提供对运营商…

实习------SpringBoot 框架

Spring Boot 是什么 (了解)Spring Boot 是 Spring 开源组织下的子项目&#xff0c;其设计目的是专注于Spring应用的开发&#xff0c;开发人员可以把更多的精力放在业务代码上&#xff0c;而无需过多关注XML的配置&#xff0c;从而简化Spring应用开发&#xff0c;提高开发效率Sp…