生成器模式-代码的艺术系列(一)

2021-06-24 11:20:16 +08:00
 xiaoxuz

前言

看代码"文档",学设计模式

网上讲解设计模式都是基于现实生活场景举例,但作为 coder,还需要有将现实生活场景代码实现场景的转化思维,所以我认为,了解设计模式简单,实践到对应代码场景中有难度。

so 我们的代码的艺术系列会以还原 coding 现场的方式,讲诉设计模式


生成器模式

来看概念:

生成器模式是一种创建型设计模式,也叫建造者模式。它把对象的创建步骤抽象成生成器,并且可以通过指导类(director)对所有生成步骤的先后顺序进行控制。客户端使用指导类并传入相应的生成器,通过指导类的接口便可以得到相应的对象。

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations. 将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示

概括的说:有些对象的创建流程是一样的,但是因为自身特性的不同,所以在创建他们的时候需要将创建过程个性化的属性分离出来。

基本看不懂啥意思,! 继续看结构~

来看结构:

  1. 生成器 ( Builder ) 接口声明在所有类型生成器中通用的产品构造步骤。
  2. 具体生成器 ( Concrete Builders ) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
  3. 产品 ( Products ) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
  4. 主管 ( Director ) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
  5. 客户端 ( Client ) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。


实战学习

概念性描述你懂了么?不懂就对了,程序员先理解概念不如直接上代码来的刺激。

这里贯彻下我们前言中的宗旨:

  1. 不要生活场景的举例,要产品需求的案例。
  2. 不要纯图文的描述,要可阅读的代码。

上实战~

产品需求

没有产品思维的程序员不是好销售,此需求纯属虚构

pm 要在自家的电商网站电脑产品垂类下增加报价功能。

PRD 描述当用户进入 mac 品牌的详情页,他可以选择 I7 CPU,500G 内存,1T 磁盘的配置,查看报价。

不同品牌的部件价格不相同,并且不同品牌在同一时刻有不同的优惠折扣。

需求目标: 实时计算出用户选择的电脑配置折后价钱。

需求收益: 提高下单率 50%

技术文档

技术脑爆时刻到了!

乍一听感觉很简单,没什么复杂逻辑。

其实真的很简单。但是问题是不同品牌的部件配置价格不同,而且不同品牌的折扣也是不同的。用户选择了 A,B,C-Z 一坨配置,我的代码要这么写么?

var cpuPrice map[string]float {
  xxx : 100,
  xxxx : 200
}
// 电脑有 N 个部件,我的函数入参就要有 N 个么?
// 这个函数谁敢用?
getMaxPrice(type= '', cpu='',mem='',ram='',disk=''...一堆配置){
	if type == 'mac' {
    price := 0
    if(cpu == '') {
      cpu="默认配置"
    }
    price +=  cpuPrice[cpu]
    if(mem == '') {
      mem="默认配置"
    }
    price +=  memPrice[mem]
    ...
	} elseif (type == 'huawei') {
		...
	}
}

这样看这坨代码的代码量绝对高,并且大部分是重复代码,而且当电脑配置越来越多,getMaxPrice 函数入参也跟着变多,参数顺序谁能保证?有的产生是必填有的是非必填,怎么帮助必填的没有被漏掉?

这样的代码时间久了,逻辑看着很简单,但是没人敢用吧。

怎么办呢?

使用生成器模式来解决是不是好一点,每个部件作为一个生成步骤,每次执行一个步骤即添加一个部件配置,最终生成一个完整的电脑报价,并且设置部件、获取折扣、计数报价这些步骤本身是有序的,是可以通过生成器模式中的 Director 小干部来统一操作的。

好,来看代码吧!

代码"文档"

Tips: 代码 + 注释。

自我要求:注释覆盖率 > 60%

1.先定义一个电脑报价的配置总类,即我们要生成的产品:Computer

package builder

// 产品: 这个是我们的目标,computer 要有这些配置
// computer 可以理解成我们要制作一个什么产品
// 结构体字段 可以理解为我们要做的产品都要哪些配置,对应上文 生成函数的 N 多个入参
type Computer struct {
	name       string    // 电脑类型 比如 mac/华为
	cpuModel   CPU_MODEL // cpu 型号
	gpuModel   GPU_MODEL // gpu 型号
	memorySize MEM_SIZE  // 内存大小
	diskSize   DISK_SIZE // 磁盘大小

	discount float64 // 折扣
	price    float64 // 整体报价
}

