设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。站在巨人的肩膀上才能看得更远,但对于小程序的编写其实不是太必要强行套用各种设计模式,该怎么办怎么办就好。
创建型模式
这种设计模式主要是对对象创建逻辑的封装,而不是直接在代码中显式的采用构造函数或者new
类型关键字去创建对象。这样提高了程序在对于某个需要的时候创建对象的可扩展性有提高。
单例模式
- 适用场景
- 需要频繁访问的数据库或者文件对象,连接或者重新读取很麻烦
- 该对象保有当前的状态信息,不能够被重置
- 创建对象的时候要消耗大量的资源并且频繁使用
type Config struct {
Name string
Email string
}
var cfg *Config
func NewConfig(name string, email string) *Config {
// Check for the existence of the object.
if cfg == nil {
// Guaranteed to be generated only once.
once.Do(func() {
cfg = &Config{
Name: name,
Email: email,
}
return cfg
})
}
return cfg
}
工厂模式
通过定义一个接口,用不同的子类来实现这个接口,在创建的过程中不会暴露创建逻辑,可以很方便的在不同的情况下创建不同的对象实现接口来使用。
简单工厂
简单工厂的实现非常简单就是通过switch
来根据不同的参数来返回不同的对象而已。
type Student interface {
Say()
}
type GoodStudent struct{}
func (g *GoodStudent) Say() {
fmt.Println("Hello!")
}
type BadStudent structs{}
func (b *BadStudent) Say() {
fmt.Println("Fuck!")
}
// Factory functions are used to generate different objects that implement this
// kind of interface.
func Create(method string) Student {
switch isGood {
case "good":
return &GoodStudent{}
case "bad":
return &BadStudent{}
}
}
弊端:增加一个新的类型时,除了要增加新的类和方法之外,还需要修改工厂函数,在工厂函数中添加 case,这一点违背了开放-封闭原则。
工厂方法
工厂方法和上面的简单工厂的区别是,工厂方法进一步对工厂提供了接口,可以给对应的产品设置对应的工厂。
type Factory interface {
CreateProduct()
}
// Objects are generated in different factories.
type FactoryA struct{}
type FactoryB struct{}
抽象工厂
抽象工厂针对的是多个对象等级,有不同种类的对象可以由抽象工厂来生成实现。
type School interface {
// Notice that the return value here is the interface to the product.
CreateGoodStudent() Student
CreateBadStudent() Student
}
type Student interface {
Say()
}
type GoodStudent struct{}
func (g *GoodStudent) Say() {
fmt.Println("Hello!")
}
type BadStudent struct{}
func (b *BadStudent) Say() {
fmt.Println("Fuck!")
}
// Layer calls to methods make different kinds of distinctions.
type SchoolA struct{}
func (s *SchoolA) CreateGoodStudent() Student {
return &GoodStudent{}
}
func (s *SchoolA) CreateBadStudent() Student {
return &BadStudent{}
}
结构型模式
结构型设计模式主要是用于管理类之间的组合和继承,并且结合接口来实现新的生成对象的功能的管理。
装饰器模式
动态的包装一个对象,增加这个对象的功能,这样做的使用组合代替继承的方法。用装饰的方法在运行时添加或者删除职责比静态的书写继承更加的简便而且易用。
type Car interface {
Move()
}
// Decorators to wrap the original functionality so that it does not change
// itself and add new features.
type NormalCar struct{}
func (n *NormalCar) Move() {
fmt.Println("Moving...")
}
type FastCar struct {
// The decorator has a decorated object.
normalCar NormalCar
}
func (f *FastCar) Move() {
normalCar.Move()
fmt.Println("Fast!")
}
代理模式
代理模式是让一个对象对被代理对象提供代理,就是通过这个代理对象来跟本来的对象进行一些交互而不是直接通过和被代理对象交互,这样又提供了一层访问控制。
网络连接的访问
较大资源文件的访问
type ProxyFunc interface {
Say()
}
type Person struct {
Name string
}
func (p *Person) Say() {
fmt.Println("Hello!")
}
// The agent is implemented by embedding pointers to the proxy object.
type Proxier struct {
person *Person
}
func (p *Proxier) Say() {
if p.person == nil {
// establish a proxy relationship
p.person = &Person{}
}
// called through this proxy
p.person.Say()
}
通常使用的过程中是通过之直接的主动赋值而不是等待代理者执行函数的时候才绑定的被代理者。
person := &Person{
Name: "Wong",
}
proxier := &Proxier{
person: person,
}
适配器模式
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。——Gang of Four
,这就是适配器的含义的描述了。
- 适配器包含老接口,通常要实现老接口的实例来赋值
- 用适配器调用老接口的函数包装
- 将老接口函数转化为新接口的函数名
type Old interface {
Talk()
}
type New interface {
Say()
}
type Adapter struct {
old Old
}
// The method that calls the old interface in the implementation of the new
// interface method.
func (a *Adapter) Say() {
a.old.Talk()
}
行为型模式
行为型模式主要面向的是对象之间的通信和协同关系,通常一个任务需要多个对象的多个方法来完成实现的,这时候就需要通过行为模式来对其进行规划。
策略模式
定义一系列算法实现同一个接口,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,这种变化符合开闭原则,可以不修改主要程序的情况下添加功能。
首先通过策略接口去定义一系列的算法,给执行者的策略接口赋值来让执行者同样的调用调用不同的算法,但由于是通过接口实现的所以一个算法一个类,类的数量就会很多。
// common interface
type Strategy interface {
Do(float64, float64) float64
}
type Add struct{}
func (a *Add) Do(x, y float64) float64 {
return x + y
}
type Sub struct{}
func (s *Sub) Do(a, b float64) float64 {
return a - b
}
type Operator struct {
strategy Strategy
}
// A common operator combination operation.
func (o *Operator) SetStrategy(strategy Strategy) {
o.strategy = strategy
}
func (o *Operator) Calculate(a, b float64) float64 {
// Different methods are called through a bound interface.
return o.strategy.Do(a, b)
}
有一个技巧那就是如果结构体嵌套了匿名接口,则该结构体初始化的时候可以使用实现了该接口的对象去初始化,即使对应的属性不一样
观察者模式
当观察者对象所依赖的或者同步的被观察者发生改变的时候进行通知,同步做出改变。
- 被观察者通知观察者进行改变是核心
type Database interface {
Update()
}
type A struct{}
func (a *A) Update() {
fmt.Println("Data A Update...")
}
type B struct{}
func (b *B) Update() {
fmt.Println("Data B Update...")
}
type User struct {
Databases []Database
}
func (u *User) Notify() {
// Notify the database of the change information when the observed person changes.
for _, db := range u.Databases {
db.Update()
}
}
命令模式
命令模式可以将发送者和接收者完全解耦,使他们之间没有直接的引用关系。发送者只需要知道如何发送请求,接收者只需要知道如何完成请求。
主要分为以下四个角色:
Receiver
:接收者做出请求相应的操作
Invoker
:通过命令对象来发送请求
Command
:抽象的接口包含执行请求的Execute()
这个方法调用了接收者的操作
ConcreteCommand
:具体的命令对象实现了Command
接口
type Car struct{}
func (c *Car) Start() {
fmt.Println("Start to moving")
}
// The interface of the command is defined.
type Command interface {
Execute()
}
type StartCommand struct {
car Car
}
func (s *StartCommand) Execute() {
// You don't need to know that a command needs to execute a command to do.
s.car.Start()
}
模板方法模式
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。Golang
中子类如果不重写父类的方法则会直接使用父类的方法,模板方法模式其实就是抽象类的子类的重写。
type Work interface {
InitTask()
StartWork()
EndWork()
Work()
}
type Worker struct{}
func (w *Worker) InitTask() {
fmt.Println("Init...")
}
func (w *Worker) StartWork() {
fmt.Println("Start...")
}
func (w *Worker) EndWork() {
fmt.Println("End...")
}
func (w *Worker) Work() {
// This function is critical when it contains the process of the operation,
// often defined by the parent class, and the steps in it are overriding
// with sub-classes.
w.InitTask()
w.StartWork()
w.EndWork()
}