Contents

设计模式(Design Pattern)

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式概述

“设计模式"总的来说有设计原则6个.

  • 单一职责原则(Single Responsibility Principle, SRP) : 每个模块或类都应该对软件提供的功能的一部分负责,而这个责任应该完全由类来封装。它的所有服务都应严格遵守这一职责。
  • 开闭原则(Open Close Principle, OCP) : 软件中的对象(类、模块、函数等)对扩展是开放的,对修改是封闭的。
  • 里氏替换原则(Liskov Substitution Principle, LSP) : 所有使用基类的地方必须能透明地使用其子类的对象
  • 依赖倒转原则(Dependence Inversion Principle, DIP) : 是指一种特定的解耦(传统的依赖关系建立在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。
  • 接口隔离原则(Interface Segregation Principle, ISP) : 客户端不应该依赖它不需要的接口。
  • 迪米特法则(Law of Demeter, LoD), 最少知识原则(Principle of Least Knowledge) : 1. 每个对象应该对其他对象尽可能最少的知道 2. 每个对象应该仅和其朋友通信;不和陌生人通信 3. 仅仅和直接朋友通信

设计模式按照类别分为三大类。创建型模式结构型模式行为型模式

  • 创建型模式
    • 🐎 单例模式:让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
    • 🦊 工厂模式:让你能创建一系列相关的对象, 而无需指定其具体类。
    • 🐑 建造者模式:使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
    • 🐫 原型模式:让你能够复制已有对象, 而又无需使代码依赖它们所属的类。
  • 结构型模式
    • 🐃 代理模式:让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。(适合对基础库的再封装)
    • 🦁桥接模式:可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
    • 🐻 装饰器模式:允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。(也适合对基础库的二次在封装,加自己的逻辑)
    • 🐏 适配器模式:让接口不兼容的对象能够相互合作。(不兼容接口的适配)
    • 🐨 外观模式:能为程序库、 框架或其他复杂类提供一个简单的接口。
    • 🦌享元模式:摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。(大量重复对象创建)
    • 🐒组合模式:你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。(将属性类似的对象封装进行相同的操作)
  • 行为型模式
    • 🐄 观察者模式:允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。(类似消息队列的 订阅机制)
    • 🐪 模板方法模式:在超类中定义一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。(按照模板运行某个固定业务逻辑,如发通知)
    • 🦉 命令模式:它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。
    • 🦃策略模式:能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。
    • 🐬责任链模式:允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
    • 🐌访问者模式:将算法与其所作用的对象隔离开来。
    • 🐛迭代器模式:让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。(适合明确的栈、队列等数据结构的封装)
    • 🐙 中介者模式:能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。(适合资源调度类场景)
    • 🐜 状态模式:让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。(适合状态机调度)
    • 🐝 备忘录模式:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。(类似游戏存档)


创建型模式

单例模式 (Singleton)

🐎 :让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点


  • 定义以及使用场景
  1. 确保某一个类只有一个实例,而且向整个系统提供这个实例
  2. 确保某个类有且仅有一个对象的场景,避免产生多个对象消耗过多的资源;或者某种类型的对象应该有且只有一个。(如 Logger 实例、Config 实例等)
  • 实现单例模式的几个关键点
  1. 构造函数不对外开放,一般为private
  2. 通过一个静态方法或者枚举返回单例类对象
  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下
  4. 确保单例类对象在反序列化时不会重新构建对象
  • 饥汉模式

直接创建好对象,这样不需要判断为空,同时也是线程安全。唯一的缺点是在导入包的同时会创建该对象,并持续占有在内存中。

1
2
3
4
5
var instance Tool

func GetInstance() *Tool {
    return &instance
}
  • 懒汉模式 (Lazy Loading)

只有需要时才会初始化,在一定程度上节约了资源。如果不加锁的话非线程安全,即在多线程下可能会创建多次对象。懒汉方式是开源项目中使用最多的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// private
var instance *singleton
var mu sync.Mutex

// public
func GetInstance() *singleton {
	mu.Lock()
	defer mu.Unlock()

	if instance == nil {
		instance = &singleton{}
	}
	return instance
}
  • DCL(双重检查)模式 (推荐)

DCL的优点就是资源利用率高,只有第一次执行getInstance才会初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var lock sync.Mutex
var instance Tool

/**
* 第一次判断不加锁,第二次加锁保证线程安全,一旦对象建立后,获取对象就不用加锁了
*/
func GetInstance() *Tool {
    if instance == nil {
        lock.Lock()

        if instance == nil {
            instance = new(Tool)
        }

        lock.Unlock()
    }

    return instance
}

或者在golang中还可以使用 sync.Once 保证单例。

1
2
3
4
5
6
7
8
var once sync.Once

func GetInstance() *Tool {
    once.Do(func() {
        instance = new(Tool)
	})
    return instance
}

工厂模式 (Factory)

🦊 :让你能创建一系列相关的对象, 而无需指定其具体类


  • 定义以及使用场景

创建一个用户创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。

使用场景

  1. 工厂方法模式通过依赖抽象来达到解耦的效果,并且将实例化的任务交给子类去完成,有非常好的扩展性
  2. 在任何需要生成复杂对象的地方,都可以使用工厂方法模式。复杂对象适合使用工厂模式,用new就可以完成创建的对象无需使用工厂方法
  3. 工厂方法模式的应用非常广泛,然而缺点也很明显,就是每次我们为工厂方法添加新的产品时,都需要编写一个新的产品类,所以要根据实际情况来权衡是否要用工厂方法模式

类似我要造汽车,将造汽车的通用的几个方法定义好,就可以创建一个接口。任何实现了这套造汽车标准的厂商都可以被初始化。并造出一辆汽车。

  • 举例实现工厂方法模式

假设我们在做一款小型翻译软件,软件可以将德语、英语、日语都翻译成目标中文,并显示在前端。

  • 简单工厂模式

我们会有三个具体的语言翻译结构体,或许以后还有更多,但现在分别是GermanTranslater、EnglishTranslater、JapaneseTranslater,他们都共同实现了一个接口Translator。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//翻译接口
type Translator interface {
	Translate(string) string
}

//德语翻译类
type GermanTranslator struct{}
func (*GermanTranslator) Translate(words string) string {
	return "德语"
}

//英语翻译类
type EnglishTranslator struct{}
func (*EnglishTranslator) Translate(words string) string {
	return "英语"
}

接下来在程序入口获取用户输入的文本,并将其翻译

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
		time.Sleep(3 * time.Second)
	}()

	var lan int
	fmt.Printf("%s ,%s", "以下是可翻译的语言种类,请输入代表数字", "1:德语、2:英语")
	_, _ = fmt.Scanln(&