2.再定义一个电脑生成的步骤规范接口

package builder

// 生成器接口: 产品的生成器接口
// 可以理解为这个产品生成 必须要有哪些具体的步骤和行为, 后面每一个抽象的产品生成对象都要继承这个生成器接口
type builder interface {
	setCpuModel(CPU_MODEL)  // 设置 cpu 型号
	setGpuModel(GPU_MODEL)  // 设置 gpu 型号
	setMemorySize(MEM_SIZE) // 设置 内存型号
	setDiskSize(DISK_SIZE)  // 设置磁盘型号

	setDiscount()    // 设置折扣粒度, 这个折扣粒度是系统内置的,不需要客户端设置也就是说此功能不是给前台用户询价时自定义的。
	calculatePrice() // 计算报价

  getComputer() *Computer // 给主管(director)使用
}

3.开始定义各个电脑品牌抽象生成器

先看 Mac 的

package builder

import (
	"time"
)

// 抽象的产品生成器
// 可以理解为 computer 这个产品中某一类型产品的生成器
// 抽象生成器即包含了产品(computer)的所有配置,也继承了 builder 公共生成器的所有生成步骤
type MacComputerBuilder struct {
	c *Computer
}

// 实力化一个 Mac 电脑报价
func NewMacComputerBuilder() builder {
	return &MacComputerBuilder{
		c: &Computer{name: "mac"},
	}
}

// 返回*Computer
func (mc *MacComputerBuilder) getComputer() *Computer {
	return mc.c
}

// 设置 CPU 型号
// 设置配置的时候要判断,如果客户端已经配置了,那么跳过
// 这块是因为 director 会在最后编译的时候统一整体执行一遍,防止客户端漏掉配置,走默认配置
func (mc *MacComputerBuilder) setCpuModel(m CPU_MODEL) {
	// demo
	if mc.c.cpuModel != "" {
		return
	}
	if price, ok := partsCpuPriceMap[m]; ok {
		mc.c.cpuModel = m
		mc.c.price += price
	} else {
		mc.c.cpuModel = MAC_CPU_I5 // 此为 mac 电脑默认 cpu 配置
		mc.c.price += partsCpuPriceMap[MAC_CPU_I5]
	}
}

// 设置 GPU 型号
// 设置配置的时候要判断,如果客户端已经配置了,那么跳过
// 这块是因为 director 会在最后编译的时候统一整体执行一遍,防止客户端漏掉配置,走默认配置
func (mc *MacComputerBuilder) setGpuModel(m GPU_MODEL) {
	// demo
	if mc.c.gpuModel != "" {
		return
	}
	if price, ok := partsGpuPriceMap[m]; ok {
		mc.c.gpuModel = m
		mc.c.price += price
	} else {
		mc.c.gpuModel = MAC_GPU_NVIDIA // 此为 mac 电脑默认 gpu 配置
		mc.c.price += partsGpuPriceMap[MAC_GPU_NVIDIA]
	}
}

// 设置内存大小
// 设置配置的时候要判断,如果客户端已经配置了,那么跳过
// 这块是因为 director 会在最后编译的时候统一整体执行一遍,防止客户端漏掉配置,走默认配置
func (mc *MacComputerBuilder) setMemorySize(s MEM_SIZE) {
	// demo
	if mc.c.memorySize != "" {
		return
	}
	if price, ok := partsMemPriceMap[s]; ok {
		mc.c.memorySize = s
		mc.c.price += price
	} else {
		mc.c.memorySize = MAC_MEM_8G // 此为 mac 电脑默认 内存 配置
		mc.c.price += partsMemPriceMap[MAC_MEM_8G]
	}
}

// 设置 磁盘大小
// 设置配置的时候要判断,如果客户端已经配置了,那么跳过
// 这块是因为 director 会在最后编译的时候统一整体执行一遍,防止客户端漏掉配置,走默认配置
func (mc *MacComputerBuilder) setDiskSize(s DISK_SIZE) {
	// demo
	if mc.c.diskSize != "" {
		return
	}
	if price, ok := partsDiskPriceMap[s]; ok {
		mc.c.diskSize = s
		mc.c.price += price
	} else {
		mc.c.diskSize = MAC_DISK_500G // 此为 mac 电脑默认 磁盘 配置
		mc.c.price += partsDiskPriceMap[MAC_DISK_500G]
	}
}

