计模式之结构型模式

迭代沙漏
• 阅读 761

简述
在面向对象编程中,有两个常见的对象设计方法,组合和继承,两者都可以解决代码复用的问题,但是使用后者时容易出现继承层次过深,对象关系过于复杂的副作用,从而导致代码的可维护性变差。因此,一个经典的面向对象设计原则是:组合优于继承。

我们都知道,组合所表示的语义为“has-a”,也就是部分和整体的关系,最经典的组合模式描述如下:

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

Go语言天然就支持了组合模式,而且从它不支持继承关系的特点来看,Go也奉行了组合优于继承的原则,鼓励大家在进行程序设计时多采用组合的方法。Go实现组合模式的方式有两种,分别是直接组合(Direct Composition)和嵌入组合(Embedding Composition),下面我们一起探讨这两种不同的实现方法。

Go实现
直接组合(Direct Composition)的实现方式类似于Java/C++,就是将一个对象作为另一个对象的成员属性。

一个典型的实现如《使用Go实现GoF的23种设计模式(一)》中所举的例子,一个Message结构体,由Header和Body所组成。那么Message就是一个整体,而Header和Body则为消息的组成部分。

type Message struct {

Header *Header
Body   *Body

}
现在,我们来看一个稍微复杂一点的例子,同样考虑上一篇文章中所描述的插件架构风格的消息处理系统。前面我们用抽象工厂模式解决了插件加载的问题,通常,每个插件都会有一个生命周期,常见的就是启动状态和停止状态,现在我们使用组合模式来解决插件的启动和停止问题。

首先给Plugin接口添加几个生命周期相关的方法:

复制代码
package plugin
...
// 插件运行状态
type Status uint8

const (

Stopped Status = iota
Started

)

type Plugin interface {
// 启动插件

Start()

// 停止插件

Stop()

// 返回插件当前的运行状态

Status() Status

}
// Input、Filter、Output三类插件接口的定义跟上一篇文章类似
// 这里使用Message结构体替代了原来的string,使得语义更清晰
type Input interface {

Plugin
Receive() *msg.Message

}

type Filter interface {

Plugin
Process(msg *msg.Message) *msg.Message

}

type Output interface {

Plugin
Send(msg *msg.Message)

}
复制代码
对于插件化的消息处理系统而言,一切皆是插件,因此我们将Pipeine也设计成一个插件,实现Plugin接口:

复制代码
package pipeline
...
// 一个Pipeline由input、filter、output三个Plugin组成
type Pipeline struct {

status plugin.Status
input  plugin.Input
filter plugin.Filter
output plugin.Output

}

func (p *Pipeline) Exec() {

msg := p.input.Receive()
msg = p.filter.Process(msg)
p.output.Send(msg)

}
// 启动的顺序 output -> filter -> input
func (p *Pipeline) Start() {

p.output.Start()
p.filter.Start()
p.input.Start()
p.status = plugin.Started
fmt.Println("Hello input plugin started.")

}
// 停止的顺序 input -> filter -> output
func (p *Pipeline) Stop() {

p.input.Stop()
p.filter.Stop()
p.output.Stop()
p.status = plugin.Stopped
fmt.Println("Hello input plugin stopped.")

}

func (p *Pipeline) Status() plugin.Status {

return p.status

}
复制代码
一个Pipeline由Input、Filter、Output三类插件组成,形成了“部分-整体”的关系,而且它们都实现了Plugin接口,这就是一个典型的组合模式的实现。Client无需显式地启动和停止Input、Filter和Output插件,在调用Pipeline对象的Start和Stop方法时,Pipeline就已经帮你按顺序完成对应插件的启动和停止。

相比于上一篇文章,在本文中实现Input、Filter、Output三类插件时,需要多实现3个生命周期的方法。还是以上一篇文章中的HelloInput、UpperFilter和ConsoleOutput作为例子,具体实现如下:

复制代码
package plugin
...
type HelloInput struct {

status Status

}

func (h HelloInput) Receive() msg.Message {
// 如果插件未启动,则返回nil

if h.status != Started {
    fmt.Println("Hello input plugin is not running, input nothing.")
    return nil
}
return msg.Builder().
    WithHeaderItem("content", "text").
    WithBodyItem("Hello World").
    Build()

}

func (h *HelloInput) Start() {

h.status = Started
fmt.Println("Hello input plugin started.")

}

func (h *HelloInput) Stop() {

h.status = Stopped
fmt.Println("Hello input plugin stopped.")

}

func (h *HelloInput) Status() Status {

return h.status

}
package plugin
...
type UpperFilter struct {

status Status

}

func (u UpperFilter) Process(msg msg.Message) *msg.Message {

if u.status != Started {
    fmt.Println("Upper filter plugin is not running, filter nothing.")
    return msg
}
for i, val := range msg.Body.Items {
    msg.Body.Items[i] = strings.ToUpper(val)
}
return msg

}

func (u *UpperFilter) Start() {

u.status = Started
fmt.Println("Upper filter plugin started.")

}

func (u *UpperFilter) Stop() {

u.status = Stopped
fmt.Println("Upper filter plugin stopped.")

}

func (u *UpperFilter) Status() Status {

return u.status

}

package plugin
...
type ConsoleOutput struct {

status Status

}

func (c ConsoleOutput) Send(msg msg.Message) {

if c.status != Started {
    fmt.Println("Console output is not running, output nothing.")
    return
}
fmt.Printf("Output:\n\tHeader:%+v, Body:%+v\n", msg.Header.Items, msg.Body.Items)

}

