结构型设计模式 #
组合模式 #
介绍 #
组合模式是指将一组相似的对象当作一个单一对象的设计模式。
组合模式描述了一组对象,这些对象被视为相同类型对象的单个实例。组合模式可以将对象组合成树形结构,从而表示部分或整体的层次结构。
组合模式允许开发者拥有一个树形结构,并且要求树形结构中的每个节点都执行一项任务。组合模式的主要功能是在整个树形结构中递归调用方法并对结果进行汇总。
使用场景:
- 当客户需要忽略组合对象和单个对象之间的差异时。如果开发者以相同的方式使用多个对象,并且用几乎相同的代码处理每个对象。
- 如果需要实现树形结构。只需要通过请求树的顶层对象,就可以对整棵树进行统一操作。在组合模式中,添加和删除数的节点非常方便,并且遵循开闭原则。
- 如果开发者希望客户端可以以统一的方式处理简单或复杂的元素。
接口隔离原则要求开发者尽量将臃肿庞大的接口拆分成更小、更具体的接口,使接口中只包含客户端感兴趣的方法。
// 组件接口
type Component interface {
Execute()
}
// 叶节点,用于描述层次结构中的原始叶节点对象
type Leaf struct {
value int
}
// 创建一个新的叶节点对象
func NewLeaf(value int) *Leaf {
return &Leaf{value}
}
// 打印叶节点对象的值
func (l *Leaf) Execute() {
fmt.Printf("%v ", l.value)
}
定义组件类,用于表示复杂元素。该数组必须能同时存储叶节点和组合,因此需要确保将其声明为组件接口类型。在实现组件接口中的方法时,组合应该将大部分工作交给其子元素完成。
// 组件的组合
type Composite struct {
children []Component
}
// 创建一个新的组合对象
func NewComposite() *Composite {
return &Composite{make([]Component, 0)}
}
// 将一个新组件添加到组合中
func (c *Composite) Add(component Component) { //传入就将结构体赋值给接口
c.children = append(c.children, component)
}
// 遍历复合子对象
func (c *Composite) Execute() {
for i := 0; i < len(c.children); i++ {
c.children[i].Execute()
}
}
func main() {
composite := NewComposite() //获取一个结构体,里面是一个接口数组
leaf1 := NewLeaf(99) //获得一个叶子节点结构体,将99 赋值到里面
composite.Add(leaf1) //将结构体放入接口数组中,传入的时候就将结构体给接口了
leaf2 := NewLeaf(100) //同样的
composite.Add(leaf2)
leaf3 := NewComposite() //获取一个结构体,里面是一个接口数组
composite.Add(leaf3) //接口数组,加入到接口数组,可递归 相当于把主节点放入,递归执行叶子节点
composite.Execute() //遍历接口数组,执行相应的方法
}
优点 #
- 开发者无需了解构成树形结构的对象的具体类,也无需了解对象是简明的文件,还是复杂的文件夹,只需要调用通用接口中的方法,并且以相同的方式对其进行处理。在开发者调用该方法后,对象会将请求沿着树形结构传递下去。
- 客户端可以使用组件对象与复合结构体中的对象进行交互。
- 如果调用的是叶节点对象,则直接处理请求。
- 如果调用的是组合对象,那么组合模式会将请求转发给它的子组件。
缺点 #
- 组合模式一旦定义了树形结构,复合设计就会使树过于笼统。
- 组合模式很难将树的组件限制为特定的类型。
- 为了强制执行这种约束,程序必须依赖运行时检查,因为组合模式不能使用编程语言的类型系统。
示例 #
一个文件存储系统,系统中有两类对象,分别是文件和文件夹。一个文件夹可以包含多个文件和文件夹,这些内嵌文件夹中同样可以包含多个文件或文件夹,以此类推。如何计算每个用户存储的文件总数量和总存储空间的大小?
type File struct {
Name string
}
func (f *File) Search(keyword string) {
fmt.Printf("在文件 %s 中递归搜索关键 %s \n", f.Name, keyword)
}
func (f *File) GetName() string {
return f.Name
}
type Folder struct {
Components []Component
Name string
}
func (f *Folder) Search(keyword string) {
fmt.Printf("在文件夹 %s 中递归搜索关键 %s \n", f.Name, keyword)
for _, composite := range f.Components {
composite.Search(keyword)
}
}
func (f *Folder) Add(c Component) {
f.Components = append(f.Components, c)
}
type Component interface {
Search(string)
}
func main() {
File1 := &File{Name: "File1"}
File2 := &File{Name: "File2"}
File3 := &File{Name: "File3"}
Folder1 := &Folder{
Name: "Folder1",
}
Folder1.Add(File1)
Folder2 := &Folder{
Name: "Folder2",
}
Folder2.Add(File2)
Folder2.Add(File3)
Folder2.Add(Folder1) //构造文件夹
Folder2.Search("keyword") //递归执行
}
//在文件夹 Folder2 中递归搜索关键 keyword
//在文件 File2 中递归搜索关键 keyword
//在文件 File3 中递归搜索关键 keyword
//在文件夹 Folder1 中递归搜索关键 keyword
//在文件 File1 中递归搜索关键 keyword
适配器模式 #
适配器模式是指将一个类的接口转换成客户端希望的另一个接口,是原本因接口不兼容而不能一起工作的类可以一起工作。
适配器模式分为对象适配器模式和类适配器模式。类适配器模式的类之间耦合度比对象适配器模式的类之间耦合度高,并且要求开发者了解现有组件库中相关组件的内部结构,所以使用场景相对较少。适配器可以担任两个对象之间的分装器,它可以接收对一个对象的调用命令,并且将其转换为另一个对象可识别的格式和接口。
使用场景:
- 当开发者希望使用某个类,但是其接口与其他代码不兼容时,或者当开发者使用两个不兼容的系统、类或接口时,可以使用适配器模式。适配器模式使代码更简单、一致且易于推理。
- 当系统 需要使用一些现有的类,而这些类的接口不符合系统的要求,甚至没有这些类的源代码时,可以使用适配器模式。
- 当开发者需要创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类(包括一些可能在将来引入的类)一起工作时。
对象适配器模式 #
通过关联实现适配
//适配者类
type ObjectAdaptee struct {
}
// 目标接口
type ObjectTarget interface {
Execute()
}
//适配者类的方法
func (b *ObjectAdaptee) SpecificExecute() {
fmt.Println("最终执行的方法")
}
//适配器类
type ObjectAdapter struct { //结构体套原来结构体
Adaptee ObjectAdaptee
}
// 适配器类的方法
func (p *ObjectAdapter) Execute() { //方法里面调用原来的方法
p.Adaptee.SpecificExecute()
}
func main() {
//创建客户端
adapter := ObjectAdapter{} //简单来说就在外面套一层
adapter.Execute()
}
类适配器模式 #
通过继承实现适配
// Adaptee 定义了需要被适配的类
type Adaptee struct {
}
// Target 是要适配的目标接口
type Target interface {
Execute()
}
//定义了用于执行的方法SpecificExecute()
func (a *Adaptee) SpecificExecute() {
fmt.Println("最终执行的方法")
}
// Adapter 是新接口 Target 的适配器,继承了 Adaptee 类
type Adapter struct {
*Adaptee
}
// 实现 Target 接口,同时继承了 Adaptee 类
func (a *Adapter) Execute() {
a.SpecificExecute()
}
func main() {
//创建客户端
adapter := Adapter{} //跟上面一样,区别在于直接继承
adapter.Execute()
}
优点 #
- 适配器模式可以将多个不同的适配者类适配到同一个目标接口,有助于提高可重用性和灵活性。
- 可以适配一个适配者类的子类,由于适配器类试适配者类的子类,因此可以在适配器类中置换一些适配者类的方法,使适配器类的灵活性更强。
- 客户端不会因为使用不同的接口而变得复杂,并且可以很方便地在适配器类的不同实现之间进行交换。
缺点 #
- 在适配器模式中,要在适配器类中置换适配者类的某些方法不是很方便。
- 适配器模式的所有请求都会被转发,开销略有增加。
示例 #
//电脑接口
type Computer interface {
ConvertToUSB()
}
//客户端
type Client struct {
}
//将Lightning类型接口插入电脑
func (c *Client) InsertIntoComputer(com Computer) {
fmt.Println("客户端将Lightning类型接口插入计算机")
com.ConvertToUSB()
}
//Mac系统
type Mac struct {
}
//插入接口
func (m *Mac) ConvertToUSB() {
fmt.Println("Lightning类型接口已插入Mac电脑")
}
//Windows操作系统
type Windows struct{}
//插入USB接口到Windows电脑
func (w *Windows) InsertIntoUSB() {
fmt.Println("USB接口已插入Windows电脑")
}
//Windows系统适配器
type Adapter struct {
WindowsMachine *Windows
}
func (w *Adapter) ConvertToUSB() {
fmt.Println("适配器将Lightning类型信号转换为USB")
w.WindowsMachine.InsertIntoUSB()
}
func main() {
//创建客户端
Client := &Client{}
Mac := &Mac{}
//客户端插入Lightning类型连接器到Mac电脑
Client.InsertIntoComputer(Mac)
WindowsAdapter := &Windows{}
WindowsAdapterAdapter := &Adapter{
WindowsMachine: WindowsAdapter,
}
//客户端插入Lightning类型连接器到Windows适配器
Client.InsertIntoComputer(WindowsAdapterAdapter)
}
//$ go run main.go
//Lightning类型接口已插入Mac电脑
//客户端将Lightning类型接口插入计算机
//适配器将Lightning类型信号转换为USB
//USB接口已插入Windows电脑
桥接模式 #
介绍 #
桥接模式是将实现类分装在接口或抽象类内部的设计模式。
桥接模式将抽象部分与实现部分分离,使它们可以独立变化。它是用组合关系替代继承关系实现的,可以降低抽象部分和实现部分这两个可变维度的耦合度。桥接模式可以将业务逻辑或一个大类拆分为不同的层次结构,从而独立的进行开发。
使用场景:
- 如果开发者要拆分或重组一个具有多重功能的庞杂类(如能与多个数据库服务器进行交互的类),则可以使用桥接模式。
- 如果开发者希望在几个独立维度上扩展一个类,则可以使用桥接模式。
- 如果开发者需要在运行时切换不同的实现,则可以使用桥接模式。
- 如果开发者希望避免抽象部分与其实现方法之间的永久绑定,则可以使用桥接模式。
- 抽象部分及其实现方法都应该可以通过子类化扩展。
// 实现接口
type Implementor interface {
Implementation(str string)
}
// 具体实现
type ConcreteImplementor struct{}
func (*ConcreteImplementor) Implementation(str string) {
fmt.Printf("打印信息:[%v]", str)
}
// 初始化具体实现对象
func NewConcreteImplementor() *ConcreteImplementor {
return &ConcreteImplementor{}
}
// 抽象接口
type Abstraction interface {
Execute(str string)
}
// 扩充抽象
type RefinedAbstraction struct {
method Implementor
}
// 扩充抽象方法
func (c *RefinedAbstraction) Execute(str string) {
c.method.Implementation(str)
}
// 初始化扩充抽象对象
func NewRefinedAbstraction(im Implementor) *RefinedAbstraction {
return &RefinedAbstraction{method: im}
}
func main() {
concreteImplementor := NewConcreteImplementor() //创建结构体指针
refinedAbstraction := NewRefinedAbstraction(concreteImplementor)
refinedAbstraction.Execute("Hello Bridge~")
}
打印信息:[Hello Bridge~]
优点 #
- 桥接模式可以提高代码的可伸缩性,开发者在添加功能时无需担心破坏程序的其他部分
- 当实体的数量基于两个概念的组合(如形状和颜色时),桥接模式可以减少子类的数量
- 桥接模式可以分别处理两个独立的层次结构——抽象和实现。两个不同的开发者可以在不深入研究彼此代码细节的情况下对程序进行修改。
- 桥接模式可以降低类之间的耦合度—-两个类耦合的唯一地方是桥。
缺点 #
- 根据具体情况和项目的整体结构,桥接模式可能会对程序的性能产生负面影响。
- 由于需要在两个类之间切换,因此桥接模式会使代码的可读性降低。
示例 #
(1)定义抽象接口计算机接口
//电脑接口
type Computer interface {
Print()
SetPrinter(Printer)
}
(2)定义扩充抽象类
// Mac系统
type Mac struct {
Printer Printer
}
// 打印
func (m *Mac) Print() {
fmt.Println("Print request for Mac")
m.Printer.PrintFile()
}
// 设置打印机
func (m *Mac) SetPrinter(p Printer) {
m.Printer = p
}
// Windows系统
type Windows struct {
Printer Printer
}
// 打印
func (w *Windows) Print() {
fmt.Println("Print request for Windows")
w.Printer.PrintFile()
}
// 设置打印机
func (w *Windows) SetPrinter(p Printer) {
w.Printer = p
}
(3)定义实现接口Printer
// 打印机接口
type Printer interface {
PrintFile()
}
(4)定义具体实现类
// 联想打印机
type Lenovo struct {
}
// 打印文件
func (p *Lenovo) PrintFile() {
fmt.Println("Printing by a Lenovo Printer")
}
// 佳能打印机
type Canon struct {
}
// 打印文件
func (p *Canon) PrintFile() {
fmt.Println("Printing by a Canon Printer")
}
(5)客户端
func main2() {
//联想打印机
lenovoPrinter := &Lenovo{}
//佳能打印机
canonPrinter := &Canon{}
//Mac打印
macComputer := &Mac{}
//Mac电脑用SetPrinter()方法设置联想打印机
macComputer.SetPrinter(lenovoPrinter)
macComputer.Print()
fmt.Println()
//Mac电脑用SetPrinter()方法设置佳能打印机
macComputer.SetPrinter(canonPrinter)
macComputer.Print()
fmt.Println()
winComputer := &Windows{}
//Windows电脑用SetPrinter()方法设置联想打印机
winComputer.SetPrinter(lenovoPrinter)
winComputer.Print()
fmt.Println()
///Windows电脑用SetPrinter()方法设置佳能打印机
winComputer.SetPrinter(canonPrinter)
winComputer.Print()
fmt.Println()
}
//Print request for Mac
//Printing by a Lenovo Printer
//
//Print request for Mac
//Printing by a Canon Printer
//
//Print request for Windows
//Printing by a Lenovo Printer
//
//Print request for Windows
//Printing by a Canon Printer
装饰器模式 #
介绍 #
装饰器模式是旨在不改变现有对象结构的情况下,动态的给该对象增加一些职责(增加额外功能)的设计模式,它属于对象结构型设计模式。
装饰器模式会创建一个装饰类,包装原始类并提供额外的功能,使类的方法签名保持不变。
使用场景:
- 如果开发者希望在不修改代码的情况下使用对象,并且在运行时为对象添加额外的功能,则可以使用装饰器模式。
- 装饰器模式可以将业务逻辑按照层次结构进行分类,开发者可以为每个分类创建一个装饰器,并且将不同的装饰器组合起来。由于这些对象都可以与通用接口进行交互,因此客户端能以相同的方式使用这些对象。
- 如果通过继承扩展对象行为的方案难以实现或根本行不通,则可以使用装饰器模式。
在使用装饰器模式前,先确保业务逻辑可以用一个基本组件及多个额外的可选层次结构表示。
(1)找出基本组件和可选层次的通用方法
// 组件接口
type Component interface {
Operation()
}
(2)定义具体组件类及方法
// 具体组件
type ConcreteComponent struct {
}
// 具体组件方法
func (c *ConcreteComponent) Operation() {
fmt.Println("具体的对象开始操作...")
}
(3)定义装饰器类及其方法
// 装饰
type Decorator struct {
component Component
}
// 装饰设置组件方法
func (d *Decorator) SetComponent(c Component) { //接口初始化
d.component = c
}
// 装饰方法
func (d *Decorator) Operation() {
if d.component != nil {
d.component.Operation()
}
}
(4)将装饰器类扩展为具体装饰器类
// 具体装饰器A
type DecoratorA struct {
Decorator
}
// 具体装饰器A的方法
func (d *DecoratorA) Operation() {
d.component.Operation() //调到ConcreteComponent结构哪里的方法
d.IndependentMethod()
}
func (d *DecoratorA) IndependentMethod() {
fmt.Println("装饰A扩展的方法~")
}
// 具体装饰器B
type DecoratorB struct {
Decorator
}
// 具体装饰器B的方法
func (d *DecoratorB) Operation() {
d.component.Operation() //调用operation方法 调到A哪里,
fmt.Println(d.String())
}
// 具体装饰器B的拓展方法
func (d *DecoratorB) String() string {
return "装饰B扩展的方法~"
}
(5)客户端
func main() {
concreteComponent := &ConcreteComponent{} //得到ConcreteComponent结构体指针
decoratorA := &DecoratorA{} //结构体A指针
decoratorB := &DecoratorB{} //结构体B指针
decoratorA.SetComponent(concreteComponent) //结构体指针进入方法,传给接口
decoratorB.SetComponent(decoratorA) //B的接口是A结构体
decoratorB.Operation()
}
//$ go run main.go
//具体的对象开始操作...
//装饰A扩展的方法~
//装饰B扩展的方法~
优点 #
- 装饰器模式为扩展功能提供了一种灵活替代子类的方法
- 允许在运行时修改方法,而不是返回现有代码并进行修改
- 装饰器模式是排列问题的一个很好的解决方案,因为开发者可以用任意数量的装饰器包装一个组件
- 装饰器模式支持”类应该对外开放,对修改关闭“的原则。
缺点 #
- 装饰器模式在设计中可能会创建许多小对象,过度使用可能会很复杂。
- 如果客户端严重依赖组件的具体类型,那么使用装饰器模式可能会导致其他问题。
- 装饰器模式会使实例化组件的过程复杂化,因为开发者不仅要实例化组件还要将其包装在多个装饰器中
- 让装饰器跟踪其他装饰器可能会很复杂。
示例 #
(1)定义手机零件接口
type Phone interface {
GetPrice() float32
}
(2)定义基础零件类
// 基础零件
type BaseParts struct {
}
// 获取基础零件手机价格
func (p *BaseParts) GetPrice() float32 {
return 2000
}
(3)定义装饰器类及其方法
// 装饰
type Decorator struct {
Phone Phone //手机零件接口类型
}
// 装饰设置组件方法
func (d *Decorator) SetComponent(c Phone) {
d.Phone = c
}
// 装饰方法
func (d *Decorator) GetPrice() {
if d.Phone != nil {
d.Phone.GetPrice()
}
}
(4)定义具体装饰器类
type IPhone struct {
Decorator
}
// 获取IPhone价格
func (c *IPhone) GetPrice() float32 {
phonePrice := c.Phone.GetPrice()
return phonePrice + 6000
}
type Xiaomi struct {
Decorator
}
// 小米手机的价格
func (c *Xiaomi) GetPrice() float32 {
phonePrice := c.Phone.GetPrice()
return phonePrice + 1000
}
(5)创建客户端
func main() {
//具体零件
phone := &BaseParts{}
fmt.Printf("基础零件的价格为:%f\n", phone.GetPrice())
//定义添加IPhone手机
iPhone := &IPhone{}
iPhone.SetComponent(phone) //结构体传入给到接口
fmt.Printf("苹果的价格为:%f\n", iPhone.GetPrice())
//定义添加Xiaomi手机
xiaomi := &Xiaomi{}
xiaomi.SetComponent(phone)
fmt.Printf("小米的价格为:%f\n", xiaomi.GetPrice())
}
//基础零件的价格为:2000.000000
//苹果的价格为:8000.000000
//小米的价格为:3000.000000
外观模式 #
介绍 #
外观模式是一种通过为多个复杂的子系统提供一个一致的接口,使这些子系统更容易被访问的设计模式。外观模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体细节,从而大幅降低应用程序的复杂度,增强应用程序的可维护性。外观模式可以为复杂系统,程序库或框架提供一个简单的接口。
使用场景:
- 如果开发者需要一个指向复杂子系统的直接接口,并且该接口的功能有限,则可以使用外观模式。
- 如果需要子系统组织为多层结构,则可以使用外观模式。通过创建外观模式定义子系统中各层次的入口。开发者可以要求子系统仅使用外观模式进行交互,从而降低子系统之间的耦合度。
(1)定义外观类
// 外观类
type Facade struct {
subSystemA SubSystemA
subSystemB SubSystemB
}
// 初始化
func NewFacade() *Facade {
return &Facade{
subSystemA: SubSystemA{},
subSystemB: SubSystemB{},
}
}
(2)定义外观类方法
// 外观方法A
func (c *Facade) MethodA() {
c.subSystemB.MethodThree()
c.subSystemA.MethodOne()
c.subSystemB.MethodFour()
}
// 外观方法B
func (c *Facade) MethodB() {
c.subSystemB.MethodFour()
c.subSystemA.MethodTwo()
}
(3)定义子系统类及方法
// 子系统A
type SubSystemA struct {
}
// 初始化子系统A
func NewSubSystemA() *SubSystemA {
return &SubSystemA{}
}
// 子系统A方法
func (c *SubSystemA) MethodOne() {
fmt.Println("SubSystemA - MethodOne")
}
// 子系统A方法
func (c *SubSystemA) MethodTwo() {
fmt.Println("SubSystemA - MethodTwo")
}
// 子系统B
type SubSystemB struct {
}
// 初始化子系统B
func NewSubSystemB() *SubSystemB {
return &SubSystemB{}
}
// 子系统B方法
func (c *SubSystemB) MethodThree() {
fmt.Println("SubSystemB - MethodThree")
}
// 子系统B方法
func (c *SubSystemB) MethodFour() {
fmt.Println("SubSystemB - MethodFour")
}
func main() {
fa := NewFacade()
fa.MethodA()
fa.MethodB()
sub := NewSubSystemA()
sub.MethodOne()
sub.MethodTwo()
}
//$ go run main.go
//SubSystemB - MethodThree
//SubSystemA - MethodOne
//SubSystemB - MethodFour
//SubSystemB - MethodFour
//SubSystemA - MethodTwo
//SubSystemA - MethodOne
//SubSystemA - MethodTwo
优点 #
-
外观模式允许定义一个简单的接口,用于隐藏子系统相互依赖的复杂性。外观模式不但降低了程序的整体复杂度,而且有利于将不需要的依赖移动到同一个位置。
-
外观模式的外观类可以将代码解耦,使以后添加功能更容易。
-
允许定义特定于客户需求的方法,而不会强制开发者使用系统提供的可用方法。
-
可以将子系统的复杂性隐藏在单个外观类的后面,从而帮助提高代码的可读性和可用性。
缺点 #
- 提高了系统复杂性
- 可能会增加一些额外的请求
- 外观模式在各个子系统之间创建了依赖关系,各个子系统之间通过调用相关方法为客户端提高服务
- 外观模式需要在外观类中引入客户端的具体API,因此需要进行额外的维护。
示例 #
(1)定义信用卡外观类
// 定义钱包的外观类
type WalletFacade struct {
Account *Account // 账户
Wallet *Wallet // 钱包
VerificationCode *VerificationCode // 验证码
Notification *Notification // 通知
Ledger *Ledger // 分类帐
}
// 创建钱包的外观类
func NewWalletFacade(accountID string, code int) *WalletFacade {
WalletFacacde := &WalletFacade{
Account: NewAccount(accountID),
VerificationCode: NewVerificationCode(code),
Wallet: NewWallet(),
Notification: &Notification{},
Ledger: &Ledger{},
}
return WalletFacacde
}
(2)定义复杂子系统的组成部分
// 账户
type Account struct {
name string
}
// 创建账户
func NewAccount(accountName string) *Account {
return &Account{
name: accountName,
}
}
// 检查账户
func (a *Account) CheckAccount(accountName string) error {
if a.name != accountName {
return fmt.Errorf("%s", "账户名不正确~")
}
fmt.Println("账户验证通过~")
return nil
}
// 添加钱到钱包
func (w *WalletFacade) AddMoneyToWallet(accountID string, securityCode int, amount int) error {
fmt.Println("添加钱到钱包")
//1.检查账户
err := w.Account.CheckAccount(accountID)
if err != nil {
return err
}
//2.检查验证码
err = w.VerificationCode.CheckCode(securityCode)
if err != nil {
return err
}
//3.添加金额
w.Wallet.AddBalance(amount)
//4.发送信用通知
w.Notification.SendWalletCreditNotification()
w.Ledger.MakeEntry(accountID, "credit", amount)
return nil
}
// 从钱包里扣款
func (w *WalletFacade) DeductMoneyFromWallet(accountID string, securityCode int, amount int) error {
fmt.Println("从钱包里扣款")
//1.检查账户
err := w.Account.CheckAccount(accountID)
if err != nil {
return err
}
//2.检查验证码
err = w.VerificationCode.CheckCode(securityCode)
if err != nil {
return err
}
//3.借款金额
err = w.Wallet.DebitBalance(amount)
if err != nil {
return err
}
//4.发送借款通知
w.Notification.SendWalletDebitNotification()
w.Ledger.MakeEntry(accountID, "credit", amount)
return nil
}
// 分类帐
type Ledger struct {
}
// 生成分类帐条目
func (s *Ledger) MakeEntry(accountID, txnType string, amount int) {
fmt.Printf("为账户:%s 生成分类帐条目,账目类型为:%s,金额为:%d\n", accountID, txnType, amount)
return
}
// 通知
type Notification struct {
}
// 发送信用通知
func (n *Notification) SendWalletCreditNotification() {
fmt.Println("发送钱包信用通知...")
}
// 发送借款通知
func (n *Notification) SendWalletDebitNotification() {
fmt.Println("发送钱包借款通知...")
}
// 验证码
type VerificationCode struct {
code int
}
// 创建验证码
func NewVerificationCode(code int) *VerificationCode {
return &VerificationCode{
code: code,
}
}
// 检查验证码
func (s *VerificationCode) CheckCode(incomingCode int) error {
if s.code != incomingCode {
return fmt.Errorf("%s", "验证码不正确")
}
fmt.Println("验证通过~")
return nil
}
// 钱包
type Wallet struct {
balance int
}
// 创建钱包
func NewWallet() *Wallet {
return &Wallet{
balance: 0,
}
}
// 添加金额
func (w *Wallet) AddBalance(amount int) {
w.balance += amount
fmt.Println("添加钱包金额成功~")
return
}
// 借款金额
func (w *Wallet) DebitBalance(amount int) error {
if w.balance < amount {
return fmt.Errorf("%s", "金额无效~")
}
fmt.Println("钱包金额足够~")
w.balance = w.balance - amount
return nil
}
(3)创建客户端
func main() {
//实例化外观模式
WalletFacade := NewWalletFacade("barry", 1688)
fmt.Println()
//添加16元到钱包
err := WalletFacade.AddMoneyToWallet("barry", 1688, 16)
if err != nil {
log.Fatalf("Error: %s\n", err.Error())
}
fmt.Println()
//从钱包取出5元
err = WalletFacade.DeductMoneyFromWallet("barry", 1688, 5)
if err != nil {
log.Fatalf("Error: %s\n", err.Error())
}
}
//添加钱到钱包
//账户验证通过~
//验证通过~
//添加钱包金额成功~
//发送钱包信用通知...
//为账户:barry 生成分类帐条目,账目类型为:credit,金额为:16
//
//从钱包里扣款
//账户验证通过~
//验证通过~
//钱包金额足够~
//发送钱包借款通知...
//为账户:barry 生成分类帐条目,账目类型为:credit,金额为:5
享元模式 #
介绍 #
享元模式摒弃了在每个对象中存储所有数据的方式,通过共享多个对象的相同状态,使开发者可以在有限的内存容量中载入更多对象。享元模式通过共享已经存在的对象,大幅度减少了需要创建的对象数量,避免了大量相似的开销,从而提高了系统资源的利用率。
使用场景:
- 如果程序必须支持大量对象且没有足够的内存容量
- 如果程序需要生成数量巨大的相似对象
- 如果程序有可能耗尽目标设备的所有内存资源
- 如果对象中包含可抽取且能在多个对象之间共享的重复状态
(1)定义享元接口
// 享元接口
type Flyweight interface {
Operation()
}
(2)定义具体享元类
在定义具体享元类时,需要将具体享元类的成员变量拆分为以下两个部分:
- 内部状态:包含不变的、可在多个对象中重复使用的数据的成员变量
- 外部状态:包含每个对象各自不同的情景数据的成员变量
// 创建具体享元类,可以共享以支持大型有效的对象数量
type ConcreteFlyweight struct {
intrinsicState string
}
// 具体享元对象初始化
func (fw ConcreteFlyweight) Init(intrinsicState string) {
fw.intrinsicState = intrinsicState
}
// 具体享元对象的方法
func (fw ConcreteFlyweight) Operation(extrinsicState string) string {
fmt.Println(fw.intrinsicState) //享元模式将对象的内在状态(不变的、可以共享的部分)与外在状态(可变的、特定于上下文的部分)分离。
if extrinsicState != "" {
return extrinsicState //intrinsicState 是内在状态,而 extrinsicState 是外在状态。
} //通过这种分离,可以让多个对象共享相同的内在状态,从而减少内存使用。
return "empty extrinsicState"
}
// 创建一个新的具体享元类
func NewConcreteFlyweight(state string) *ConcreteFlyweight {
return &ConcreteFlyweight{state}
}
(3)定义用于创建和存储具体享元对象的享元工厂类及其方法
// 创建用于创建和存储享元的享元工厂类
type FlyweightFactory struct {
pool map[string]*ConcreteFlyweight
}
// 创建一个新的享元工厂对象
func NewFlyweightFactory() *FlyweightFactory {
return &FlyweightFactory{pool: make(map[string]*ConcreteFlyweight)}
}
// 获取或创建具体享元对象
func (f *FlyweightFactory) GetFlyweight(state string) *ConcreteFlyweight {
flyweight, _ := f.pool[state]
if f.pool[state] == nil {
flyweight = NewConcreteFlyweight(state)
f.pool[state] = flyweight
}
return flyweight
}
(4)创建客户端
func main() { //意思就是我用到这个对象了,我再去创建,而不是刚开始就创建
factory := NewFlyweightFactory()
flyweight1 := factory.GetFlyweight("Barry") //ConcreteFlyweight 类的实例可以被多个客户端共享,而不是为每个客户端创建独立的实例
flyweight2 := factory.GetFlyweight("Shirdon")
fmt.Println(flyweight1.Operation("ok"))
fmt.Println(flyweight2.Operation("good"))
}
//$ go run main.go
//Barry
//ok
//Shirdon
//good
优点 #
- 通过减少对象数量提高应用程序的性能
- 可以减少占用内存资源,因为公共属性在内部属性的对象之间共享
- 缩短实例化时间,降低相关成本
- 一个类的一个对象可以提供很多虚拟实例
缺点 #
- 如果对象中没有可共享的属性,那么享元模式是没用的
- 如果内存资源充足,享元模式是多余的
- 会提高代码复杂度
示例 #
在篮球比赛中,两个球队各派5名球员上场比赛,两个球队的队员身着不同颜色的服装。为了方便,我们假设两个球队各有一种服装类型。
下面是球员类,服装对象被嵌入球员类
// 队员类
type Player struct {
Dress Dress //服装接口
PlayerType string
lat int
long int
}
假设有5名红队球员,5名蓝队球员,那么创建服装对象的方法有两种:
- 10名球员各自创建不同的服装对象,并且将其嵌入玩家类,总共会创建10个服装对象。
- 创建两个服装对象,分别在蓝队和红队队员中共享。
第二种方法使用的就是享元模式,创建的2个服装对象称为享元对象。
享元模式可以从对象(球员对象)中提取出公共部分并创建享元对象(服装对象),这些享元对象(服装对象)随后可以在多个对象(球员对象)中共享,从而极大地减少服装对象的数量,即使创建更多的对象(球员对象),也只需要2个服装对象。
在享元模式中,我们可以将享元对象存储于map容器中。在创建共享享元对象的其他对象时,从map容器中获取享元对象即可。
此类安排的内部状态和外部状态:
- 内部状态:存储内部状态的服装对象可以在多个红队球员对象和蓝队球员对象之间共享。
- 外部状态:球员对象位置是外部状态,因为它在每个球员对象中都是不同的。
(1)定义享元工厂类DressFactory(服装工厂类)及其方法。
const (
//蓝队服装类型
BlueTeamDressType = "Blue Dress"
//红队服装类型
RedTeamDressType = "Red Dress"
)
var (
DressFactorySingleInstance = &DressFactory{
DressMap: make(map[string]Dress),
}
)
// 享元服装工厂
type DressFactory struct {
DressMap map[string]Dress
}
// 获取服装类型
func (d *DressFactory) GetDressByType(DressType string) (Dress, error) {
if d.DressMap[DressType] != nil {
return d.DressMap[DressType], nil
}
if DressType == BlueTeamDressType {
d.DressMap[DressType] = newBlueTeamDress()
return d.DressMap[DressType], nil
}
if DressType == RedTeamDressType {
d.DressMap[DressType] = newRedTeamDress()
return d.DressMap[DressType], nil
}
return nil, fmt.Errorf("%s", "Wrong Dress type")
}
func newBlueTeamDress() *BlueTeamDress {
return &BlueTeamDress{color: "blue"}
}
func newRedTeamDress() *RedTeamDress {
return &RedTeamDress{color: "red"}
}
(2)定义享元接口Dress(服装接口)
// 服装接口
type Dress interface {
GetColor() string
}
(3)定义具体享元类
// 蓝队服装
type BlueTeamDress struct {
color string
}
func (t *BlueTeamDress) GetColor() string {
return t.color
}
// 创建红队服装
type RedTeamDress struct {
color string
}
func (c *RedTeamDress) GetColor() string {
return c.color
}
(4)定义球员类,及其方法
// 队员类
type Player struct {
Dress Dress
PlayerType string
lat int
long int
}
// 创建队员位置
func (p *Player) NewLocation(lat, long int) {
p.lat = lat
p.long = long
}
(5)创建游戏类NewGame及其方法
// 创建游戏
type NewGame struct {
}
// 创建蓝队队员
func (ng *NewGame) AddBlueTeam(DressType string) *Player {
return NewPlayer("terrorist", DressType)
}
// 创建红队队员
func (ng *NewGame) AddRedTeam(DressType string) *Player {
return NewPlayer("counterBlueTeam", DressType)
}
// 创建一个队员
func NewPlayer(PlayerType, DressType string) *Player {
Dress, _ := GetDressFactorySingleInstance().GetDressByType(DressType)
return &Player{
PlayerType: PlayerType,
Dress: Dress,
}
}
(6)客户端
func main() {
game := NewGame{}
//创建红队
game.AddBlueTeam(BlueTeamDressType)
game.AddBlueTeam(BlueTeamDressType)
game.AddBlueTeam(BlueTeamDressType)
game.AddBlueTeam(BlueTeamDressType)
//创建蓝队
game.AddRedTeam(RedTeamDressType)
game.AddRedTeam(RedTeamDressType)
game.AddRedTeam(RedTeamDressType)
DressFactoryInstance := GetDressFactorySingleInstance()
for DressType, Dress := range DressFactoryInstance.DressMap {
fmt.Printf("服装类型: %s\n服装颜色: %s\n", DressType, Dress.GetColor())
}
}
func GetDressFactorySingleInstance() *DressFactory {
return DressFactorySingleInstance
}
//服装类型: Blue Dress
//服装颜色: blue
//服装类型: Red Dress
//服装颜色: red
代理模式 #
介绍 #
代理模式是指出于某些原因,需要给某个对象提供一个代理对象的设计模式,用于控制对该对象的访问。这时,访问对象不适合或不能直接引用目标对象,可将代理对象作为访问对象和目标对象之间的中介。
好处:如果需要在类的主要业务逻辑之前或之后执行一些操作,那么开发者不需要修改类就能完成这项工作。
代理模式建议创建一个与原服务对象接口相同的代理类,然后更新应用,从而将代理对象传递给所有原始对象客户端。代理对象在接收到客户端请求后,会创建实际的服务对象,并将所有的工作委托给它。
使用场景:
- 延迟初始化(虚拟代理):如果开发者有一个偶尔使用的重量级服务对象,一直使用该对象保持运行会消耗系统资源,则可以使用代理模式。
- 访问控制(保护代理):如果开发者希望特定客户端使用服务对象,这里的服务对象是操作系统中非常重要的部分,而客户端是各种已启动的程序,则可以使用代理模式。
(1)定义服务接口
// 服务接口
type ServiceInterface interface {
Execute(access string)
}
(2)定义服务类
// 服务实现了用于执行任务的 ServiceInterface 接口
type Service struct {
}
// 服务对象的方法
func (t *Service) Execute(access string) {
fmt.Println("Proxy Service: " + access)
}
(3)定义代理类,其中必须包含一个指向服务对象的引用的成员变量。在通常情况下,代理对象负责创建服务对象并对其整个生命周期进行管理。
// 代理对象
type Proxy struct {
realService *Service
}
// 创建代理对象
func NewProxy() *Proxy {
return &Proxy{realService: &Service{}}
}
// 拦截 Execute 命令并将其重新路由到服务命令
func (t *Proxy) Execute(access string) {
if access == "yes" {
t.realService.Execute(access)
}
}
根据需求实现代理方法。在通常情况下,代理对象在完成一些任务后,应该将工作委派给服务对象,可以新建一个方法,用于判断客户端获取的是代理对象还是实际服务对象。开发者可以在代理类中创建一个简单的方法,用于实现代理功能,也可以创建一个完整的工厂方法,用于实现代理功能。
(4)创建客户端
func main() {
proxy := NewProxy()
proxy.Execute("yes")
}
//$ go run main.go
//Proxy Service: yes
优点 #
- 代理模式更安全,且易于实施。
- 代理模式可以避免巨型对象和内存密集型对象的重复,从而提高应用程序性能。
- 远程代理可以通过客户端机器中安装本地代码代理(存根),然后在远程代码的帮助下访问服务器,从而确保安全性。
缺点 #
- 由于代理模式引入了另一层抽象,因此,如果一部分客户端直接访问真实的服务对象,而另一部分客户端访问代理对象,则可能导致不同步问题。
示例 #
Apache的web服务器可以充当应用程序服务器的代理对象。
该服务器具备如下基本功能:
- 提供对应用程序服务器的受控访问权限
- 可限制速度
- 可缓存请求
(1)定义主体服务器接口
//定义主体服务器接口
type Server interface {
HandleRequest(string, string) (int, string)
}
(2)定义代理类Apache及其方法
// Apache类
type Apache struct {
Application *Application
maxAllowedRequest int
rateLimiter map[string]int
}
// 创建Apache服务器
func NewApacheServer() *Apache {
return &Apache{
Application: &Application{},
maxAllowedRequest: 2,
rateLimiter: make(map[string]int),
}
}
// 处理请求
func (n *Apache) HandleRequest(url, method string) (int, string) {
allowed := n.CheckRateLimiting(url)
if !allowed {
return 403, "Not Allowed"
}
return n.Application.HandleRequest(url, method)
}
// 检查频率限制
func (n *Apache) CheckRateLimiting(url string) bool {
if n.rateLimiter[url] == 0 {
n.rateLimiter[url] = 1
}
if n.rateLimiter[url] > n.maxAllowedRequest {
return false
}
n.rateLimiter[url] = n.rateLimiter[url] + 1
return true
}
(3)定义真实主体类Application及其方法
// 定义真实主体类
type Application struct {
}
// 处理请求
func (a *Application) HandleRequest(url, method string) (int, string) {
if url == "/user/status" && method == "GET" {
return 200, "Ok"
}
if url == "/user/login" && method == "POST" {
return 201, "User Login"
}
return 404, "Not Ok"
}
(4)客户端
func main() {
//初始化Apache服务器
ApacheServer := NewApacheServer()
userStatusURL := "/user/status"
userLoginURL := "/user/login"
//发送一个GET请求
httpCode, body := ApacheServer.HandleRequest(userStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", userStatusURL, httpCode, body)
//发送一个POST请求
httpCode, body = ApacheServer.HandleRequest(userStatusURL, "POST")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", userStatusURL, httpCode, body)
//发送一个GET请求
httpCode, body = ApacheServer.HandleRequest(userLoginURL, "POST")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", userStatusURL, httpCode, body)
//发送一个POST请求
httpCode, body = ApacheServer.HandleRequest(userLoginURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", userStatusURL, httpCode, body)
}
//
//Url: /user/status
//HttpCode: 200
//Body: Ok
//
//Url: /user/status
//HttpCode: 404
//Body: Not Ok
//
//Url: /user/status
//HttpCode: 201
//Body: User Login
//
//Url: /user/status
//HttpCode: 404
//Body: Not Ok