// 设置折扣
// 不同产品策略不一样
// 此操作为内置操作,不需要外部设置
func (mc *MacComputerBuilder) setDiscount() {
	// 2021-06-24 00:17:33
	// 如果大于这个时间,那么 mac 电脑整体打 5 折
	// 否则 整体打 8 折
	if time.Now().Unix() > 1624465043 {
		mc.c.discount = 0.5
	} else {
		mc.c.discount = 0.8
	}
}

// 计数价格
// 注意看,这块就是需要时序的地方,需要先 setDiscount 才能进行报价
// 所以 需要通过 指挥者来统一进行构建,保证各个行为执行顺序
func (mc *MacComputerBuilder) calculatePrice() {
	mc.c.price = (mc.c.price * mc.c.discount)
}

在看一个 huawei 的。

package builder

import "C"

// 抽象的产品生成器
// 可以理解为 computer 这个产品中某一类型产品的生成器
// 抽象生成器即包含了产品(computer)的所有配置,也继承了 builder 公共生成器的所有生成步骤
type HuaweiComputerBuilder struct {
	c *Computer
}

func NewHuaweiComputerBuilder() builder {
	return &HuaweiComputerBuilder{
		c: &Computer{name: "huawei"},
	}
}

func (hc *HuaweiComputerBuilder) getComputer() *Computer {
	return hc.c
}

/**
 * 以下设置各个配置方法和 Mac 逻辑一样,当然也可以自定义策略,不过 demo 就这样了,保证篇幅,所以就不写了
 */
// 设置 CPU 型号
func (hc *HuaweiComputerBuilder) setCpuModel(m CPU_MODEL) {}
// 设置 GPU 型号
func (hc *HuaweiComputerBuilder) setGpuModel(m GPU_MODEL) {}
// 设置内存大小
func (hc *HuaweiComputerBuilder) setMemorySize(s MEM_SIZE) {}
// 设置 磁盘大小
func (hc *HuaweiComputerBuilder) setDiskSize(s DISK_SIZE) {}


// 设置优惠折扣,这块是内部逻辑,不需要外部调用方定义,而且不同产品策略不一样
func (hc *HuaweiComputerBuilder) setDiscount() {
	// 华为机器不打折,国产赞。 这块就是和 mac 差异化的地方
	hc.c.discount = 1
}

// 既然华为不打折,那么直接输出就好了
func (hc *HuaweiComputerBuilder) calculatePrice() {
}

看到区别了吧,两个品牌生成器的优惠策略不同计数价格方法不同,但是统一生成步骤一样,所以需要主管来统一调度执行

来看主管director

package builder

// director 主管,负责整体 build 执行
// 可以理解为总指挥,他来负责计算报价
type director struct {
	builder builder
}

// 实例化一个主管
func NewDirector(b builder) *director {
	return &director{
		builder: b,
	}
}

// 手动重置主管,方便进行多次不同产品生成构建
func (d *director) resetBuilder(b builder) {
	d.builder = b
}

// 执行编译生成,这块就是要严格统一管理编译的步骤和顺序
// 当前这个 demo , 因为时计算报价的例子而不是生成电脑配置的例子,所以前置的那些 setXXX 都在客户端自定义执行了
// 但是有可能前台用户没有选择某些配置,所以需要主管统一兜底
// 1. 兜底每个电脑配置
// 2. 根据当前时间选择折扣粒度
// 3. 计算报价
func (d *director) buildComputer() *Computer {
	// 第一步,兜底每一个电脑配置
	d.builder.setCpuModel(DIRECTOR_CHECK_PARAMS)
	d.builder.setGpuModel(DIRECTOR_CHECK_PARAMS)
	d.builder.setMemorySize(DIRECTOR_CHECK_PARAMS)
	d.builder.setDiskSize(DIRECTOR_CHECK_PARAMS)

	// 第二步设置折扣
	d.builder.setDiscount()
	// 第三步 计算报价
	d.builder.calculatePrice()

	// 返回产品对象
	return d.builder.getComputer()
}