func (c *ConsoleOutput) Start() {

c.status = Started
fmt.Println("Console output plugin started.")

}

func (c *ConsoleOutput) Stop() {

c.status = Stopped
fmt.Println("Console output plugin stopped.")

}

func (c *ConsoleOutput) Status() Status {

return c.status

}
复制代码
测试代码如下:

复制代码
package test
...
func TestPipeline(t *testing.T) {

p := pipeline.Of(pipeline.DefaultConfig())
p.Start()
p.Exec()
p.Stop()

}
// 运行结果
=== RUN TestPipeline
Console output plugin started.
Upper filter plugin started.
Hello input plugin started.
Pipeline started.
Output:

Header:map[content:text], Body:[HELLO WORLD]

Hello input plugin stopped.
Upper filter plugin stopped.
Console output plugin stopped.
Hello input plugin stopped.
--- PASS: TestPipeline (0.00s)
PASS
复制代码
组合模式的另一种实现,嵌入组合(Embedding Composition),其实就是利用了Go语言的匿名成员特性,本质上跟直接组合是一致的。

还是以Message结构体为例,如果采用嵌入组合,则看起来像是这样:

type Message struct {

Header
Body

}
// 使用时,Message可以引用Header和Body的成员属性,例如:
msg := &Message{}
msg.SrcAddr = "192.168.0.1"

点赞
收藏
评论区
推荐文章
亚瑟 亚瑟
4年前
面向对象设计原则
面向对象设计原则对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。7种常用的面向对象设计原则|设计原则名称|定义|使用频率||
Stella981 Stella981
3年前
Javascript 是如何体现继承的 ?
js继承的概念js里常用的如下两种继承方式:原型链继承(对象间的继承)类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念。所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现在面向对象的语言中,我们使用类来创建一个自定义对象
Stella981 Stella981
3年前
JavaScript面向对象编程的15种设计模式
在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。在JavaScript中并没有类这种概念,面向对象编程不是基于类,而是基于原型去面向对象编程,JS中的函数属于一等对象,而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。ps:本文之讲述面向对象编程的设计模式策略,JavaScript原型的基础请参考阮一峰面向
Wesley13 Wesley13
3年前
JAVA 基础知识
JAVA纯面向对象语言,有平台无关性,一次编译到处运行,编辑器会把java代码变成中间代码,然后在JVM上解释执行。拥有很多内置的类库,提供了对Web应用开发的支持,具有较好的安全和健壮性。JAVA和C的异同都是面向对象语言使用了面向对象的思想(封装,继承,多态),面向对象的特性(继承和组合)  面向对象有以下特点:  (1
Stella981 Stella981
3年前
Javascript继承5:如虎添翼
/寄生式继承其实就是对原型继承的第二次封装,在封装过程中对继承的对象进行了扩展。也存在原型继承的缺点!!这种思想的作用也是为了寄生组合式继承模式的实现。///声明基对象varbook{name:'jsbook',al
Wesley13 Wesley13
3年前
Java 面向对象的设计原则
一、1、面向对象思想的核心:封装、继承、多态。2、面向对象编程的追求:  高内聚低耦合的解决方案;  代码的模块化设计;3、什么是设计模式:  针对反复出现的问题的经典解决方案,是对特定条件下(上下文)问题的设计方案的经验总结,是前人设计实践经验的精华。4、面向对象设计原则
Wesley13 Wesley13
3年前
23种设计模式(面向对象语言)
一、设计模式的分类总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。创建型模式是用来创建对象的模式,抽象了实例化的过程,帮助一个系统独立于其他关联对象的创建、组合和表示方式。所有的创建型模式都有两个主要功能:  1.将系统所使用的具体类的信息封装起来  2.隐藏
设计模式-单例模式概述 | 京东云技术团队
我们常把23种经典的设计模式分为三类:创建型、结构型、行为型,其中创建型设计模式主要解决“对象的创建”问题,将创建和使用代码解耦,结构型设计模式主要解决“类或对象的组合或组装”问题,将不同功能代码解耦,行为型设计模式主要解决“类或对象之间的交互”问题,将不
小万哥 小万哥
1年前
C++ 多级继承与多重继承:代码组织与灵活性的平衡
C多级继承多级继承是一种面向对象编程(OOP)特性,允许一个类从多个基类继承属性和方法。它使代码更易于组织和维护,并促进代码重用。多级继承的语法在C中,使用:符号来指定继承关系。多级继承的语法如下:cclassDerivedClass:publ
小万哥 小万哥
10个月前
Kotlin 面向对象编程 (OOP) 基础:类、对象与继承详解
面向对象编程(OOP)是一种编程范式,它通过创建包含数据和方法的对象来组织代码。相较于过程式编程,OOP提供了更快更清晰的结构,有助于遵守DRY(Don&39;tRepeatYourself)原则,使代码更易于维护和扩展。在Kotlin中,类和对象是OOP的核心。类作为对象的模板,定义了对象的行为和状态;对象则是类的具体实例。例如,Car类可以定义汽车的品牌、型号等属性,以及如驾驶和刹车等功能。通过构造函数可以快速初始化对象的属性。此外,Kotlin支持继承机制,子类可以从父类继承属性和方法,促进代码重用。
迭代沙漏
迭代沙漏
Lv1
汉月垂乡泪,胡沙费马蹄。
文章
4
粉丝
0
获赞
0