Go中interface编程思想
Go中interface编程思想
理解interface
- interface是方法声明的集合
- 任何类型的对象实现了interface接口中声明的全部方法,则表明该类型实现了接口
- interface可以作为一种数据类型,实现该接口的任何对象都可以对对应接口类型变量赋值。
开闭原则
interface的意义其实是为了满足软件设计的高内聚、低耦合
思想。那这其中就需要满意一些原则,如:开闭原则
。
举例:
package main
import "fmt"
type Banker struct {
}
// Save 存款业务
func (this *Banker) Save() {
fmt.Println("进行了 存款业务...")
}
// Transfer 转账业务
func (this *Banker) Transfer() {
fmt.Println("进行了 转账业务...")
}
// Pay 支付业务
func (this *Banker) Pay() {
fmt.Println("进行了 支付业务...")
}
func main() {
banker := &Banker{}
banker.Save()
banker.Pay()
banker.Transfer()
}
上面的例子很简单,银行业务有:存款业务、转账业务、支付业务。但是后面会增加很多很多业务的时候,这里业务模块会爆炸。
每次新增业务都要这里添加业务,如果新加的业务崩溃也会导致前面业务一起挂掉。业务都在一个Banker中,耦合度太高,开发人员维护成本太高。
如何优化?
我们可以使用interface将业务抽象出来,制作一个抽象的Banker模块,然后提供一个抽象的方法。 分别根据这个抽象模块,
去实现支付Banker(实现支付方法),转账Banker(实现转账方法)。
当我们给一个系统添加一个功能的时候,不是通过修改代码,而是通过增添代码来完成,那么就是开闭原则的核心思想了。所以要想满足上面的要求,
是一定需要interface来提供一层抽象的接口的。
代码修改如下:
func main() {
/* banker := &Banker{}
banker.Save()
banker.Pay()
banker.Transfer()*/
/*//进行存款
sb := &SaveBanker{}
sb.DoBusiness()
//进行转账
tb := &TransferBanker{}
tb.DoBusiness()
//进行支付
pb := &PayBanker{}
pb.DoBusiness()
*/
// 存款
BankerBusiness(&SaveBanker{})
// 转账
BankerBusiness(&TransferBanker{})
// 支付
BankerBusiness(&PayBanker{})
}
// AbstractBanker 抽象银行业务
type AbstractBanker interface {
DoBusiness()
}
// TransferBanker 转账业务员 进行转账业务
type TransferBanker struct {
}
func (tb *TransferBanker) DoBusiness() {
fmt.Println("用户转账...")
}
// SaveBanker 存库业务员 进行存库业务
type SaveBanker struct {
}
func (sb *SaveBanker) DoBusiness() {
fmt.Println("用户存款...")
}
// PayBanker 支付的业务员
type PayBanker struct {
//AbstractBanker
}
func (pb *PayBanker) DoBusiness() {
fmt.Println("进行支付")
}
// BankerBusiness 实现架构层(基于抽象层进行业务封装-针对interface接口进行封装)
func BankerBusiness(banker AbstractBanker) {
//通过接口来向下调用,(多态现象)
banker.DoBusiness()
}
依赖倒置
在软件设计的时候,一般为了业务功能扩展性会将模块分为3个层次,抽象层、实现层、业务逻辑层。
如何做?
先将抽象层的模块和接口定义出来, 这里就需要interface接口的设计,然后我们依照抽象层,依次实现每个实现层的模块,在我们写实现层代码的时候,实际上我们
只需要参考对应的抽象层实现就好了,实现每个模块,也和其他的实现的模块没有关系,这样也符合了上面介绍的开闭原则。这样实
现起来每个模块只依赖对象的接口,而和其他模块没关系,依赖关系单一。
指定业务逻辑也是一样,只需要参考抽象层的接口来业务就好了,抽象层暴露出来的接口就是我们业务层可以使用的方法,然后
可以通过多态的线下,接口指针指向哪个实现模块,调用了就是具体的实现方法,这样我们业务逻辑层也是依赖抽象成编程。
这种的设计原则就叫做依赖倒转原则。
示例:
组装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
card Card
memory Memory
}
func NewComputer(cpu CPU, card Card, memory Memory) *Computer {
return &Computer{
cpu: cpu,
card: card,
memory: memory,
}
}
func (c *Computer) DoWork() {
c.cpu.Calculate()
c.card.Display()
c.memory.Storage()
}
// 实现层
type IntelCard struct {
Card
}
func (intel *IntelCard) Display() {
fmt.Println("intel card 显示内容了。。。")
}
type IntelMemory struct {
Memory
}
func (intel *IntelMemory) Storage() {
fmt.Println("intel memory storage ...")
}
type IntelCpu struct {
CPU
}
func (intel *IntelCpu) Calculate() {
fmt.Println("intel calculate 计算内容...")
}
type KingstonMemory struct {
Memory
}
func (k *KingstonMemory) Storage() {
fmt.Println("Kingston's memory (内存3) storage ...")
}
type NVIDIACard struct {
Card
}
func (n *NVIDIACard) Display() {
fmt.Println("NVIDIA card 显示内容了。。。")
}
// 逻辑层
func main() {
//intel系列的电脑
com1 := NewComputer(&IntelCpu{}, &IntelCard{}, &IntelMemory{})
com1.DoWork()
// 其他牌子
com2 := NewComputer(&IntelCpu{}, &NVIDIACard{}, &KingstonMemory{})
com2.DoWork()
}
参考
- https://www.yuque.com/aceld/golang/uh0124