02_组间协作模式

组件协作 模式

  • 现代软件工业化的结果, 框架与应用
  • 这几个体现最为明显

模板模式

对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

运行流程,框架是稳定的,但是具体功能是变化的。

晚绑定,先开发的依赖于后续实现。

定义:

定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。

稳定中有变化。

缺点: run()(模板流程)如果不稳定,则不应该用模板模式。都稳定,也没有必要使用设计模式了。

程序例子

Downloader
+Download(uri: string)
Template
- implement: Implement
- uri: string
+newTemplate(impl: Implement)
+Download(uri: string)
+save()
HTTPDownloader
- template: Template
+NewHTTPDownloader()
+download()
+save()
FTPDownloader
- template: Template
+NewFTPDownloader()
+download()
+save()
Implement
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()
}

IProgress
+DoProgress(value: float64)
ProgressBar
- progress: float64
FileSplitter
- path: string
- nums: int
- m_iprogress: IProgress
+split()
TextBox
- text: string
+getText()
MainForm
- txtFilePath: TextBox
- txtFileNumber: TextBox
- progress: ProgressBar
- IProgress
+DoProgress(value: float64)
+Button1_Click()

目前只有一个观察者,即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()
}

类图

1
*
1
1
1
2
IProgress
+DoProgress(value: float64)
ProgressBar
- progress: float64
FileSplitter
- path: string
- nums: int
- m_iprogress: []IProgress
+add_IProgress(iprogress: ...IProgress)
+remove_IProgress(iprogress: []IProgress)
+OnProgress(value: float64)
+split()
TextBox
- text: string
+getText()
MainForm
- txtFilePath: *TextBox
- txtFileNumber: *TextBox
- progress: *ProgressBar
+Button1_Click()
+DoProgress(value: float64)
Console
+DoProgress(value: float64)

书上类图,

在这里插入图片描述

总结

  • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
  • Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

都学点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值