手撸golang 结构型设计模式 组合模式 缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

组合模式

组合模式(Composite Pattern)又叫作整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性,属于结构型设计模式。
_

透明组合vs安全组合
  • 透明组合: 叶子节点和树枝节点具有完全一致的接口
  • 安全组合: 叶子节点和树枝节点从公共接口继承, 并做个性化扩展
场景
  • 某线上学校, 提供系列java学习课程
  • 提供某些简单课程, 如"java基础", 学员可一次性学完
  • 提供某些组合课程, 内部包含了若干简单课程, 如"java架构师"课程, 内部包含"java基础", "java高级编程"等多门课程, 需要分步依次学习, 才能完成
  • 为方便业务层调用, 根据安全组合模式, 简单课程和组合课程均实现公共接口的Learn方法.
设计
  • IUser: 定义学员用户接口
  • ICourse: 定义课程的公共接口
  • ICompositeCourse: 组合课程的个性化接口, 从ICourse继承, 并添加了Append(子课程)的方法
  • tMockUser: 虚拟学员的实现类
  • tSimpleCourse: 简单课程的实现类, 实现ICourse接口
  • tCompositeCourse: 组合课程的实现类, 继承tSimpleCourse, 并实现了ICompositeCourse接口
单元测试

composite_pattern_test.go

package structural_patterns

import (
    "learning/gooop/structural_patterns/composite"
    "testing"
)

func Test_CompositePattern(t *testing.T) {
    user := composite.NewMockUser(1, "张三")

    sc := composite.NewSimpleCourse(11, "Java基础", 100)
    user.Learn(sc)

    user = composite.NewMockUser(2, "李四")
    cc := composite.NewCompositeCourse(21, "Java架构师", 500)
    cc.Append(composite.NewSimpleCourse(11, "Java基础", 100))
    cc.Append(composite.NewSimpleCourse(12, "Java高级编程", 100))
    cc.Append(composite.NewSimpleCourse(13, "设计模式", 100))
    cc.Append(composite.NewSimpleCourse(14, "Spring技术内幕", 100))
    cc.Append(composite.NewSimpleCourse(15, "SpringCloud架构指南", 100))
    user.Learn(cc)
}
测试输出
$ go test -v composite_pattern_test.go 
=== RUN   Test_CompositePattern
张三 is learning Java基础
李四 is learning Java架构师.Java基础
李四 is learning Java架构师.Java高级编程
李四 is learning Java架构师.设计模式
李四 is learning Java架构师.Spring技术内幕
李四 is learning Java架构师.SpringCloud架构指南
--- PASS: Test_CompositePattern (0.00s)
PASS
ok      command-line-arguments  0.005s
IUser.go

定义学员用户接口

package composite

type IUser interface {
    ID() int
    Name() string
    Learn(course ICourse)
}
ICourse.go

定义课程的公共接口

package composite

type ICourse interface {
    ID() int
    Name() string
    Price() float64

    SetUser(user IUser)
    Learn() LearningStates
}


type LearningStates int
const MORE LearningStates = 1
const DONE LearningStates = 2
ICompositeCourse.go

组合课程的个性化接口, 从ICourse继承, 并添加了Append(子课程)的方法

package composite

type ICompositeCourse interface {
    ICourse

    Append(course ICourse)
}
tMockUser.go

虚拟学员的实现类

package composite


type tMockUser struct {
    iID int
    sName string
}

func NewMockUser(id int, name string) IUser {
    return &tMockUser{
        id, name,
    }
}

func (me *tMockUser) ID() int {
    return me.iID
}

func (me *tMockUser) Name() string {
    return me.sName
}

func (me *tMockUser) Learn(course ICourse) {
    course.SetUser(me)

    for {
        state := course.Learn()
        if state == DONE {
            break
        }
    }
}
tSimpleCourse.go

简单课程的实现类, 实现ICourse接口

package composite

import "fmt"

type tSimpleCourse struct {
    iID int
    sName string
    fPrice float64
    mUser IUser
}


func NewSimpleCourse(id int, name string, price float64) ICourse {
    return &tSimpleCourse{
        id, name, price, nil,
    }
}

func (me *tSimpleCourse) ID() int {
    return me.iID
}

func (me *tSimpleCourse) Name() string {
    return me.sName
}

func (me *tSimpleCourse) Price() float64 {
    return me.fPrice
}

func (me *tSimpleCourse) SetUser(user IUser) {
    me.mUser = user
}

func (me *tSimpleCourse) Learn() LearningStates {
    fmt.Printf("%s is learning %s\n", me.mUser.Name(), me.sName)
    return DONE
}
tCompositeCourse.go

组合课程的实现类, 继承tSimpleCourse, 并实现了ICompositeCourse接口

package composite

import "fmt"

type tCompositeCourse struct {
    tSimpleCourse

    mCourseList []ICourse
    iCourseIndex int
}


func NewCompositeCourse(id int, name string, price float64) ICompositeCourse {
    return &tCompositeCourse {
        tSimpleCourse: tSimpleCourse{
            id, name, price, nil,
        },
        mCourseList: make([]ICourse, 0),
        iCourseIndex: 0,
    }
}

func (me *tCompositeCourse) Append(course ICourse) {
    me.mCourseList = append(me.mCourseList, course)
}

func (me *tCompositeCourse) Learn() LearningStates {
    if me.IsDone() {
        fmt.Printf("%s is learning %s: no more courses\n", me.mUser.Name(), me.Name())
        return DONE
    }


    course := me.mCourseList[me.iCourseIndex]
    fmt.Printf("%s is learning %s.%s\n", me.mUser.Name(), me.Name(), course.Name())
    me.iCourseIndex++

    if me.IsDone() {
        return DONE
    } else {
        return MORE
    }
}

func (me *tCompositeCourse) IsDone() bool {
    return me.iCourseIndex >= len(me.mCourseList)
}
组合模式小结

组合模式的优点
(1)清楚地定义各层次的复杂对象,表示对象的全部或部分层次。
(2)让客户端忽略了层次的差异,方便对整个层次结构进行控制。
(3)简化客户端代码。
(4)符合开闭原则。
组合模式的缺点
(1)限制类型时会较为复杂。
(2)使设计变得更加抽象。

(end)