到这块是不是差不多看懂了?最后我们看下客户端是如何调用实现的:

package builder

import "fmt"

// 客户端询问报价
// 即用户在前台页面选择了 mac 电脑
// CPU i7
// GPU xxx
func getPrice() {
	// 先实例化抽象生成器对象,即 mac 电脑
	mcb := NewMacComputerBuilder()
	// 设置我想询问的配置
	mcb.setCpuModel(MAC_CPU_I7)
	mcb.setGpuModel(MAC_GPU_NVIDIA)
	mcb.setMemorySize(MAC_MEM_16G)
	// 磁盘我不选了,用默认的
	//mcb.setDiskSize()

	// 然后实例化一个主管,来准备生成报价
	d := NewDirector(mcb)
	// 执行编译,生成最终产品
	product := d.buildComputer()

	// ok 搞定了,我们可以看看最终这个产品的配置和报价
	fmt.Printf("current computer name: %s\n", product.name)
	fmt.Printf("choose config cpuModel: %s\n", product.cpuModel)
	fmt.Printf("choose config gpuModel: %s\n", product.gpuModel)
	fmt.Printf("choose config memorySize: %s\n", product.memorySize)
	fmt.Printf("choose config diskSize: %s\n", product.diskSize)

	fmt.Printf("give you discount: %f\n", product.discount)
	fmt.Printf("final offer: %f\n", product.price)

	fmt.Printf("---------------询问下一个电脑---------------\n")
	// 下面 我们再生成一个华为的电脑报价
	hwcb := NewHuaweiComputerBuilder()
	hwcb.setCpuModel(HW_CPU_I7)
	hwcb.setGpuModel(HW_GPU_ATI)
	hwcb.setMemorySize(HW_MEM_16G)
	hwcb.setDiskSize(HW_DISK_1T)
	d.resetBuilder(hwcb)
	// 执行编译,生成最终产品
	product2 := d.buildComputer()

	// ok 搞定了,我们可以看看最终这个产品的配置和报价
	fmt.Printf("current computer name: %s\n", product2.name)
	fmt.Printf("choose config cpuModel: %s\n", product2.cpuModel)
	fmt.Printf("choose config gpuModel: %s\n", product2.gpuModel)
	fmt.Printf("choose config memorySize: %s\n", product2.memorySize)
	fmt.Printf("choose config diskSize: %s\n", product2.diskSize)

	fmt.Printf("give you discount: %f\n", product2.discount)
	fmt.Printf("final offer: %f\n", product2.price)

}

上线效果

=== RUN   TestGetPrice
current computer name: mac
choose config cpuModel: maci7
choose config gpuModel: mac-NVIDIA
choose config memorySize: mac-16g
choose config diskSize: mac-500g
give you discount: 0.500000
final offer: 600.000000

---------------询问下一个电脑---------------

current computer name: huawei
choose config cpuModel: hwi7
choose config gpuModel: hw-ATI
choose config memorySize: hw-16g
choose config diskSize: hw-1t
give you discount: 1.000000
final offer: 2800.000000
--- PASS: TestGetPrice (0.00s)
PASS

还是符合预期的!

Demo 源码https://github.com/xiaoxuz/design-pattern/tree/main/create/builder

生成器优缺点

思考

认识我们的职业,不是码农,是软件工程师!

收工

打完收工,感谢阅读!

** [点击] 关注再看,您的关注是我前进的动力~!**

2310 次点击
所在节点    程序员
38 条回复
no1xsyzy
2021-06-24 19:37:22 +08:00
> GoF 借鉴的是一个叫 Christopher Alexander 的建筑师的做法。Alexander 给一些建筑学里的“设计模式”起了名字,试图让建筑师们有一些“共同语言”。可惜的是,Alexander 后来自己都承认,他的实验失败了。因为这些固定的模式,并没能有效地传递精髓的知识,没能让新手成长为出色的建筑师。
ChoateYao
2021-06-24 20:11:30 +08:00
你拿着
https://refactoringguru.cn/design-patterns/template-method
这本书的图,不标记一下来源?
xiaoxuz
2021-06-24 20:30:52 +08:00
@lesismal

