设计模式之选项模式(C#+Go版)
简介
最近,我在学习和研究Go语言的时候突然发现了一个选项模式,为什么说是突然发现,因为我看的两本关于设计模式的书籍,都没有提及过选项模式(也可能是我孤陋寡闻?),所以,我研究了一下,并决定写一篇博客跟大家分享一下。
代码的坏味道
顾名思义,选项模式,就是为了更好地配置选项而产生的,我们先来看看普通的代码,如何来配置一个选项类的。
我们先定一个Option类,用来表示选项。
public class Option
{
public string Op1 { get; set; }
public string Op2 { get; set; }
public int Op3 { get; set; }
public int Op4 { get; set; }
}
这里就简单定义几个属性,两个string类型的,两个int类型的。接着,我们看一下,普通方法如何去构造一个Option。
public static Option CreateOption(string op1, string op2, int op3, int op4)
{
Option opt = new Option();
opt.Op1 = op1;
opt.Op2 = op2;
opt.Op3 = op3;
opt.Op4 = op4;
return opt;
}
这种方式,通过函数传递的方式来构造一个Option。
在这里,我们思考一下,这种方式来构造Option有什么缺点,Emm,思考5分钟…。
五分钟过去了
经过了5分钟的思考,相信聪明的读者已经想出来了。
这种方式的缺点如下:
- 如果需要增加Option的属性或者字段的话,CreateOption方法的参数,也需要同步添加;
- 如果某些属性或者字段需要使用默认值,那么我们需要提前知道,并通过CreateOption方法的参数传进去。
好了知道了缺点之后,就是时候,正式介绍一下选项模式了。
选项模式
选项模式,其实是通过闭包,将每个选项的配置写成一个独立的函数,通过调用对应的函数来进行配置的一种方式。
上面的这句话是我的总结啦,可能有点难懂,我们直接上码?吧!
C#代码
首先定义一个用来表示设置选项的委托
public delegate void OptionFunction(Option option);
下面是关键的代码,看懂了的话,基本就懂选项模式了(这里需要注意一下,我这里写成static,主要因为我用的是console来演示的)
1.为每个选项配置属性写对应的函数
public static OptionFunction WithOp1(string op1)
{
return (Option option) => option.Op1 = op1;
}
public static OptionFunction WithOp2(string op2)
{
return (Option option) => option.Op2 = op2;
}
public static OptionFunction WithOp3(int op3)
{
return (Option option) => option.Op3 = op3;
}
public static OptionFunction WithOp4(int op4)
{
return (Option option) => option.Op4 = op4;
}
2.构造Option的函数
public static Option InitOption(params OptionFunction[] optfuncs)
{
Option opt = new Option();
foreach (var func in optfuncs)
{
func(opt);
}
return opt;
}
这里我们通过可变参数的形式,传入每个选项的配置函数,在构造的时候,直接调用所有传进来的配置函数就可以达到配置Option的目的了。
3.如何调用
static void Main(string[] args)
{
var opt = InitOption(
WithOp1("op1"),
WithOp2("op2")
);
}
这里我们只希望配置Op1和Op2,让Op3和Op4保持默认值,所以我们只需要传配置Op1和Op2的函数就可以了,这样就可以做到,只配置自己希望配置的属性,同时,在Option增加了属性情况下,我们也只需要增加一个对应的配置函数就可以,如果这个属性也需要配置的话,只需将这个函数传递给InitOption就可以了,这样就不会因为Option属性的增加而需要修改构造它的函数了。
Go代码
既然这个方式是在学习Go的时候发现的,这里当然就不能少了Go语言的实现版本啦?。
这里我就不详细解释了,直接上代码,因为主要的差异也只是在于语法啦。
package main
import "fmt"
type OptionFunc func(op *Option)
type Option struct {
op1 string
op2 string
op3 int
op4 int
}
func InitOptions(optionFuncs ...OptionFunc) Option {
option := Option{}
for _, op := range optionFuncs {
op(&option)
}
return option
}
func WithOp1(op1 string) OptionFunc {
return func(op *Option) {
op.op1 = op1
}
}
func WithOp2(op2 string) OptionFunc {
return func(op *Option) {
op.op2 = op2
}
}
func WithOp3(op3 int) OptionFunc {
return func(op *Option) {
op.op3 = op3
}
}
func WithOp4(op4 int) OptionFunc {
return func(op *Option) {
op.op4 = op4
}
}
func main() {
op := InitOptions(
WithOp1("op1"),
WithOp2("op2"),
WithOp3(3),
WithOp4(4),
)
fmt.Printf("%#v\n", op)
}
总结
其实,我在写这篇博客的时候,思考过一个问题,我们在构造一个配置属性类的时候,为什么不使用建造者模式呢(如果没了解过建造者模式的读者可以通过《如何打造完美女朋友》这篇博客了解一下。
我思考下来,觉得建造者模式对于这种情况来讲,过于笨重,不够灵活,为什么这样讲呢?因为建造者模式是有固定的建造流程,而配置则不一定,有的属性需要保留默认值(或者说不参与构建流程),当然啦,建造者模式的优势是将细节隐藏起来,让调用者不需要知道详细的构造过程(如果配置的属性异常地多,也可以考虑使用建造者模式)。当然也可以试一下使用选项模式来完成《如何打造完美女朋友》的,哈哈哈,有兴趣的读者可以自己尝试一下。