阅读 333

图解设计模式

本文不限于任何面向对象的编程语言
复制代码

前言


常常听别人说设计模式不太容易理解,以及学习设计模式到底能帮我们解决什么问题,今天我们就用几张图来看看:

  • 设计模式到底是什么?
  • 为什么我们需要学习设计模式?

我也写过烂代码


是的,没什么,我也写过烂代码,刚毕业时业务逻辑也会一个函数干到底,只知道能实现功能就可以了。

package demo
 
func YourFunc() {
	// 所有的逻辑代码一股脑写完......
}
复制代码

知道了拆分函数


自然而然知道了需要合理拆分函数。

然后把各个函数组织起来。

面临新的困境


某一天产品的需求需要支持新的场景,发现某一处的代码逻辑有变动需要支持新的场景,怎么办?

  1. 整个代码拷贝一份?不会有人这么干吧?(其实我还真见过,你们呢😏)
  2. 把绿色变动的代码块,复制成一个新的函数,修改为新场景使用的函数?
  3. 把变动的代码再提为两个新函数,一个绿色为老代码,一个蓝色为新场景代码?

上面这种解决问题的方式就是面向过程的编程思想。

我们都在变优秀


随着我们不断的学习,学会使用了面向对象的特性。

以往函数式编程:

package demo
 
// 函数式编程
// 把一个个你以为可以独立的逻辑封住到一个函数里
func YourFunc() {
	// ......
}
复制代码

面向对象编程:

package demo
 
// 面向对象编程
// 把不同的逻辑独立成一个对象
type DemoStruct struct{}
 
func (d *DemoStruct) YourFunc() {
	// ......
}
复制代码

所以,我们如何用面向对象的思想组织上面的代码呢?

答案:继承。

学会了使用继承


特别备注:Go里面用合成复用
复制代码

定义一个父类,并把差异业务代码抽象为一个抽象函数,其他代码逻辑都实现在父类。

不同的场景定义为不同的子类,子类继承父类,并实现抽象方法(也就是写差异代码)。

灰色:父类
绿色:场景一子类
蓝色:场景二子类
复制代码

是不是很优雅的解决上面的场景的问题。

什么是设计模式?


就是优雅解决上面场景问题时,利用面向对象特性的经验总结,就是设计模式。然而在历史的长河中,已经为我们总结了20+的常用设计模式,我们只需要学习和加以灵活运用即可。比如:

这!就是模板模式

还记得上面使用继承的过程吗?其实我们只需要做一件事情,就是经典的模板模式了,是什么?

答案:保证该场景下父类中封装的方法调用过程是稳定不变的,只是其中的方法可能变化。

灰色:父类
绿色:场景一子类
蓝色:场景二子类
黄色:场景三子类
复制代码

这!就是策略模式

我们把上面代码做些改动:

  1. 不使用继承。
  2. 定义一个接口interface类型。
  3. 变更原抽象方法为调用一个接口interface类型的函数。
package demo
 
// DemoInterface 接口
type DemoInterface interface {
    DoSomething(ctx *Context) error 
}
 
var CurrentStrategyInstance DemoInterface
 
func Demo() {
	//.....逻辑略......
	CurrentStrategyInstance.DoSomething(c)
	//.....逻辑略......
}
复制代码
灰色:主业务类
复制代码

  1. 不同的场景定义为一个具体的类,且实现上面的interface。
灰色:主业务类
绿色:场景一DemoInterface的具体实现类
复制代码

灰色:主业务类
绿色:场景一DemoInterface的具体实现类
蓝色:场景二DemoInterface的具体实现类
复制代码

灰色:主业务类
绿色:场景一DemoInterface的具体实现类
蓝色:场景二DemoInterface的具体实现类
黄色:场景三DemoInterface的具体实现类
复制代码

  1. 最后我们判断不同的场景初始化不同的具体类,再调用即可。

这!就是简单工厂模式 + 策略模式

接着我们把判断不同的场景初始化不同的具体类单独封装起来,这就是简单工厂模式 + 策略模式。

package demo
 
type DemoFactory struct {
}
 
// Get 获取实例
func (f *DemoFactory) Get(instanceType string) DemoInterface {
	switch instanceType {
	case "DemoA":
		return &DemoA{}
	case "DemoB":
		return &DemoB{}
	case "DemoC":
		return &DemoC{}
 
	default:
		panic("不支持的类型")
	}
}
 
// DemoInterface 接口
type DemoInterface interface {
    DoSomething(ctx *Context) error 
}
 
var CurrentStrategyInstance DemoInterface
 
func Demo() {
	//.....逻辑略......
  CurrentStrategyInstance = (DemoFactory{}).Get("DemoA")
	CurrentStrategyInstance.DoSomething(c)
	//.....逻辑略......
}
复制代码
灰色(大):主业务类
灰色(小):简单工厂类
绿色:场景一DemoInterface的具体实现类
蓝色:场景二DemoInterface的具体实现类
黄色:场景三DemoInterface的具体实现类
复制代码

这!就是状态模式

假设判断上面使用何种策略不是依赖外部,而是依赖内部状态,则我们调整下代码,则就变成了状态模式。

package demo

var currentStateInstance DemoInterface

func init() {
    // 定时器更新状态
    go func() {
        for {
            select {
            case t := <-time.NewTicker(1 * time.Second).C:
                // 模拟变成状态 StateA
                currentStateInstance = setState("StateA")
            }
        }
    }()
}
 
// Get 获取实例
func setState(State string) DemoInterface {
  // 变更状态
	switch State {
	case "StateA":
		return &StateA{}
	case "StateB":
		return &StateB{}
	case "StateC":
		return &StateC{}
 
	default:
		panic("不支持的状态")
	}
}
 
// DemoInterface 接口
type DemoInterface interface {
    DoSomething(ctx *Context) error 
}

// type StateA StateB StateC 略
 
// 模拟
func Demo() {
	//.....逻辑略......
	CurrentStateInstance.DoSomething(c)
	//.....逻辑略......
}
复制代码
灰色(大):主业务类
灰色(小):修改内部状态的函数
绿色:场景一DemoInterface的具体实现类
蓝色:场景二DemoInterface的具体实现类
黄色:场景三DemoInterface的具体实现类
复制代码

结语


举了这么多🌰,所以关于:

  • 设计模式到底是什么?
  • 为什么我们需要学习设计模式?

你有答案了吗?

文章分类
后端
文章标签