组件协作 模式
- 现代软件工业化的结果, 框架与应用
- 这几个体现最为明显
模板模式
对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
运行流程,框架是稳定的,但是具体功能是变化的。
晚绑定,先开发的依赖于后续实现。
定义:
定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。
稳定中有变化。
缺点: run()
(模板流程)如果不稳定,则不应该用模板模式。都稳定,也没有必要使用设计模式了。
程序例子
package template
import "fmt"
// Download是一个个模板方法,由具体的步骤
type Downloader interface {
Download(uri string)
}
type template struct {
implement
uri string
}
type implement interface {
download()
save()
}
func newTemplate(impl implement) *template {
return &template{
implement: impl,
}
}
// Download方法的具体实现
func (t *template) Download(uri string) {
t.uri = uri
fmt.Print("prepare downloading\n")
t.implement.download()
t.implement.save()
fmt.Print("finish downloading\n")
}
// 默认实现
func (t *template) save() {
fmt.Print("default save\n")
}
// 具体的Http下载
type HTTPDownloader struct {
*template
}
// http下载器的构造器
func NewHTTPDownloader() Downloader {
downloader := &HTTPDownloader{}
template := newTemplate(downloader)
downloader.template = template
return downloader
}
func (d *HTTPDownloader) download() {
fmt.Printf("download %s via http\n", d.uri)
}
func (*HTTPDownloader) save() {
fmt.Printf("http save\n")
}
type FTPDownloader struct {
*template
}
func NewFTPDownloader() Downloader {
downloader := &FTPDownloader{}
template := newTemplate(downloader)
downloader.template = template
return downloader
}
func (d *FTPDownloader) download() {
fmt.Printf("download %s via ftp\n", d.uri)
}
总结
- Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
- 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
策略模式
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)
动机
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
例如,一个计算税率的程序,
package strategy
import "fmt"
const (
CN = iota
US
DE
)
type SalesOrder struct {
tax int
}
func (s *SalesOrder) CalculateTax() {
switch s.tax {
case CN:
//...
fmt.Println("CN")
case US:
//
fmt.Println("US")
case DE:
fmt.Println("DE")
default:
fmt.Println("Not support")
}
}
如果现在,我们要添加一个法国的税收计算方法,需要这样做:
package strategy
import "fmt"
const (
CN = iota
US
DE
FR
)
type SalesOrder struct {
tax int
}
func (s *SalesOrder) CalculateTax() {
switch s.tax {
case CN:
//...
fmt.Println("CN")
case US:
//
fmt.Println("US")
case DE:
fmt.Println("DE")
case FR:
fmt.Println("FR")
default:
fmt.Println("Not support")
}
}
这样看似解决了问题,但违背了开闭原则,对扩展开放,对修改封闭。修改了源码,维护更困难。
定义基类
package strategy
import "fmt"
type Context struct {
}
type TaxStrategy interface {
Calculate(context *Context) float64
}
然后跟分别为CN\US\DE实现计算方法
type CNTax struct {
TaxStrategy
}
func (tax *CNTax) Calculate(ctx *Context) float64 {
fmt.Println("CN Tax Calculate")
return 0
}
type USTax struct {
TaxStrategy
}
func (tax *USTax) Calculate(ctx *Context) float64 {
fmt.Println("US Tax Calculate")
return 0
}
type DETax struct {
TaxStrategy
}
func (tax *DETax) Calculate(ctx *Context) float64 {
fmt.Println("DE Tax Calculate")
return 0
}
使用,会初步设计到工厂模式,展示就理解为返回我们需要的实例
type StrategyFactory struct {
}
func (factor *StrategyFactory) NewStrategy() TaxStrategy {
//here are some implement
// 工厂模式
// 工厂决定返回哪个实例
return nil
}
type SalesOrder struct {
taxStrategy TaxStrategy
}
func (s *SalesOrder) New(StrategyFactory *StrategyFactory) *SalesOrder {
return &SalesOrder{
taxStrategy: StrategyFactory.NewStrategy(),
}
}
func (s *SalesOrder) CalculateTex() float64 {
context := &Context{} //上下文
//真正的实现
return s.taxStrategy.Calculate(context)
}
如果现在要添加法国,只需要添加一个新的实现
type FRTax struct {
TaxStrategy
}
func (tax *FRTax) Calculate(ctx *Context) float64 {
fmt.Println("FR Tax Calculate")
return 0
}
调用完全不需要该变,
package strategy
import "testing"
func TestCNTex(t *testing.T) {
var strategy StrategyFactory
order := NewSalesOrder(&strategy)
order.CalculateTex()
}
总结
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
- Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。(单例模式)
如果出现了大量的if else可用用策略模式。if else 是分而治之 。
如果if else绝对不变,不会继续扩展,则不用strategy。
观察者模式/Event模式
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
动机
- 通知依赖的对象,关系又不能太紧密。在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
实例
package observer
import "strconv"
type FileSplitter struct {
path string
nums int
}
func (s *FileSplitter) split() {
//do something
//split a big file to nums small files
}
type TextBox struct {
}
func (box *TextBox) getText() string {
return ""
}
type MainForm struct {
txtFilePath *TextBox
txtFileNumber *TextBox
}
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
splitter := FileSplitter{filePath, number}
splitter.split()
}
需要显示进度条,怎么办?
朴素想法,需要使用ProgressBar
,然后更新它,但是进度信息在split
里。可能需要在FileSpilter
中定义一个ProgressBar
于是得到如下解决方法
package observer
import "strconv"
type ProgressBar struct {
progress float64
}
type FileSplitter struct {
path string
nums int
psb *ProgressBar
}
func (s *FileSplitter) split() {
//do something
//split a big file to nums small files
for i := 0; i < s.nums; i++ {
if s.psb != nil {
s.psb.progress = float64((i + 1) / s.nums)
}
}
}
type TextBox struct {
}
func (box *TextBox) getText() string {
return ""
}
type MainForm struct {
txtFilePath *TextBox
txtFileNumber *TextBox
progress *ProgressBar
}
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
splitter := FileSplitter{filePath, number, main.progress}
splitter.split()
}
存在问题:
- 违背了依赖倒置原则,高层不应该依赖低层,抽象不依赖具体。
- 抽象依赖是实现细节了,在
FileSplitter
中依赖了具体的容易变化的ProgressBar
. - 单纯抽象类也不行,
- 我们核心是需要有一个通知。
解决方法:
1 实现一个抽象的IProgress接口
type IProgress interface {
DoProgress(value float64)
}
2 具体的更新进度的方法
type FileSplitter struct {
path string
nums int
m_iprogress IProgress//这是最关键的
}
func (s *FileSplitter) split() {
//do something
//split a big file to nums small files
for i := 0; i < s.nums; i++ {
if s.m_iprogress != nil {
s.m_iprogress.DoProgress(float64((i + 1) / s.nums))
}
}
}
3 实现具体的方法
type MainForm struct {
txtFilePath *TextBox
txtFileNumber *TextBox
progress *ProgressBar
IProgress
}
func (main *MainForm) DoProgress(value float64) {
main.progress.progress = value
}
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
splitter := FileSplitter{filePath, number, main}
splitter.split()
}
目前只有一个观察者,即progress bar。如果我们要支持多个观察者呢?
比如:多一个控制台的程序,
type Console struct {
IProgress
}
func (c *Console) DoProgress(value float64) {
fmt.Printf(".")
}
那么此时,我们只能传递要么,main中的ProgressBar,要么console
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
console := &Console{}
splitter := FileSplitter{filePath, number, console}
splitter.split()
}
也就是对于一个数据,只有一个观察者。
我们可用添加一组观察者:
type FileSplitter struct {
path string
nums int
m_iprogress []IProgress //多个观察者
}
完整代码
package observer
import (
"fmt"
"strconv"
"time"
)
func removeDuplicates(A []IProgress, B []IProgress) []IProgress {
result := []IProgress{}
// 创建一个 map 用于记录切片 B 中的元素
bMap := make(map[IProgress]bool)
for _, value := range B {
bMap[value] = true
}
// 遍历切片 A,如果元素不在切片 B 中,则将其添加到结果切片中
for _, value := range A {
if !bMap[value] {
result = append(result, value)
}
}
return result
}
type IProgress interface {
DoProgress(value float64)
}
type ProgressBar struct {
progress float64
}
type FileSplitter struct {
path string
nums int
m_iprogress []IProgress //多个观察者
}
func (s *FileSplitter) add_IProgress(iprogress ...IProgress) {
s.m_iprogress = append(s.m_iprogress, iprogress...)
}
func (s *FileSplitter) remove_IProgress(iprogress []IProgress) {
s.m_iprogress = removeDuplicates(s.m_iprogress, iprogress)
}
// 通知所有观察者
func (s *FileSplitter) OnProgress(value float64) {
for _, progress := range s.m_iprogress {
go progress.DoProgress(value)
}
}
func (s *FileSplitter) split() {
//do something
//split a big file to nums small files
for i := 0; i < s.nums; i++ {
time.Sleep(1 * time.Second)
s.OnProgress(float64(i+1) / float64(s.nums))
}
}
type TextBox struct {
text string
}
func (box *TextBox) getText() string {
return box.text
}
type MainForm struct {
txtFilePath *TextBox
txtFileNumber *TextBox
progress *ProgressBar
IProgress
}
func (main *MainForm) DoProgress(value float64) {
main.progress.progress = value
//我们可用在这里拿到真正的进度值
fmt.Printf("progress : %f\n", value)
}
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
console := &Console{}
splitter := FileSplitter{filePath, number, nil}
splitter.add_IProgress(main)
splitter.add_IProgress(console)
splitter.split()
}
type Console struct {
IProgress
}
func (c *Console) DoProgress(value float64) {
fmt.Printf(".")
}
测试
package observer
import (
"testing"
)
func TestMainForm(t *testing.T) {
main := &MainForm{
txtFilePath: &TextBox{"Hello.txt"},
txtFileNumber: &TextBox{"5"},
progress: &ProgressBar{
progress: 0,
},
}
main.Button1_Click()
}
类图
书上类图,
总结
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。