2

Golang设计模式(01)-创造型模式

 2 years ago
source link: https://hongker.github.io/2021/05/10/golang-design-model-build/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Golang设计模式(01)-创造型模式

2021-05-10

本文介绍设计模式里的创造型模式,以及在go里的实现方式。

在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。

主要的创造型模式包括:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。下面就依次介绍这几种设计模式:

image

  • 定义
    无论获取多少次对象,都保证对象的初始化只会调用一次。

    • 日志管理器,一般应用里只会存在一个日志管理器。
    • 对象连接池,比如数据库连接池,只会初始化一次。
    • 配置项,应用里一般只会初始化一个配置对象,用来读取、修改配置信息。
    • 以及其他在资源共享的情况下,需要避免初始化多个对象带来的损耗的情况。
  • 实现
    在golang里我们可以通过sync.Once来确保对象的初始化在运行过程中,只会调用一次。

    import "sync"

    // Container 全局容器接口
    type Container interface{
    // Add 添加元素
    Add(name string, elem interface{})
    // Get 获取元素
    Get(name string) interface{}
    }
    // MapContainer 基于map的container实现
    type MapContainer struct {
    once sync.Once
    mu sync.RWMutex
    elements map[string]interface{}
    }

    // initialize 初始化
    func (container *MapContainer) initialize() {
    log.Println("initialize..")
    // ..或者其他什么业务逻辑
    container.elements = make(map[string]interface{}, 10)
    }
    // Add 添加元素
    func (container *MapContainer) Add(name string, elem interface{}) {
    container.mu.Lock()
    defer container.mu.Unlock()
    container.elements[name] = elem
    }
    // Get 获取元素
    func (container *MapContainer) Get(name string) interface{} {
    container.mu.RLock()
    defer container.mu.RUnlock()
    return container.elements[name]
    }

    var container = new(MapContainer)
    // GetContainer 获取全局容器的实例
    func GetContainer() Container {
    // 使用once保证初始化只执行一次
    container.once.Do(func() {
    container.initialize()
    })
    return container
    }
  • import (
    "github.com/stretchr/testify/assert"
    "testing"
    )

    func TestGetContainer(t *testing.T) {
    // 调用多次GetContainer(),依然只执行一次initialize()方法
    GetContainer().Add("number", 1)
    GetContainer().Add("float", 1.4)
    assert.Equal(t, 1, GetContainer().Get("number"))
    assert.Equal(t, 1.4, GetContainer().Get("float"))
    }

工厂模式有简单工厂模式和工厂方法模式。前者负责多个对象的创建,通过参数选择创建哪个对象,而后者是专门负责某个对象的创建过程。

简单工厂模式

  • 定义
    简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。解耦。

  • // LoggerType 日志管理器类型
    type LoggerType uint
    const(
    // 文件类型
    File LoggerType = iota + 1
    // 数据库类型
    DB
    )
    // Logger 日志管理器
    type Logger interface {
    // Write 写日志
    Write()
    }

    // FileLogger 文件日志管理器
    type FileLogger struct {}
    // Write 写入文件
    func (logger FileLogger) Write() {
    fmt.Println("write to file...")
    }

    // DBLogger 数据库日志管理器
    type DBLogger struct {}
    // Write 写入数据库
    func (logger DBLogger) Write() {
    fmt.Println("write to database...")
    }
    // LoggerSampleFactory 简单工厂类
    type LoggerSimpleFactory struct {}
    // Create 创建
    func (factory LoggerSimpleFactory) Create(loggerType LoggerType) Logger{
    var logger Logger
    switch loggerType {
    case File:
    logger = new(FileLogger)
    case DB:
    logger = new(DBLogger)
    }
    return logger
    }
    // NewFactory
    func NewFactory() *LoggerSimpleFactory {
    return new(LoggerSimpleFactory)
    }
  • UnitTest

    func TestNewFactory(t *testing.T) {
    factory := NewFactory()
    fileLogger := factory.Create(File)
    fileLogger.Write()

    dbLogger := factory.Create(DB)
    dbLogger.Write()
    }

工厂方法模式

  • 定义
    工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。

  • // LoggerFactory 日志管理器的工厂接口
    type LoggerFactory interface {
    Create() Logger
    }

    type FileLoggerFactory struct {}
    func (factory FileLoggerFactory) Create() Logger{
    return new(FileLogger)
    }

    type DBLoggerFactory struct {}
    func (factory DBLoggerFactory) Create() Logger {
    return new(DBLogger)
    }

    func NewFileLoggerFactory() LoggerFactory {
    return new(FileLoggerFactory)
    }

    func NewDBLoggerFactory() LoggerFactory {
    return new(FileLoggerFactory)
    }

