English version also available: TinySolution: Resolve the restrictions of enum
Swift的enum
相比Objective-C来说已经强大了很多,我们可以创建基于各种类型、带有不同参数的case
值,在一些不同状态或对象具有截然不同的数据类型的情况下非常有用,例如我们常用的Result
类型。
enum
虽然强大,但也存在一些限制,举个例子,我们经常需要在程序画两种线条,实线和虚线,可以指定线宽,这时候我们可以用enum
实现这个工具类。
现实需求总不是那么简单
虚线其实除了需要指定线宽之外,还需要支持定义虚线段的宽度和间隔位置,当然我们可以把Line
改成以下格式:
问题确实解决了,但大部分的虚线都使用同样的线段宽和间隔,我们并不想每个地方都手动传一次相同的值,所以我们尝试使用默认参数:
很不幸编译器告诉我们enum
不支持默认参数,但我们从Objective-C就承受着这个限制,对于如何实现默认参数的效果也驾轻就熟了,于是我们改成这个模样:
编译器告诉我们ok,三种case
对象的创建也都成功。所以问题解决了?至少看起来确实如此。
奇怪的表现
当你开始写switch
方法的时候,怪异的情况就发生了。例如我添加一个output()
的方法,出现了一个警告:
前面一个case dash(...)
包含了后面一个的所有情况了,考虑到前一个参数少一点,我们交换一下:
然而情况还是一样,看来两个case dash(...)
对于编译器来说都是一样的,因此我们无法找出准确的case
。
如果这就是问题的全部,那这种实现还是有作用,但事实并非如此。让我们基于交换后那个Line
类型编写几个测试:
可以看到Line.dash(width:1)
被判断成default
,不属于Line.dash
类型,因此这种写法已经属于一个异常的写法了,同名的case
后者会把前者的功能覆盖掉,前者仅有创建对象的功能,而且switch
也无法判断,基本属于不可用的状态。
这或许属于Swift的bug?知道的可以告诉我一下。
解决方案
既然是TinySolution,我们当然要针对问题给出解决方法。简单实现我们可以手动创建一个static func dash
替代case dash(width: Float)
这样我们就能正常输出和判断我们的Line
对象:
但把自动生成和手动添加的初始化方法混合使用始终有点不纯粹,特别是如果初始化的时候需要执行某些配置,两者的灵活性不一致。因为如果我们尝试重写自动生成的静态方法,会出现重定义的编译错误:
更好的解决方案
当然既然我们能定义一个初始化方法,我们可以把case
名称改掉,然后自行创建所有初始化方法,但这样就有两套初始化方法混杂,可能存在不一样的行为,而且这个对象还是enum
,当我们switch
的时候就会看到一套跟我们日常使用完全不一致的case
,我觉得这样并不是一种好的方案,所以我推荐另一种更加清晰的实现:
首先我们用一个struct Line
包裹起原来的enum Line
,为了命名不要冲突我们把enum Line
的名称改成enum Content
,然后struct
持有对应的enum
对象,把原有enum
的方法抽取到struct
层,再添加静态创建方法去实现enum
的创建效果,因为是静态方法所以我们的默认参数又可以大派用场了。这时候我们依然不用修改之前enum
对象的创建代码,直接就可以正常使用。