@sheepzh
@no1xsyzy
行了 兄弟们,我服了, 你们赢了,都是战争贩子!
写文章的意图是介绍设计模式,重点是设计模式。
你们却开始说别拿设计模式搞 go,真无语!
我要是只会 go 还不能发文章咯?
我要是只用 go 发文章就成了 那个话密的老中医口中的『你写你的,我认为不好的我就提出来,不让某个观点独霸市场。』 咯?

行,都是茬的!

对了还有那个话密的老中医,感谢在评论中带个私活,活儿不错!! 君 x 子 r 之举!
xiaoxuz
2021-06-24 22:15:58 +08:00
@sheepzh
@lesismal

这就好比买了份盒饭去路边吃,忘记拿筷子了,只能拿一个泡面叉子吃。那怎么地让别人看见了就认为是崇洋媚外了么?
重点是吃饭啊,兄弟~,你管我用啥吃呢?
文章也是,重点是讲述生成器模式啊,兄弟~。讲的是生成器模式,又不是讲计算机语言语法!

真的是,看球不看球,光去数人头。

1.全文上下 我也没有 引导读者用设计模式去搞 go 的字样吧?

2.我也不是标题党,标题也没有说写成 “用 golang 实现生成器模式” 吧?

我 tm 写文章的时候压根就没想过, 文章内容和案例 demo 的技术栈是否冲突的问题?
lesismal
2021-06-24 22:18:18 +08:00
@xiaoxuz

上一楼语气还看着正常些的,说翻脸就翻脸,淡定点。

要是真侃技术,咱就聊技术的,别扯远了,我这也一直聊的技术,别扯什么中医不中医的,你看看#18 楼你几句回复,看上去认真在回复似的,但是都是在把话题扯远了、没在正题上。

既然用 go 举例子,还不能让别人说了?而且设计模式本身的很多臃肿,并不限于 go,我从 go 开喷只是因为你用了这么臃肿的 go 代码做示范。你要说用 go 做示范但是是为了设计模式、跟 go 没丝毫关系和影响,这逻辑就像莫文蔚穿个啥乳 h 品牌然后强调自己只是打广告跟乳 h 没关系类似的道理,然后还不允许别人喷

你要是没用 go 举例子,如果我点进来看一眼是 java 我转身就走了,头都不回,因为个人不喜欢 java 觉得 java 不值得我浪费时间、毫无兴趣,但是我也不随便就喷 java,因为不只是一个人的 java 臃肿

还有不是要安利什么的说法,我在之前楼里也说过了,你都写文章这么正向推介了,还解释不是安利作何?没必要的,安利了就安利了,你认为对、拿出来大家聊,越辩越明就完事了。逻辑搞清楚点,别聊技术又不正面应对又不承认这不承认那的,也别给别人扣帽子什么中医

还有,关于私货这个,如果只是简单提起+一个链接,你说 xr 之举也就算了。但即使私货,我也带上了大段的内容,希望能有人看到、从中吸收一些知识点,对于宣传知识贡献开源而言、没什么不好意思的,做这项目也不是为了钱,到了职业年龄末段了,只是想给社区一些回馈、给自己留些念想,如果幸运能多来点人到仓库看看、用用甚至再来点 issue 、pr,那更开心了。但是说句失望的,就这么段内容,能 get 到点的人非常少,一是别人也未必认真读到我这段内容了,二是别人之前不知道这个问题相关的这么些信息,三是确实是有些难度。你反手看看其他一些异步网络库,只做自家业务比如游戏的 tcp 协议异步库也就算了,但是对于 http/websocket/tls,数千 star 那些,基本没有支持的,都是玩具。

最后,既然写文章发出来了,就要接受你的受众的反馈,既有正向的又有负向的,零差评是几乎不可能的事情,不要那么在意面子,太在意面子,即使能在面子驱动下搞好技术也气大伤身、没必要。如果实在在意,可以先内修、等心态更平和的年纪再把文章发出来
lesismal
2021-06-24 22:31:51 +08:00
@xiaoxuz
如果实在觉得用 go 举例子不影响看球,那多举点例子:

一把杀过人的普通菜刀赠送给邻居,邻居不会愿意要,觉得不吉利,是不是应该强调菜刀只是道具跟杀人没关系?
赵薇日本军旗,军旗只是拍时尚照道具而已,赵薇不应该被喷,军旗也没毛病?

