• golang设计模式——组合模式


    组合模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-86JxiUa2-1660313923766)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812215343211.png)]

    组合模式针对于特定场景,如文件管理、组织管理等,使用该模式能简化管理,使代码变得非常简洁。

    组合模式:将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

    UML

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70LYbG9K-1660313923767)(https://shidawuhen.github.io/2021/06/20/Go%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-16-%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/image-20210610101011712.png)]

    分析

    单看UML图可能不清晰,举个栗子会容易一些。Composite是目录,Leaf是目录下的文件,目录和文件都继承自Component。目录能够增加、删除文件,可以展示目录所在位置,文件只能展示文件所在位置。

    对于目录这种需求,有两种实现方式。

    第一种不使用组合模式,只用一个类,有2个核心变量

    • 一个成员变量表明对象是文件还是目录
    • 一个成员变量存放目录下文件列表,如果对象为文件,则该变量为空
    type FileSystemNode struct {
       isFile   bool             //表明是文件还是目录
       subNodes []FileSystemNode //目录下包含的内容
    }
    
    • 1
    • 2
    • 3
    • 4

    第二种方案使用组合模式。虽然第一种方案能够实现文件管理的功能,但并不优雅。因为文件和目录是不同的,各自有各自的特性,将特有的内容放到一个类里,不满足单一职责原则

    所以我们可以将其拆分为两个类:文件类和目录类。两个类必须继承自同一个父类,除了重复的功能可以复用外,更重要的一点是消除了两个类调用上的区别,subNodes不需要做任何区分。而且这两个类可以独立进化,相互不影响,何乐而不为呢。

    使用场景

    组合模式在使用上,特别像深度优先遍历或者广度优先遍历,一般用于组织结构、文件管理上,这些功能都有共通点:个体和集体无论在功能上还是认知上都极为相似。

    代码实现

    这次代码简单实现一下目录和文件的添加、显示功能吧。

    package main
    
    import "fmt"
    
    const Separator = "--"
    
    /**
     * @Description: 文件系统接口,文件和目录都要实现该接口
     */
    type FileSystemNode interface {
       Display(separator string)
    }
    
    /**
     * @Description: 文件通用功能
     */
    type FileCommonFunc struct {
       fileName string
    }
    
    /**
     * @Description: 设置文件名称
     * @receiver f
     * @param fileName
     */
    func (f *FileCommonFunc) SetFileName(fileName string) {
       f.fileName = fileName
    }
    
    /**
     * @Description: 文件类
     */
    type FileNode struct {
       FileCommonFunc
    }
    
    /**
     * @Description: 文件类显示文件内容
     * @receiver f
     */
    func (f *FileNode) Display(separator string) {
       fmt.Println(separator + f.fileName + "   文件内容为:Hello,world")
    }
    
    /**
     * @Description: 目录类
     */
    type DirectoryNode struct {
       FileCommonFunc
       nodes []FileSystemNode
    }
    
    /**
     * @Description: 目录类展示文件名
     * @receiver d
     */
    func (d *DirectoryNode) Display(separator string) {
       fmt.Println(separator + d.fileName)
       for _, node := range d.nodes {
          node.Display(separator + Separator)
       }
    }
    
    /**
     * @Description: 添加目录或者文件
     * @receiver d
     * @param f
     */
    func (d *DirectoryNode) Add(f FileSystemNode) {
       d.nodes = append(d.nodes, f)
    }
    func main() {
       //初始化
       biji := DirectoryNode{}
       biji.SetFileName("笔记")
    
       huiyi := DirectoryNode{}
       huiyi.SetFileName("会议")
    
       chenhui := FileNode{}
       chenhui.SetFileName("晨会.md")
    
       zhouhui := FileNode{}
       zhouhui.SetFileName("周会.md")
       //组装
       biji.Add(&huiyi)
       huiyi.Add(&chenhui)
       huiyi.Add(&zhouhui)
       //显示
       biji.Display(Separator)
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    ➜ myproject go run main.go

    –笔记

    —-会议

    ——晨会.md 文件内容为:Hello,world

    ——周会.md 文件内容为:Hello,world

    文件类和目录类都实现了FileSystemNode接口,所以目录类管理文件类如同管理自己一样。两者都组合了FileCommonFunc类,可以复用相同功能。最后就是两者可以独立变化,如目录类有Add功能,但文件类没有。

    实例

    公司的人员组织就是一个典型的树状的结构,现在假设我们现在有部分,和员工,两种角色,一个部门下面可以存在子部门和员工,员工下面不能再包含其他节点。
    我们现在要实现一个统计一个部门下员工数量的功能

    代码

    package composite
    
    // IOrganization 组织接口,都实现统计人数的功能
    type IOrganization interface {
    	Count() int
    }
    
    // Employee 员工
    type Employee struct {
    	Name string
    }
    
    // Count 人数统计
    func (Employee) Count() int {
    	return 1
    }
    
    // Department 部门
    type Department struct {
    	Name string
    
    	SubOrganizations []IOrganization
    }
    
    // Count 人数统计
    func (d Department) Count() int {
    	c := 0
    	for _, org := range d.SubOrganizations {
    		c += org.Count()
    	}
    	return c
    }
    
    // AddSub 添加子节点
    func (d *Department) AddSub(org IOrganization) {
    	d.SubOrganizations = append(d.SubOrganizations, org)
    }
    
    // NewOrganization 构建组织架构 demo
    func NewOrganization() IOrganization {
    	root := &Department{Name: "root"}
    	for i := 0; i < 10; i++ {
    		root.AddSub(&Employee{})
    		root.AddSub(&Department{Name: "sub", SubOrganizations: []IOrganization{&Employee{}}})
    	}
    	return root
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    单元测试

    package composite
    
    import (
    	"testing"
    
    	"github.com/stretchr/testify/assert"
    )
    
    func TestNewOrganization(t *testing.T) {
    	got := NewOrganization().Count()
    	assert.Equal(t, 20, got)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结

    组合模式是对指定场景有用,所以大家能不能用到,完全看运气。这个设计模式满足单一职责原则、开闭原则、里氏替换原则。

  • 相关阅读:
    FFplay文档解读-48-多媒体过滤器二
    软键盘控制cesium相机移动旋转
    Altium Dsigner 20 工艺参数设置修改
    (D卷,100分)- 最富裕的小家庭(Java & JS & Python & C)
    实现一个Prometheus exporter
    【数字IC验证快速入门】4、熟悉数字IC验证中常用的Linux基本操作
    深度学习实战56-基于VR虚拟现实眼镜与计算机视觉远程操控机器人,实现远程协助独居老人生活起居
    LeetCode每日一题(756. Pyramid Transition Matrix)
    Qt OpenGL(二十五)——Qt OpenGL 核心模式-Qt封装的函数实现彩色三角形
    面对海量数据挑战,企业怎样进行数据处理?
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126312334