设计模式之选项模式(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分钟的思考,相信聪明的读者已经想出来了。

这种方式的缺点如下

  1. 如果需要增加Option的属性或者字段的话,CreateOption方法的参数,也需要同步添加;
  2. 如果某些属性或者字段需要使用默认值,那么我们需要提前知道,并通过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)
}

总结

其实,我在写这篇博客的时候,思考过一个问题,我们在构造一个配置属性类的时候,为什么不使用建造者模式呢(如果没了解过建造者模式的读者可以通过《如何打造完美女朋友》这篇博客了解一下。
我思考下来,觉得建造者模式对于这种情况来讲,过于笨重,不够灵活,为什么这样讲呢?因为建造者模式是有固定的建造流程,而配置则不一定,有的属性需要保留默认值(或者说不参与构建流程),当然啦,建造者模式的优势是将细节隐藏起来,让调用者不需要知道详细的构造过程(如果配置的属性异常地多,也可以考虑使用建造者模式)。当然也可以试一下使用选项模式来完成《如何打造完美女朋友》的,哈哈哈,有兴趣的读者可以自己尝试一下。


版权声明:本文为jmelzc原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。