设计模式
这是Easy搞定Golang设计模式的学习记录
面向对象设计原则
单一职责原则
类的职责单一,对外只提供一种功能,而引起类的变化的原因通常只有一个。这是为了避免别人看代码时产生歧义,提高代码的逻辑性
设计一个类,对外提供的功能应该单一,接口单一,仅有这个接口会影响这个类。一个类的一个接口应具备这个类的功能含义,职责单一并且不复杂
开闭原则
对扩展开放,对修改关闭
如果我们想为某个类增添新的功能,最好的方法是基于接口或者组合的方式对其进行扩展,而不是在原有的类代码上进行修改。比如一个类已经有了99个方法,那么增添第100个方法时就很有可能导致前面99个方法无法正常工作(毕竟谁知道那99个方法用了什么奇技淫巧)
所以开闭原则的核心思想就是:不是通过修改代码,而是通过增添代码来给系统添加功能
依赖倒转原则(重要)
感觉依赖倒转原则的核心就是面向接口编程
首先需要做好中间抽象层的设计,然后依照这抽象层一次将实现层的每一个模块进行实现。这一步提现了实现层向上依赖抽象层
另一方面,抽象层向业务逻辑层暴露出功能的接口,业务逻辑层只需要根据暴露出来的接口实现对也的业务逻辑即可,无需关系功能具体是如何实现
合成复用原则
如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用组合,就大大降低了这种依赖关系。对于组合和继承,优先使用组合。(Go天然实现组合😂)
比如父类有100个方法,而子类只想使用其中的某一个方法,那么子类采用组合的方式就能屏蔽掉父类的其他99个方法
迪米特法则
一个对象应当对其他对象尽可能了解得少,从而降低各个对象之间的耦合,提高系统的可维护性。
创建型模式
简单工厂模式
业务逻辑层 —> 工厂模块 —> 基础类模块
反例:业务逻辑层 —> 基础类模块
type Fruit struct {
}
func (f *Fruit) Name() {
}
func NewFruit(name string) *Fruit {
res := &Fruit{}
switch {
case name == "apple":
// do something
case name == "pear":
// do something
default:
// do something
}
return res
}
func main() {
apple := NewFruit("apple")
apple.Name()
pear := NewFruit("pear")
pear.Name()
}
简单工厂模式实现代码:
type Fruit interface {
Show()
}
type Apple struct {
}
func (a *Apple) Show() {
}
type Pear struct{
}
func (p *Pear) Show() {
}
type Banana struct{
}
func (b *Banana) Show() {
}
type Factory struct{}
func (f *Factory) CreateFruit(name string) (Fruit, bool) {
var fruit Fruit
ok := false
switch {
case name == "apple":
fruit, ok = new(Apple), true
case name == "pear":
fruit, ok = new(Pear), true
case name == "banana":
fruit, ok = new(Banana), true
}
return fruit, ok
}
优点:
- 实现了对象创建和使用的分离
缺点: - 违法“开闭原则”
- 工厂类职责过重,一旦不能工作,系统深受影响
- 复杂度和理解度随着类的个数增加
工厂模式
工厂模式就是“简单工厂模式 + 开闭原则”
在Go里面,实现工厂模式比较常见的写法就是Newxxx()
func NewFactory() *Factory {
return &Factory{}
}
单例模式
保证一个类永远只能有一个对象,且该对象的功能依然能被其他模块使用
饿汉式:
type single struct{}
var instance = new(single)
func (s *single) Show() {
// do something
}
func GetInstance() *single {
return instance
}
懒汉式:延迟初始化,直到调用GetInstance方法时才进行初始化。需要重点关心的是如何保证并发安全
- 锁
- 原子操作
- once
type single struct{}
var (
instance *single
mtx sync.RWMutex
lock uint32
once sync.Once
)
func GetInstance() *single {
// 保证并发安全:锁、原子操作、once
// 原子操作
//if atomic.LoadUint32(&lock) == 1 {
// return instance //} //mtx.Lock() //defer mtx.Unlock() //instance = new(single) //return instance // once
once.Do(func() {
instance = new(single)
})
return instance
}
结构型模式
代理模式
Subject:具体主题和代理主题的公共接口
RealSubject:具体主题
Proxy:代理主题,包含了对具体主题角色的引用,通常可以在代理主题中封装具体主题需要执行的一些流程,提高代码复用性
type D interface {
Dance()
}
type KunDance struct {
}
func (k *KunDance) Dance() {
fmt.Println("KunDance")
}
type KunKunDance struct{}
func (k *KunKunDance) Dance() {}
type SingDanceRap struct {
d D
}
func (s *SingDanceRap) Dance() {
s.Sing()
// 动态调用某一个Dance方法
s.d.Dance()
s.Rap()
}
func (s *SingDanceRap) Sing() {}
func (s *SingDanceRap) Rap() {}
func NewSingDanceRap(d D) D {
return &SingDanceRap{
d: d,
}
}
感觉核心点在于,实现一个公共的接口,封装一些重复性的代码,提高复用🤔
装饰器模式
装饰器模式和代理模式很像,都是在原有接口上进行功能的扩展。典型的模板就是结构体 组合 待扩展的接口
type Phone interface {
Show()
}
type Hua struct {
}
func (h *Hua) Show() {
}
type PhonePlus struct {
Phone
}
func (pp *PhonePlus) Show() {
}
func (pp *PhonePlus) AnotherMethod() {
}
适配器模式
适配器模式可以实现一个类或接口能够在一个与之毫不相干的接口环境运行、作用
type V5 interface{
Use5() }
type V220 interface{
Use220()
}
type Adapter struct {
v220 V220
}
func (a *Adapter) Use5() {
a.v220.Use220()
}
func NewAdapter(v220 V220) *Adapter {
return &Adapter{v220: v220}
}
外观模式
外观模式本质上就是添加一个中间层,这个中间层向上层提供了底层API的封装,降低了业务层和底层的耦合
type A struct {}
func (a *A)work() {
}
type T struct {
a *A
}
行为型模式
模板方法模式
模板方法模式可以用于统一接口方法的操作步骤,忽视每个实例对于方法的具体实现
(Go确实不适合按照严格的设计模式类图进行编码)
type Work interface {
Step1()
Step2()
Step3()
}
type WorkFlow interface {
DoSomething()
}
type BasicWorkFlow struct {
Work
}
func (wf *BasicWorkFlow) DoSomething() {
wf.Step1()
wf.Step2()
wf.Step3()
}
命令模式
命令模式是在一个功能类上提供特殊的命令类,用于区分功能类中的特定功能。向业务方屏蔽了功能类的具体细节。在命令类的基础上组合出一个中间类,中间类向业务方提供统一的接口,进一步屏蔽了不同命令之间的差异
type Doctor struct {}
func (d *Doctor) treatEye() {
}
type Cmd interface {
Treat()
}
type Nurse struct {
cmds []Cmd
}
func (n *Nurse) Notify() {
if n.cmds == nil {
return
}
for i := range n.cmds {
n.cmds[i].Treat()
}
}
func (d *Doctor) treatNose() {}
type cmdEye struct {
d *Doctor
}
func (c *cmdEye) Treat() {
c.d.treatEye()
}
type cmdNose struct {
d *Doctor
}
策略模式
为环境类配置一个接口,通过这个接口用户可以自由切换功能相同但实现不同的算法
type Strategy interface {
UseWeapon()
}
type AK struct{}
func (a *AK) UseWeapon() {
fmt.Println("ak")
}
type Knife struct{}
func (k *Knife) UseWeapon() { fmt.Println("k") }
type Person struct {
s Strategy
}
func (p *Person) SetStrategy(s Strategy) {
p.s = s
}
func (p *Person) Fight() {
p.s.UseWeapon()
}
观察者模式
如果系统需要创建一个触发链,A对象的行为影响B对象,B对象的行为影响C对象…,就可以使用观察者模式创建一种链式触发机制
type Listener interface {
OnTeacherComing()
}
type StuZ3 struct {
badThing string
}
func (s *StuZ3) OnTeacherComing() {
fmt.Println("z3 stop", s.badThing)
}
type StuZ4 struct {
badThing string
}
func (s *StuZ4) OnTeacherComing() {
fmt.Println("z3 stop", s.badThing)
}
type StuW5 struct {
badThing string
}
func (s *StuW5) OnTeacherComing() {
fmt.Println("z3 stop", s.badThing)
}
type Notifier interface {
Add(l Listener)
Del(l Listener)
Notify()
}
type Monitor struct {
listeners []Listener
}
func (m *Monitor) Add(l Listener) {
if m.listeners != nil {
m.listeners = append(m.listeners, l)
}
}
func (m *Monitor) Del(l Listener) {
//TODO implement me
panic("implement me")
}
func (m *Monitor) Notify() {
for i := range m.listeners {
m.listeners[i].OnTeacherComing()
}
}