如果道具在一切场景都可以解释成只是道具、不影响用来做什么,那这世界就不会存在忌讳了。

天不早了,晚安
xiaoxuz
2021-06-24 23:06:42 +08:00
@lesismal
兄弟还是你年纪大,你稳重,记得最开始你提的问题我也安静的思考,并且有找相关文章才给你的回复,锤子的例子就是设计模式相关文章 https://refactoringguru.cn/design-patterns 提出的:

但是可能思想觉悟没你高吧,或者就是你理解有什么误区。

“如果你只有一把铁锤, 那么任何东西看上去都像是钉子。”
这个例子的意思是:设计模式不当的使用会给初学模式的人们带来困扰: 在学习了某个模式后, 他们会在所有地方使用该模式, 即便是在较为简单的代码也能胜任的地方也是如此。

你直接评论下面这样:
“所以手持臃肿的铁锤的人就是那么喜欢臃肿的钉子”
“如果能只在自家臃肿就好了,放过那些新手,免得把 go 社区更多小白也整成臃肿的了。”
“另外,中了臃肿的毒的人,可能根本不知道自己中毒了。”

-- 这可能就是你所谓的平和心态,还有可能就是你说的“不要那么在意面子,太在意面子”,或者可能就是你理解的误区导致你直接开喷吧!

“最后,既然写文章发出来了,就要接受你的受众的反馈,既有正向的又有负向的,零差评是几乎不可能的事情”
-- 还有,这个我很认同啊,楼上很多人说案例比喻不准确,我接受了啊,人家是有理有据的评价文章内容的,没毛病,技术的碰撞才能产出更好的技术。但是你这确实有点偷换概念, 不说主题了吧,就事儿论事,你玩什么边车呢?

还有上面的举例属实吓人:
“一把杀过人的普通菜刀赠送给邻居,邻居不会愿意要,觉得不吉利,是不是应该强调菜刀只是道具跟杀人没关系?”
这种例子感觉不太妥当,有失风度了!

还有说你是“老中医”,确实是我考虑不周,但是你张口闭口的“中了臃肿的毒” “自己中毒”,不得不让我去遐想你的专业方向和思维逻辑了。

行了,兄弟,我写我的设计模式,你聊你的 go 和设计模式冲突的问题吧,这个撕逼我感觉再聊下去就有点滚刀肉了。
有机会还想讨论可以加个微信探讨下技术问题,
xiaoxuz
2021-06-24 23:35:57 +08:00
@lesismal 或者可以关注文章底部公众号,不喷不怼聊一聊也未尝不可!
lesismal
2021-06-25 00:50:41 +08:00
以上所有打字直到现在,我确实心里没什么波澜,算是平和的。
中毒这种说法是因为早年也跟不少 cpp 语言党聊过太多了,cpp 中毒太深的人过度玩弄语法语义和各种特性,cpp 里也有不少乐于搞设计模式的,一个单例能搞出 6 种还是 8 种实现。然而实际的架构设计、工程设计、算法设计、结构设计却不到位,导致日常夸夸其谈,项目问题不断。
这些多年撕得多得出的结论,而且也因为撕得多,所以早就平和了。

就像无法叫醒一个装睡的人,执着于语言特性、设计模式的好多人,也难回头,所以我称之为中毒。
经济学上讲,是因为“沉没成本效应”。在程序员这种职业上体现出来就是:自己花了不少功夫在某些知识上,舍不得承认这些知识的无用,并且继续对它学习研究,从而遮了眼、阻碍了自己的提升。
就比如设计模式吧,@no1xsyzy 见识广博,#21 里已经提到过了,设计模式之父自己都承认实际效果并不那么好。你再掰开了揉碎了仔细思考,真正有用的一些设计模式,比如观察者 /发布订阅,这种总结出来确实不错,但是大多数的设计模式真的未必有用,反倒是误以为宝贝的人,反复琢磨把玩、浪费了很多时间。比如单例模式,除了 java 这种一切皆类、必须通过实现单例或者直接 static 变量(不方法不优雅)才能使用这个公共变量的方式,其他语言基本都是一个公共变量就完事了,最多再加个显示的初始化或者作为函数返回值,让调用的方式看上去有一点虚伪的逼格而已。再比如工厂系列,最初知道这竟然是一种设计模式的时候,我都不好意思笑,多自然的一个事情,构造函数写舒服点就是工厂系列,非要整个单独的 createXX 之类的也行,设计模式之父自己都承认了一些事(我没去考证真假,但从设计模式实际的效用上讲,我觉得他应该承认过),我们大众何苦为难、作践自己呢。。。

设计模式和 go,我这么多认真回复,我觉得是“买椟还珠”,你作为卖家拿设计模式当个珠,用 go 的 demo 的盒子把它装里面了,而我是为了 go 这个盒子而来,真没 care 过设计模式

甚至于面向对象,大概 1996 年 cpp 标准委员会那帮老头子们开会就在聊“面向对象被社区滥用了呀”

再说点设计模式和面向对象的问题。社区已有的积累,可以继续吃老本,比如企业级、电商之类的,尤其是 java
但是良好的 OO 抽象体系也好、易扩展也好,在面对高速发展、快速迭代的产品和需求面前都不那么好使:
面向对象的鸭嘴兽问题,使得在很难预期大部分未来需求前提下,无法完美抽象、只能做到当下的抽象、无法做到未来的易扩展,设计模式同样,那些以为现在这样有哪些好处,未来可以怎样易于修改的好处,当不确定的未来真正到来的时候,很难发挥出其预期的优势,反倒可能因为早期的臃肿设计导致后续的修改难度更大。
真正有效的方案是进行阶段性重构。如果团队业务更新太快、疲于奔命,阶段性重构的频率就要降低、周期就要拉长,就得等到长时间日积月累后毕其功于一役。
今天为了以后易扩展做了很多更复杂的设计,明天可能因为非预期内的变化导致修改难度更大。所以像 go 这种不重语法糖特性和模式,反而专注于让事情简单化的语言,却成了工程性更好的选择

你应该能够从 go 语言本身感受到并且再深入思考一些:为什么 go 不天然面向对象?也不重设计模式?也不在意那些花哨的语法糖?
go 已经是站在那些以往优秀语言的巨人肩膀上,如果那些东西真的那么好,难道 c 爹和 rob pikez 这些老头子是老糊涂了?——这帮老头子们真的是老中医,切脉稳准狠,开出了 go 这副方子,让我一副药下肚,码欲大增、秀发得保,虽老矣,为心所愿之代码,尚可稍废寝食

然后我的疑问来了,既然用 go 了,你的意思好像又说你只写 go,那是谁带你跑偏去研习设计模式的?
把你引入歧途的人,是人性的扭曲还是道德的沦丧。。。你得喷他啊顺便把他也带回正轨。。。
akira
2021-06-25 01:51:44 +08:00
布教之难。。
xiaoxuz
2021-06-25 08:31:13 +08:00
@lesismal 我的天啊,职业末端都是这样偷换概念,转移话题的么?

这么优秀为什么一上来就误解我的回复并且死不认账一直在怼呢?自省一下好不好呢?

“既然用 go 了,你的意思好像又说你只写 go”
-- 这么优秀为什么理解能力这么低呢? 想要开喷最起码也得先理解别人的回复是啥意思吧?

“把你引入歧途的人,是人性的扭曲还是道德的沦丧。。。”
-- 感觉你这种偷换概念,误解后死不认账,并且站在职业末端一副大佬的模样。 才是没人性,缺道德的提现吧。。。

真的,真的忍受不了这种,误解别人意思之后 还孜孜不倦一会开怼的人。岁数再大也不知道活到?身上去了?

你喜欢怼,巧了,我抗怼。关注下时效性,咱们可以加个微信探讨一下。
xiaoxuz
2021-06-25 08:35:40 +08:00
@lesismal 也别拿什么各种原因不交换微信, 你要想喷就加个微信好好聊聊,那个东西是实时通信。
真的,事情很多不能一直关注 V 站回复,真的没时间陪你这种职业末端大佬聊天,你没多少时间也不能浪费我们的时间吧。
no1xsyzy
2021-06-25 09:51:47 +08:00
@xiaoxuz 差不多得了弟弟,我基本不用 golang,目前是 Python + Rust 的状态

我看来,任何语言都不要设计模式,设计模式本身就是垃圾、八股文。
对了,怕你不知道提一句,我引文里的 GoF 是设计模式一书的作者,跟 golang 没关系。