抽象工厂模式

  • 定义
    为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。它与工厂模式的区别在于它是创建多种对象的集合。

  • UML
    image

  • // CompanyFactory Company的工厂接口
    type CompanyFactory interface {
    // CreateEmployee 新增一个职员
    CreateEmployee() Employee
    // CreateDepartment 新增一个部门
    CreateDepartment(name string) Department
    }
    // Employee 职员接口
    type Employee interface {
    Work()
    }
    // Department 部门接口
    type Department interface {
    Name() string
    }

    // --------------具体实现----------------

    // AppleEmployee apple公司的职员
    type AppleEmployee struct {}
    func (employee AppleEmployee) Work() {
    fmt.Println("apple employee working..")
    }
    // AppleDepartment apple公司的部门
    type AppleDepartment struct {
    name string
    }
    func (department AppleDepartment) Name() string {
    return fmt.Sprintf("apple:%s", department.name)
    }

    // AppleFactory apple工厂类
    type AppleFactory struct {}
    func (factory AppleFactory) CreateEmployee() Employee {
    return &AppleEmployee{}
    }
    func (factory AppleFactory) CreateDepartment(name string) Department {
    return &AppleDepartment{name: name}
    }


    // GoogleEmployee google公司的职员
    type GoogleEmployee struct {}
    func (employee GoogleEmployee) Work() {
    fmt.Println("google employee working..")
    }

    // GoogleDepartment google公司的部门
    type GoogleDepartment struct {
    name string
    }
    func (department GoogleDepartment) Name() string {
    return fmt.Sprintf("google:%s", department.name)
    }

    // google工厂类
    type GoogleFactory struct {}
    func (factory GoogleFactory) CreateEmployee() Employee {
    return &GoogleEmployee{}
    }
    func (factory GoogleFactory) CreateDepartment(name string) Department {
    return &GoogleDepartment{name: name}
    }
  • // TestAppleFactory 测试apple工厂类
    func TestAppleFactory(t *testing.T) {
    factory := new(AppleFactory)
    employee := factory.CreateEmployee()
    employee.Work()
    department := factory.CreateDepartment("dep01")
    fmt.Println(department.Name())
    }
    // TestGoogleFactory 测试google工厂类
    func TestGoogleFactory(t *testing.T) {
    factory := new(GoogleFactory)
    employee := factory.CreateEmployee()
    employee.Work()
    department := factory.CreateDepartment("dep01")
    fmt.Println(department.Name())
    }

建造者模式

  • 定义
    当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

  • import "fmt"

    // Person 初始化该类时,有两个属性必填,两个属性选填
    type Person struct {
    name string // 必填
    sex string // 必填
    age int // 选填
    birthday string // 选填
    }

    func (person Person) Info() {
    fmt.Printf("名字:%s, 性别:%s,年龄:%d,生日:%s\n", person.name, person.sex, person.age, person.birthday)
    }

    // PersonBuilder Person的Builder接口,其中主要为Person的选填属性的setter方法
    type PersonBuilder interface {
    // SetAge 设置年龄
    SetAge(age int) *Builder
    // SetBirthday 设置生日
    SetBirthday(birthday string) *Builder
    // Build 创建Person对象
    Build() Person
    }
    // Builder PersonBuilder接口的实现类
    type Builder struct {
    name string
    sex string
    age int
    birthday string
    }

    func (builder *Builder) SetAge(age int) *Builder {
    builder.age = age
    return builder
    }

    func (builder *Builder) SetBirthday(birthday string) *Builder {
    builder.birthday = birthday
    return builder
    }

    // NewBuilder 构造一个builder
    func NewBuilder(name, sex string) PersonBuilder {
    return &Builder{name: name, sex: sex}
    }

    // Build 生成Person实例
    func (builder *Builder) Build() Person {
    return Person{
    name: builder.name,
    sex: builder.sex,
    age: builder.age,
    birthday: builder.birthday,
    }
    }
  • func TestNewBuilder(t *testing.T) {
    person := NewBuilder("张三", "男").
    SetAge(26).
    SetBirthday("1994/01/01").
    Build()

    person.Info()
    }
  • 定义
    用原型实例指定创建对象的种类,并且通过这些原型创建新的对象,这种行为成为拷贝(克隆)。

  • 拷贝方式
    拷贝分浅拷贝和深拷贝两种方式。

    浅拷贝:更改拷贝项中的指针或引用对象,源自同一个原型的全部的克隆对象都会受到影响
    深拷贝:重新申请内存空间,更改拷贝项中的指针或引用对象,不会影响其他克隆对象
  • 使用场景
    在实例化对象比较复杂繁琐、成本较大的时候,可以选择原型模式,克隆已有的对象,简化创建过程,提高效率。

  • type Student struct {
    Name string
    Books []string
    }

    // Clone 拷贝原型对象
    func (student *Student) Clone() *Student {
    // 浅拷贝:更改拷贝项中的指针或引用对象,源自同一个原型的全部的克隆对象都会受到影响
    return student
    }

    // DeepClone 深拷贝:重新申请内存空间,更改拷贝项中的指针或引用对象,不会影响其他克隆对象
    func (student *Student) DeepClone() *Student {

    books := make([]string, 0, len(student.Books))
    for _, book := range student.Books {
    books = append(books, book)
    }
    return &Student{
    Name: student.Name,
    Books: student.Books,
    }
    }
  • // TestStudent_Clone 测试浅拷贝
    func TestStudent_Clone(t *testing.T) {
    student := &Student{
    Name: "user1",
    Books: []string{"三国演义","红楼梦","算法导论"},
    }

    studentClone := student.Clone()
    assert.Equal(t, student.Name, studentClone.Name)
    assert.Equal(t, student.Books, studentClone.Books)

    student.Books = append(student.Books, "计算机网络")
    assert.Equal(t, student.Books, studentClone.Books)
    }
    // TestStudent_DeepClone 测试深拷贝
    func TestStudent_DeepClone(t *testing.T) {
    student := &Student{
    Name: "user1",
    Books: []string{"三国演义","红楼梦","算法导论"},
    }

    studentClone := student.DeepClone()
    assert.Equal(t, student.Name, studentClone.Name)
    assert.Equal(t, student.Books, studentClone.Books)

    student.Books = append(student.Books, "计算机网络")
    assert.NotEqual(t, student.Books, studentClone.Books)
    }

通过这几种创建型模式去优化我们在项目中创建对象的代码,一方面可以提高可扩展性,另一方面也是让代码更加优雅。

PS:为什么我们需要面向接口编程?比如有人问你:明明用一个struct就好了,为什么要定义一个interface?我只能说:navie!

我想在开发过程中,人的思维总是被限制的,你总是无法考虑到未来会变成什么样。比如项目现在用的是mysql数据库,说不定后面就换成mongodb了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK