设计模式介绍
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
go代码封装
封装:把抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只能通过被授权的方法,才能对字段进行操作。
封装的好处:
- 隐藏实现细节;
- 可以对数据进行验证,保证数据安全合理。
如何体现封装 - 对结构体中的属性进行封装;
- 通过方法,包,实现封装
封装的实现步骤: - 将结构体、字段的首字母小写;
- 给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数;
- 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值;
- 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
案例演示:代码结构如下:
package hello
import "fmt"
type person struct {
Name string
age int
sal float64
desc string
}
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
func (p *person) SetDesc(desc string) {
p.desc = desc
}
func (p *person) GetDesc() string {
return p.desc
}
func (p *person) SetAge(age int) {
if age > 0 && age<150 {
p.age = age
} else {
fmt.Println("年龄不正确")
}
}
func (p *person) GetAge() int {
return p.age
}
package main
import (
"fmt"
"hello"
)
func main() {
p := hello.NewPerson("dada")
p.SetAge(200)
p.SetDesc("haha")
fmt.Println(p)
}
年龄不正确
&{dada 0 0 haha}
工厂模式自动注册
简单工厂模式
前面的封装案例即为简单工厂模式的实例代码
go 语言没有构造函数一说,所以一般会定义NewXXX函数来初始化相关类。 NewXXX 函数返回接口时就是简单工厂模式,也就是说Golang的一般推荐做法就是简单工厂。
在这个simplefactory包中只有API 接口和NewAPI函数为包外可见,封装了实现细节。
package simplefactory
import (
"fmt"
)
type API interface {
Say(name string) string
}
type api1 struct {}
func (a api1) Say(name string) string {
return fmt.Sprintf("api1, %s", name)
}
type api2 struct {}
func (a api2) Say(name string) string {
return fmt.Sprintf("api2, %s", name)
}
func NewAPI(a int) API{
switch a {
case 1:
return api1{}
case 2:
return api2{}
default:
return nil
}
}
package main
import (
"fmt"
"simplefactory"
)
func main(){
gc := simplefactory.NewAPI(1)
s := gc.Say("haha")
fmt.Println(s)
}
工厂模式自动注册
这种模式就是上一个版本的升级版本,可以很好的优化工厂中的if和与else,
实现的思路:
- 工厂实例中会存在这用于绑定和记录工厂的map集合;同时也会提供注册和获取的方法;
- 工厂对象实例会在初始化的时候加入进工厂的map集合中;
- 运用的时候通过工厂实例的获取方法来获取对应的工厂对象实例
比如以支付为例如微信支付、支付包支付,有多种支付方式这个就可以利用到工厂模式;
package pay
type OBJ interface{
Unifideorder(order string)
}
var (
pays = make(map[string] func() OBJ)
)
func Register(kk string, factory func() OBJ) {
pays[kk] = factory
}
func GetPay(kk string) OBJ {
if fun,isok := pays[kk]; isok {
return fun()
}
panic("kk not found!!")
}
package pay
import (
"fmt"
)
type Alipay struct {}
func init() { //init引入包是会自动初始化执行
Register("alipay",func() OBJ{
return &Alipay{}
})
}
func (a *Alipay) Unifideorder(order string) {
fmt.Println("Alipay is good !!!"+order)
}
package pay
import (
"fmt"
)
type Wechat struct {}
func init() {
Register("wechat",func() OBJ{
return &Wechat{}
})
}
func (a *Wechat) Unifideorder(order string) {
fmt.Println("Wechat is good !!!"+order)
}
package main
import (
"fmt"
"simplefactory"
"pay"
)
func main(){
pay := pay.GetPay("alipay")
pay.Unifideorder("888")
}
Alipay is good !!!888
单例模式
单例模式要实现的效果就是,对于应用单例模式的类,整个程序中只存在一个实例化对象
go并不是一种面向对象的语言,所以我们使用结构体来替代
懒汉模式
构建一个示例结构体
package main
type example struct {
name string
}
var instance *example
func GetExample() *example {
if instance == nil {
instance = new(example)
}
return instance
}
懒汉模式存在线程安全问题,在第3步的时候,如果有多个线程同时调用了这个方法, 那么都会检测到instance为nil,就会创建多个对象,所以出现了饿汉模式…
饿汉模式
// 构建一个结构体,用来实例化单例
type example2 struct {
name string
}
// 声明一个私有变量,作为单例
var instance2 *example2
// init函数将在包初始化时执行,实例化单例
func init() {
instance2 = new(example2)
instance2.name = "初始化单例模式"
}
func GetInstance2() *example2 {
return instance2
}
func main() {
s := GetInstance2()
fmt.Println(s.name)
}
饿汉模式将在包加载的时候就创建单例对象,当程序中用不到该对象时,浪费了一部分空间
和懒汉模式相比,更安全,但是会减慢程序启动速度
sync包与锁
锁实际在程序中是经常会出现和运用到的,而锁的作用就是来保护数据避免程序执行出现问题;如同只有一个厕所前者上厕所锁住厕所门后者就无法进入厕所骚扰前者;一样的道理;
在并发的情况下,多个线程或协程同时其修改一个变量,使用锁能保证在某一时间内,只有一个协程或线程修改这一变量。
什么是线程安全?
我们用如下代码来测试并演示效果来解释
package main
import (
"fmt"
)
var count int
func test1() {
for i:=0; i<10000;i++ {
count++
}
}
func test2() {
for i:=0; i<10000;i++ {
count++
}
}
func main(){
test1()
test2()
fmt.Printf("count-%d\n",count)
}
count-20000
package main
import (
"fmt"
"time"
)
var count int
func test1() {
for i:=0; i<10000;i++ {
count++
}
}
func test2() {
for i:=0; i<10000;i++ {
count++
}
}
func main(){
go test1()
go test2()
time.Sleep(time.Second * 1) //休眠1秒
fmt.Printf("count-%d\n",count)
}
count-11456
在如上的代码中可以看到test1和test2同时运行对count(共享资源)进行修改时,就会出现冲突,最终结果就不是20000了
如上的情况中就出现了线程安全的问题
线程安全
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况
sync 锁
在 Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 “synchronized” 一词,这意味着线程将有序的对同一变量进行访问。
sync.Mutex 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区
栗子:
假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 Mutex 来实现的一个典型例子如下:
import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
//如果一个函数想要改变这个变量可以这样写
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
利用sync锁解决如上问题-互斥锁
package main
import (
"fmt"
"time"
"sync"
)
var count int
type dd struct {
mu sync.Mutex
}
func test1(dd *dd) {
dd.mu.Lock()
for i:=0; i<10000;i++ {
count++
}
dd.mu.Unlock()
}
func test2(dd *dd) {
dd.mu.Lock()
for i:=0; i<10000;i++ {
count++
}
dd.mu.Unlock()
}
func main(){
dd:=&dd{}
go test1(dd)
go test2(dd)
time.Sleep(time.Second * 1)
fmt.Printf("count-%d\n",count)
}
count-20000
下一篇:go-包 5-2
上一篇:go-函数与方法 4