go语言【接口-组合】
一. 描述
go语言的接口设计其实就参考了鸭子类型(python)和java的接口
1. 什么是鸭子类型
python本身是基于鸭子类型设计的一门语言 - 协议最重要
其实类并没有继承任何类,但是实现了特定的方法名, 就成实现特定的功能,其实就是一种协议
而python的协议是怎么抛出来的,实际上就是python中的魔法方法
例如:
for语句 可以对dict, list tuple set等等类型进行for循环
for语句可以对iterable类型进行操作 只要你实现了__iter__那你就可以进行for循环
你的类继承了什么不重要 你的类名称不重要 重要的是你实现了什么魔法方法
2. 接口
二. 使用
1.定义
type 接口名称 interface { method1(参数列表) 返回值列表 method2(参数列表) 返回值列表 ... methodn(参数列表) 返回值列表 }
2. 多态
在开发环境中经常遇到一些问题, 例如:
开发一个电商网站, 支付环节 使用 微信、支付宝、银行卡 你的系统支持各种类型的支付 每一种支付类型都有统一的接口
解决方式,定一个协议 1. 创建订单 2. 支付 3. 查询支付状态 4. 退款
为什么时候多态,
什么类型的时候你申明的类型是一种兼容类型, 但是实际赋值的时候是另一种类型,如下
//type AliPay struct { // //} //type WeChat struct { // //} // //type Bank struct { // //} // //var b Bank //var a AliPay //var w WeChat //var x Tongyong //x = Bank{} //x = AliPay{}
好处:
1.多态 什么类型的时候你申明的类型是一种兼容类型, 但是实际赋值的时候是另一种类型
2.接口的强制性
其他场景:
现在有一个缓存 - 这个地方你一开始使用的缓存是redis 但是后期你考虑到可能使用其他的缓存技术 - 本地 memcache
3. 使用接口
创建顺序:
1. 定义接口,定义里面包含的func方法
2.定义结构体
3. 定义接口中实现的全部方法
注意:
var pro Programmer = Pythoner{"一个python开发者"}
在赋值的时候, Pythoner这个结构体必须实现接口Programmer中定义的Coding,Debug方法
package main import "fmt" type Programmer interface { Coding() string //方法只是申明 Debug() string } type Pythoner struct { Name string } func (p Pythoner) Coding() string { fmt.Println("Python 开发者") return "Python 开发者" } func (p Pythoner) Debug() string { fmt.Println("我会python的debug") return "我会python的debug" } func main() { var pro Programmer = Pythoner{"python开发者A"} pro.Debug() }
问题点:
我们为什么使用这中方式调用,
//var pro Programmer = Pythoner{"python开发者A"} //pro.Debug()
而不是使用这种方式呢
var per Pythoner = Pythoner{"python开发者B"} per.Debug()
其实功能都可以实现,但是如果新加一种语言的话,我们还得实例化另一种语言,代码扩展性特别差
4. 接口定义说明
接口虽然是一种类型 但是和其他类型不太一样 接口是一种抽象类型 struct是具象
在定义的时候,其实就是定义抽象类型肤质给具现
var pro Programmer = Pythoner{} fmt.Printf("%T\n", pro) var pro2 Programmer = G{} fmt.Printf("%T", pro2)
5. 接口的组合
package main import ( "fmt" ) //接口是一个协议- 程序员 - 只要你能够 1. 写代码 2. 解决bug 其实就是一组方法的集合 type Programmer interface { Coding() string //方法只是申明 Debug() string } type Designer interface { Design() string } type Manger interface { Programmer Designer Manage() string } //java的话 java里面一种类型只要继承一个接口 才行 如果你继承了这个接口的话 那么这个接口里面的所有方法你必须要全部实现 type UIDesigner struct { } func (d UIDesigner) Design() string { fmt.Println("我会ui设计") return "我会ui设计" } type Pythoner struct { UIDesigner lib []string kj []string years int } type G struct { } func (p G) Coding() string { fmt.Println("go开发者") return "go开发者" } func (p G) Debug() string { fmt.Println("我会go的debug") return "我会go的debug" } func (p Pythoner) Coding() string { fmt.Println("python开发者") return "python开发者" } func (p Pythoner) Debug() string { fmt.Println("我会python的debug") return "我会python的debug" } func (p Pythoner) Manage() string { fmt.Println("不好意思,管理我也懂") return "不好意思,管理我也懂" } func HandlePy(p Programmer) { } type MyError struct { } func (m MyError) Error() string { return "错误" } func main() { //新的语言出来了, 接口帮我们完成了go语言的多态 //var pro Programmer = Pythoner{} var pros []Programmer pros = append(pros, Pythoner{}) pros = append(pros, G{}) //接口虽然是一种类型 但是和其他类型不太一样 接口是一种抽象类型 struct是具象 p := Pythoner{} fmt.Printf("%T\n", p) var pro Programmer = Pythoner{} pro.Coding() fmt.Printf("%T\n", pro) var pro2 Programmer = G{} fmt.Printf("%T\n", pro2) //如果大家对象面向对象理解的话 java 里面的抽象类型 //1. go struct组合 组合一起实现了所有的接口的方法也是可以的 //2. 接口本身也支持组合 var m Manger = Pythoner{} m.Design() m.Debug() //python语言本身设计上是采用了完全的基于鸭子类型 - 协议 影响了python语法的 for len() //struct组合完成了接口 1. 接口支持组合 - 继承 2. 结构体组合实现了所有的接口方法也没有问题 //go语言本身也推荐鸭子类型 error //var err error = errors.New(fmt.Sprintf("")) s := "文件不存在" var err error = fmt.Errorf("错误:%s", s) fmt.Println(err) }
三. 空接口
1. 说明和用途1
Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示,没有任何约束,因此任何类型变量都可以实现空接口。空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型
可以把任何类型都赋值给空接口变量
package main import "fmt" type Course struct { name string price int url string } func main() { //空接口 var i interface{} //空接口 //空接口可以类似于我们java和python中的object i = Course{} fmt.Println(i) // 输出:{ 0 } i = []string{"django", "scrapy"} print(i) }
2. 用途2:参数传递
package main import "fmt" type Course struct { name string price int url string } func Print(x interface{}) { fmt.Println("%v\n", x) } func main() { //空接口 ii := 2 Print(ii) }
4. 用途3:可以作为map的值
package main import "fmt" func main() { // teacherInfo1有一个问题,因为value值已经确认是string类型,不能传任意类型 //var teacherInfo1 = make(map[string]string) //空接口 var teacherInfo = make(map[string]interface{}) teacherInfo["name"] = "bobby" teacherInfo["age"] = 18 teacherInfo["weight"] = 75.2 teacherInfo["courses"] = []string{"django", "scrapy", "sanic"} fmt.Printf("%v", teacherInfo) }
4. 用途4:类型的转换
package main import "fmt" //结构体实现了接口中的所有方法,那么我们就说这个结构体实现了这个接口 //类型转换,结构体转接口,接口转结构体 //实现接口后,就能实现特定的功能,比如 golang自定义排序 swap() less() len() sort() type Animal interface { Say() string } type Cat struct { Name string } func(c *Cat) Say() string { return c.Name + "喵喵喵" } type Dog struct { Name string } func(d *Dog) Say() string { return d.Name + "汪汪汪" } func main() { c := &Cat{Name:"小白猫"} d := &Dog{Name:"大黄狗"} var p1 Animal p1 = c fmt.Println(p1.Say()) p1 = d fmt.Println(p1.Say()) fmt.Println(transData(c)) fmt.Println(transData(d)) } //类型转换举例,隐式转换,将结构体类型转换为接口类型 func transData(a Animal) string { return fmt.Sprintf("%s%s",a.Say(),"处理后") }
四. 接口的断言
1. 判断类型
可以判断任意类型
func print(x interface{}) { //判断类型 switch v := x.(type) { case string: fmt.Printf("%s(字符串)\n", v) case int: fmt.Printf("%d(整数)\n", v) } }
第二种
package main import "fmt" //结构体实现了接口中的所有方法,那么我们就说这个结构体实现了这个接口 //类型转换,结构体转接口,接口转结构体 //实现接口后,就能实现特定的功能,比如 golang自定义排序 swap() less() len() sort() type Animal interface { Say() string } type Cat struct { Name string } func(c *Cat) Say() string { return c.Name + "喵喵喵" } type Dog struct { Name string } func(d *Dog) Say() string { return d.Name + "汪汪汪" } func main() { c := &Cat{Name:"小白猫"} d := &Dog{Name:"大黄狗"} var p1 Animal p1 = c fmt.Println(p1.Say()) p1 = d fmt.Println(p1.Say()) fmt.Println(transData(c)) //fmt.Println(transData(d)) } //类型转换举例,隐式转换,将结构体类型转换为接口类型 //结构体转换为interface,是从下往上转,是隐式转换 //interface转换为结构体,是从上往下转,是显示转换,专业名词:断言 //没断言之前,只能拿到接口的方法,断言后,能拿到对应类型的所有属性和方法 func transData(a Animal) string { v, ok := a.(*Cat) if ok { fmt.Println("断言成功") } else { fmt.Println("断言失败") } fmt.Println(v.Name) return fmt.Sprintf("%s%s",a.Say(),"处理后") }
2. 存储到不同位置
package main import "fmt" type AliOss struct { } type LocalFile struct { } func store(x interface{}) { switch v := x.(type) { case AliOss: //此处要做一些特殊的处理,我设置阿里云的权限问题 fmt.Println(v, "阿里") case LocalFile: //检查路径的权限 fmt.Println(v, "本地") } } func main() { a := AliOss{} store(a) }
四. 使用场景
工厂模式(Factory Pattern)
工厂模式是一种创建型设计模式,用于将对象的创建过程封装起来,由子类决定实例化哪一个类。这种模式使得代码结构更加清晰,并且能够轻松替换或扩展产品类。
特点:
-
封装性:将对象的创建过程封装在工厂类中。 -
扩展性:通过继承和多态,可以轻松地添加新的产品类。 -
抽象性:工厂方法定义了创建对象的接口,但具体对象的创建由子类实现。
优点:
-
将对象的创建和使用分离,提高了模块间的独立性。 -
易于扩展,增加新的产品类时不需要修改现有代码,符合开闭原则。
缺点:
-
每增加一个产品类,就需要增加一个具体的工厂类,这可能会导致类的数量急剧增加。 -
工厂类集中了所有实例的创建逻辑,可能会导致工厂类过于庞大 -
应用场景:
-
数据库连接:根据不同的数据库类型,如MySQL、PostgreSQL,创建相应的数据库连接对象。 -
GUI组件:在图形用户界面开发中,不同的操作系统可能需要不同的组件实现,工厂模式可以根据不同平台创建相应的组件。 -
支付网关:根据不同的支付方式,如信用卡、PayPal、微信支付,创建相应的支付处理对象。 -
图像处理:在图像处理软件中,根据不同的文件格式,如JPEG、PNG,创建相应的图像处理器
-
package main import "fmt" // 定义一个接口Product,它声明了所有具体产品对象必须实现的操作 type Product interface { operation() // 产品对象的操作 } // 定义具体产品ConcreteProductA,实现了Product接口 type ConcreteProductA struct{} func (p *ConcreteProductA) operation() { fmt.Println("Operation of ConcreteProductA") } // 定义另一个具体产品ConcreteProductB,也实现了Product接口 type ConcreteProductB struct{} func (p *ConcreteProductB) operation() { fmt.Println("Operation of ConcreteProductB") } // 定义一个抽象工厂Creator,它声明了工厂方法factoryMethod,用于创建产品对象 type Creator interface { factoryMethod() Product // 工厂方法,用于创建产品对象 } // 定义具体工厂CreatorA,实现了Creator接口 type CreatorA struct{} func (c *CreatorA) factoryMethod() Product { return &ConcreteProductA{} // 具体工厂CreatorA返回ConcreteProductA的实例 } // 定义另一个具体工厂CreatorB,也实现了Creator接口 type CreatorB struct{} func (c *CreatorB) factoryMethod() Product { return &ConcreteProductB{} // 具体工厂CreatorB返回ConcreteProductB的实例 } func main() { // 创建具体工厂CreatorA的实例 creatorA := &CreatorA{} productA := creatorA.factoryMethod() productA.operation() // 调用产品A的操作 // 创建具体工厂CreatorB的实例 creatorB := &CreatorB{} productB := creatorB.factoryMethod() productB.operation() // 调用产品B的操作 // 创建具体工厂CreatorA的实例 var creator Creator = &CreatorA{} productC := creator.factoryMethod() // 通过工厂方法创建产品 productC.operation() // 调用产品A的操作 // 创建具体工厂CreatorB的实例 creator = &CreatorB{} productD := creator.factoryMethod() // 通过工厂方法创建产品 productD.operation() // 调用产品B的操作 }
解释:
Creator
接口:这是工厂接口,声明了factoryMethod()
方法,用于创建Product
类型的对象。ConcreteProductA
和ConcreteProductB
:这是两个具体的产品,分别实现了Product
接口。CreatorA
和CreatorB
:这是两个具体的工厂,分别返回ConcreteProductA
和ConcreteProductB
的实例。main()
函数:在主函数中,通过具体的工厂(CreatorA
或CreatorB
),调用工厂方法来获取对应的产品对象,并调用产品的操作方法。
观察者模式(Observer Pattern)
观察者模式是一种行为设计模式,它定义了对象间的一种一对多的依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。这种模式非常适合于实现分布式事件处理系统。
特点:
-
一对多关系:一个主题可以有多个观察者。 -
抽象耦合:观察者和主题之间是抽象耦合的,增加新的观察者不会影响现有的系统。 -
动态联动:观察者可以在任何时候加入或退出。
优点:
-
降低了对象之间的耦合度,主题与观察者之间是松散耦合的。 -
扩展性好,增加新的观察者或主题类不影响现有的类。
缺点:
-
当观察者对象很多时,通知的分发可能会造成性能问题。 -
如果观察者和主题之间的依赖关系过于复杂,会导致系统难以维护。
应用场景:
-
事件监听系统:在GUI应用程序中,用户界面组件(如按钮、文本框等)可以作为观察者,监听用户的输入事件。 -
UI更新:在应用程序中,当数据模型发生变化时,界面需要相应地更新,使用观察者模式可以自动完成这一过程。 -
消息系统:在即时通讯软件中,当有新消息到达时,所有在线的用户(观察者)都会收到通知。 -
股票市场:股票价格更新时,所有订阅了该股票的投资者(观察者)都会收到最新价格信息。 -
资源监控:在系统监控工具中,当系统资源(如CPU、内存使用率)超过设定阈值时,监控系统(观察者)会收到通知并采取相应措施。
package main import "fmt" // 定义Observer接口,它声明了观察者需要实现的Update方法 type Observer interface { Update(string) // 当主题状态改变时,此方法会被调用 } // 定义Subject结构体,它包含一个观察者列表和方法来添加或通知观察者 type Subject struct { observers []Observer // 存储观察者的列表 } // Attach方法用于将一个观察者添加到观察者列表中 func (s *Subject) Attach(observer Observer) { s.observers = append(s.observers, observer) } // Notify方法用于通知所有观察者主题状态的改变 func (s *Subject) Notify(message string) { for _, observer := range s.observers { observer.Update(message) // 调用每个观察者的Update方法 } } // 定义一个具体观察者ConcreteObserverA,它实现了Observer接口 type ConcreteObserverA struct { name string } // 实现Observer接口的Update方法 func (c *ConcreteObserverA) Update(message string) { fmt.Printf("%s received message: %s\n", c.name, message) } func main() { // 创建主题对象 subject := &Subject{} // 创建具体观察者对象 observerA := &ConcreteObserverA{name: "Observer A"} // 将观察者添加到主题的观察者列表中 subject.Attach(observerA) // 当主题状态改变时,通知所有观察者 subject.Notify("State changed to State 1") }
代码解释:
-
Observer 接口:
- 定义了
Update
方法,所有的观察者必须实现这个方法。主题状态改变时,这个方法会被调用,传入变化的信息。
- 定义了
-
Subject 结构体:
- 它是观察者的管理者,维护一个观察者列表(
observers []Observer
)。 Attach(observer Observer)
:用来添加观察者到列表中。Notify(message string)
:当状态改变时,遍历所有观察者并调用它们的Update
方法,将状态变化的消息通知给它们。
- 它是观察者的管理者,维护一个观察者列表(
-
ConcreteObserverA 结构体:
- 它是一个具体的观察者,实现了
Observer
接口中的Update
方法。 Update
方法接受一个字符串作为参数,表示收到的消息,并输出到控制台。
- 它是一个具体的观察者,实现了
-
main 函数:
- 创建了一个
Subject
对象,它是观察者模式中的主题。 - 创建了一个具体的观察者
ConcreteObserverA
。 - 使用
Attach
方法将观察者observerA
添加到主题的观察者列表中。 - 使用
Notify
方法,当主题状态变化时,向所有的观察者发送通知。
- 创建了一个
工作流程:
Subject
是观察者模式中的主题,当调用Notify
方法时,所有通过Attach
方法注册的观察者都会接收到状态更新的通知。- 这里的
ConcreteObserverA
收到消息后,会输出收到的消息内容。
在部署任务的生产和消费中,观察者模式可以用来解耦生产者(任务生成者)和消费者(任务处理者)。以下是如何使用观察者模式来处理部署任务的生产和消费的一个示例。
package main import ( "fmt" ) // Observer 接口声明了观察者需要实现的 Update 方法 type Observer interface { Update(task string) // 当有新任务发布时,此方法会被调用 } // Subject 结构体包含一个观察者列表和方法来添加、移除和通知观察者 type Subject struct { observers []Observer // 存储观察者的列表 } // Attach 方法用于将一个观察者添加到观察者列表中 func (s *Subject) Attach(observer Observer) { s.observers = append(s.observers, observer) } // Detach 方法用于将一个观察者从观察者列表中移除 func (s *Subject) Detach(observer Observer) { for i, o := range s.observers { if o == observer { s.observers = append(s.observers[:i], s.observers[i+1:]...) break } } } // Notify 方法用于通知所有观察者有新任务 func (s *Subject) Notify(task string) { for _, observer := range s.observers { observer.Update(task) // 调用每个观察者的 Update 方法 } } // ConcreteConsumer 结构体实现了 Observer 接口 type ConcreteConsumer struct { name string } // 实现 Observer 接口的 Update 方法 func (c *ConcreteConsumer) Update(task string) { fmt.Printf("%s received task: %s\n", c.name, task) } // Producer 结构体负责发布任务 type Producer struct { subject *Subject } // PublishTask 方法用于发布任务 func (p *Producer) PublishTask(task string) { fmt.Println("Publishing task:", task) p.subject.Notify(task) // 通知所有观察者 } func main() { // 创建 Subject 实例 subject := &Subject{} // 创建 Producer 实例 producer := &Producer{subject: subject} // 创建 ConcreteConsumer 实例 consumer1 := &ConcreteConsumer{name: "Consumer 1"} consumer2 := &ConcreteConsumer{name: "Consumer 2"} // 将消费者添加到 Subject subject.Attach(consumer1) subject.Attach(consumer2) // 生产者发布任务 producer.PublishTask("Deploy version 1.0.0") }
装饰者模式(Decorator Pattern)
装饰者模式是一种结构型设计模式,允许用户在不修改对象自身的基础上,通过添加装饰者对象来动态地给对象添加额外的职责或功能。
特点:
-
动态扩展:可以在运行时动态地给对象添加职责。 -
透明性:装饰者模式不改变对象的接口,因此对客户端来说是透明的。 -
灵活性:可以多个装饰者组合使用,为对象添加多个职责。
优点:
-
增加对象的职责是动态的、可撤销的。 -
可以用多个装饰者包装一个对象,添加多个职责。 -
装饰者和对象可以独立变化,不会相互耦合。
缺点:
-
过度使用装饰者模式可能会使系统变得复杂,难以理解。 -
可能会引起多层装饰者调用,影响性能。
应用场景:
-
日志记录:在不修改原有对象的基础上,添加日志记录功能。 -
缓存:为对象的某些方法添加缓存功能,以提高性能。 -
安全控制:为对象添加访问控制,如权限检查。 -
事务处理:为数据库操作添加事务管理功能。 -
性能监控:为对象的方法添加性能监控功能,以分析性能瓶颈。 -
资源管理:为资源使用添加额外的管理功能,如连接池的管理。
package main import "fmt" // Component 接口是所有组件和装饰者的基类 type Component interface { operation() // 组件执行的操作 } // ConcreteComponent 结构体实现了 Component 接口 type ConcreteComponent struct{} // 实现 Component 接口的 operation 方法 func (c *ConcreteComponent) operation() { fmt.Println("ConcreteComponent: performing basic operation") } // Decorator 结构体实现了 Component 接口,并包含一个 Component 类型的字段 type Decorator struct { component Component // 用于组合 Component 接口 } // 实现 Decorator 的 operation 方法,调用其 Component 的 operation 方法 func (d *Decorator) operation() { if d.component != nil { d.component.operation() // 调用被装饰者的 operation 方法 } } // ConcreteDecoratorA 结构体嵌入了 Decorator 结构体,实现了装饰功能 type ConcreteDecoratorA struct { Decorator // 继承 Decorator,实现装饰功能 } // 为 ConcreteDecoratorA 实现 operation 方法,添加额外的职责 func (cda *ConcreteDecoratorA) operation() { cda.Decorator.operation() // 首先调用被装饰者的 operation 方法 fmt.Println("ConcreteDecoratorA: added additional responsibilities") } func main() { // 创建具体组件 component := &ConcreteComponent{} // 创建装饰者并关联具体组件 decoratorA := &ConcreteDecoratorA{Decorator{component}} // 执行装饰后的组件操作 decoratorA.operation() }