顺便,我就看了眼你主题里的例子,一眼就看出来
这是缺乏 pattern match 和 keyword argument
Python 有 keyword argument,直接把所有参数强制 kwarg 就行了
Rust 其实应该用 trait 去抽象,不过也可以写成 Enum 然后 match
当然不是说真没有 builder,很多 builder 是性能考虑,比如 string builder 效率比 string + string 效率高;但是,同一个抽象也可以做得更人性化,比如 string builder 效率持平但抽象不如 StringIO
正因为 Java 缺乏很多有用的东西,导致它平白无故地搞出这些八股文出来。

还 TM 微信,被微信搞傻了吧。
正经人谁用 IM 啊?你用吗?你用啊!那没事了!.gif
您觉得浪费时间,可以放着不回。参考: 《铜币到底是什么》 https://www.v2ex.com/help/currency
no1xsyzy
2021-06-25 10:10:28 +08:00
@xiaoxuz 迫于我已经自称是个比喻大师,我要订正下你的比喻
> 这就好比买了份盒饭去路边吃,忘记拿筷子了,只能拿一个泡面叉子吃。那怎么地让别人看见了就认为是崇洋媚外了么?
不是,我看到你明明拿了筷子却「好像没看见」,丢到一边用铁锤吃饭,一边吃还一边吹铁锤吃饭有多方便。我不觉得你崇洋媚外,我只是有些担心你突然抡起铁锤把自己的牙给敲碎,提醒一下你锤子不是拿来吃饭的。没想到你回

行了 兄弟们,我服了, 你们赢了,都是战争贩子!
写文章的意图是介绍锤子,重点是锤子。
你们却开始说别拿锤子吃炒饭,真无语!
我要是只会吃炒饭还不能发文章咯?
我要是只用吃炒饭发文章就……

我现在 #31 又跟你指了一下筷子长什么样,你现在看到你的筷子了吗?

@lesismal #29 设计模式之父是 GoF,他们并不承认这一策略的失败,并且似乎还在宣传?
你说 G.Alexander ?我盲猜他会认为你(称他为设计模式之父)是在侮辱他。
no1xsyzy
2021-06-25 10:10:52 +08:00
@no1xsyzy s/#31/#33/
no1xsyzy
2021-06-25 10:15:11 +08:00
多的不想说,PL 方面王垠的看法基本上是可以照着丢给新手的,虽然里面(可能是一时激动)说错了些术语,你能纠出错的时候你就彻底精通了。
http://www.yinwang.org/blog-cn/2013/03/07/design-patterns
lesismal
2021-06-25 11:50:55 +08:00
@no1xsyzy #34
设计模式之父是 GoF,他们并不承认这一策略的失败,并且似乎还在宣传?
你说 G.Alexander ?我盲猜他会认为你(称他为设计模式之父)是在侮辱他。
——按你说的这个 Gof 对 G.Alexander 的借鉴关系,我觉得 G.Alexander 才算是之父,Gof 这种坑害社区的存在,更加不配之父的名头
建筑和软件最大的共同点都是工程属性,但设计模式在软件领域的发挥威力比在建筑领域的发挥威力更加困难。
专业的建筑团队,建筑施工前画图纸、数学物理的公式计算、材料的实验、还有安全等各种流程的把控,要比软件工程规范得多,因为那都是人命关天的事。并且建筑建造完成后,日后建筑主体也不会大改。软件工程就不一样了,除了成型的领域,更新迭代太快了,而且 if else 的分之太多,多数产品出了事故也不是什么闹出人命的特大责任事故。
所以,在建筑领域这种相对更容易发挥设计优势的场景,人家的之父都承认实验失败了,软件领域却还嗨得飞起,只能说社区里有伪布道师们的市场,就像保健品、传销一样。

楼主根本就不是一个聊法,逻辑性太差,不跟他这浪费时间了
sheepzh
2021-06-25 13:16:28 +08:00
把代码本身当做面向需求和后续开发维护人员的一个产品。自然就应该知道什么场景用什么语言,什么框架,什么设计思想,到底是在服务级组件级还是代码级进行设计了。

还是认同 @lesismal #29 所说的“买椟还珠”的说法

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/785494

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX