使用Go实现23种设计模式——结构型模式(上)
适配器模式
将一个类的接口转换成客户端希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
适用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
Go语言实现
type MysqlDbInterface interface {
InsertIntoDb()
}
type MysqlDb struct {
DbName string
}
// 系统中存在的接口方法:InsertIntoDb
func (d *MysqlDb) InsertIntoDb() {
fmt.Printf("hello, insert into mysql: %s\n", d.DbName)
}
// 新的接口方法:SaveToDb
type DbInterface interface {
SaveToDb()
}
type MysqlDbAdapter struct {
MysqlDbInterface
}
func (a *MysqlDbAdapter) SaveToDb() {
a.InsertIntoDb()
}
func main() {
var db DbInterface
db = &MysqlDbAdapter{MysqlDbInterface: &MysqlDb{DbName: "mysql"}}
db.SaveToDb()
}
适配器模式优点
- 可以让两个没有关联的类一起运行,复用了现有的类
- 目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
适配器模式缺点
- 增加了系统的复杂性
- 降低了代码可读性
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化
适用场景
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
- 不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统
Go语言实现
type MessageInterface interface {
Send(msg string) error
}
type EmailMessage struct {
}
func (m *EmailMessage) Send(msg string) error {
fmt.Printf("send %s", msg)
return nil
}
type NotificationInterface interface {
Notify(msg string) error
}
type InfoNotification struct {
message MessageInterface
}
func NewInfoNotification(message MessageInterface) *InfoNotification {
return &InfoNotification{message: message}
}
func (n *InfoNotification) Notify(msg string) error {
return n.message.Send(msg)
}
func main() {
notify := NewInfoNotification(&EmailMessage{})
err := notify.Notify("Info")
if err != nil {
return
}
}
桥接模式优点
- 抽象与实现分离,扩展能力强
- 符合开闭原则、合成复用原则
桥接模式缺点
- 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度
组合模式
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性
适用场景
- 公司部门架构等场景
Go语言实现
type IOrganization interface {
Count() int
}
type Employee struct {
Name string
}
func (*Employee) Count() int {
return 1
}
type Department struct {
Name string
SubOrganization []IOrganization
}
func (d *Department) Count() (count int) {
for _, organization := range d.SubOrganization {
count += organization.Count()
}
return count
}
func (d *Department) AddOrganization(organization IOrganization) {
d.SubOrganization = append(d.SubOrganization, organization)
}
组合模式优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”
组合模式缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系
- 不容易限制容器中的构件,不容易用继承的方法来增加构件的新功能
装饰模式
向现有的对象添加新的功能,同时又不改变其结构,即动态地给对象添加一些额外的功能
适用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时
Go语言实现
// 抽象装饰类
type IDecorate interface {
Run()
}
type Decorate struct {
Func func()
decorateBefore []IDecorate
decorateAfter []IDecorate
}
func (d *Decorate) Before(decorate ...IDecorate) {
d.decorateBefore = append(d.decorateBefore, decorate...)
}
func (d *Decorate) After(decorate ...IDecorate) {
d.decorateAfter = append(d.decorateAfter, decorate...)
}
func (d *Decorate) Run() {
for _, decorateBefore := range d.decorateBefore {
decorateBefore.Run()
}
d.Func()
for _, decorateAfter := range d.decorateAfter {
decorateAfter.Run()
}
}
// 具体装饰类
type ADecorate struct {
}
func (d *ADecorate) Run() {
fmt.Println("执行A")
}
type BDecorate struct {
}
func (d *BDecorate) Run() {
fmt.Println("执行B")
}
func main() {
a := Decorate{Func: func() {
fmt.Println("Hello")
}}
a.Before(&ADecorate{})
a.Before(&BDecorate{})
a.After(&ADecorate{})
a.Run()
}
装饰模式优点
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
- 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
- 装饰器模式完全遵守开闭原则
装饰模式缺点
- 装饰器模式会增加许多子类,过度使用会增加程序得复杂性