多样式星期名字转换 [Design, C#]
Written by Allen Lee
1. 原来的问题...
Johnsuna 在我的《关于枚举的种种 [C#, IL, BCL]》那里提出了这样一个问题:
现在我想做一个多版本的带农历的中国万年历,月历中有星期日、星期一至六,我想使用"星期一","一"或"Monday", "Mon",或"M",但也可能使用其组合,如“星期一Mon”, 于是我定义一个公共Style枚举,里面有ChineseFullName, ChineseShortName, EnglishFullName,EnglishShortName, EnglishSingleLetter等,然后又定义一个公共的ChineseFullName枚举,里面有:星期一,星期二等等,类似的EnglishShortName:Mon,Tue等(类推)。
因为可能要进行组合,比如Style为:ChineseFullName|EnglishShortName以便得到“星期一MON”的结果,所以考虑使用枚举而未使用数组。
我的问题是:
-
如果现在选择的是星期五,Style样式为EnglishShortName,如何取得“Fri”的字符串?如果样式Style选择的是EnglishShortName | ChineseShortName,又如何得到“Fri一”呢?
- 如果EnglishSingleLetter枚举,则其枚举值为:M,T,W,T,F,S,S,很明显,T(Tuesday)与T(Thursday)重复,S(Saturday)与S(Sunday)重复,这在枚举中是不允许的。那么,如何才能正确实现呢?
2. 正向方案:实施授权。
根据 Johnsuna 的需求,我们得首先有一个 DayStyle 枚举来表示各种可用样式的名字,由于需求明确提出要进行样式的复合表示,所以我们必须在枚举上加上 FlagsAttribute:
//Code#01
[Flags]
enumDayStyle
{
ChineseFullName=0x0001,
ChineseShortName=0x0002,
EnglishFullName=0x0004,
EnglishShortName=0x0008,
EnglishSingleLetter=0x0010,
}
下面,我将采用 TDD 的思维一步一步探讨这个问题的解决方案。首先,根据上面的需求,我认为 Johnsuna 会喜欢下面这种做法:
//Code#02
textBox1.Text=day.ToString(
DayStyle.ChineseFullName|
DayStyle.ChineseShortName|
DayStyle.EnglishFullName|
DayStyle.EnglishShortName|
DayStyle.EnglishSingleLetter
);
上面这种方法很明显需要我们在 ToString 里面分析客户端把一个什么样的 DayStyle 传递进来了,当然包括单独一个 DayStyle 成员以及通过“|”运算符得到的成员组合这两种情况。
我们知道,当某个位枚举变量和某一位枚举成员的按位与操作结果不为零时,该变量包含了该枚举成员。这样,下面的代码可以放到 ToString 里面用于进行与 ChineseFullName 相关的操作:
//Code#03
if((style&DayStyle.ChineseFullName)!=0)
{
//Oh,youwanttheChinesefullname!
}
我将用同样的方法处理其他枚举成员,由于需求中提到希望的到类似“星期一Mon”这样的结果,所以我们不难想象到 ToString 里面将会涉及到字符串相加这一操作。执行这一操作的最佳人员当然就是 StringBuilder 了,于是我们有了下面的设想方案:
//Code#04
if((style&DayStyle.ChineseFullName)!=0)
{
m_Content.Append("星期一");
}
if((style&DayStyle.EnglishShortName)!=0)
{
m_Content.Append("Mon");
}
然而,上面的代码仅能用于示意,它不能真正用到实际中,为什么呢?很明显,如果我们硬编码这些星期的表示,那么 ToString 至少还需要一个参数来判断客户端需要星期几的表示。这个方案除了加重了我们的劳动强度让我们有借口叫老板加工资外,它几乎没有其他好处了。所以,我们必须想办法把这个“星期几”的具体表示分离出来。如何做到呢?很明显,Template Method 模式是一个让我们的繁重工作得到解脱的办法。
现在,我们要做的就是声明一个抽象类 Day 以及一系列的抽象方法,例如 ToChineseFullName,让 Day 的派生类,例如 Monday,实现这一组抽象方法,而 Day.ToString 就通过这一组抽象方法把获取具体的表示的工作“授权”给那些派生类:
//Code#05
protectedabstractstringToChineseFullName();
publicstringToString(DayStylestyle)
{
if((style&DayStyle.ChineseFullName)!=0)
{
m_Content.Append(ToChineseFullName());
}
//
}
好吧,现在万事俱备,只欠子类了:
//Code#06
classMonday:Day
{
protectedoverridestringToChineseFullName()
{
return"星期一";
}
//
}
好了,剩下的工作就是如法炮制其他派生类,虽然这些工作是劳动密集型的,但你仍得着手完成它。下面是 Monday 的实现:
正向方案#region正向方案
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceMyUtils.Day
{
classProgram
{
staticvoidMain(string[]args)
{
Dayday=newMonday();
Console.WriteLine(day.ToString(
DayStyle.ChineseFullName|
DayStyle.ChineseShortName|
DayStyle.EnglishFullName|
DayStyle.EnglishShortName|
DayStyle.EnglishSingleLetter
));
}
}
publicabstractclassDay
{
privateStringBuilderm_Content=newStringBuilder();
protectedabstractstringToChineseFullName();
protectedabstractstringToChineseShortName();
protectedabstractstringToEnglishFullName();
protectedabstractstringToEnglishShortName();
protectedabstractstringToEnglishSingleLetter();
publicstringToString(DayStylestyle)
{
if((style&DayStyle.ChineseFullName)!=0)
{
m_Content.Append(ToChineseFullName());
}
if((style&DayStyle.ChineseShortName)!=0)
{
m_Content.Append(ToChineseShortName());
}
if((style&DayStyle.EnglishFullName)!=0)
{
m_Content.Append(ToEnglishFullName());
}
if((style&DayStyle.EnglishShortName)!=0)
{
m_Content.Append(ToEnglishShortName());
}
if((style&DayStyle.EnglishSingleLetter)!=0)
{
m_Content.Append(ToEnglishSingleLetter());
}
returnm_Content.ToString();
}
publicoverridestringToString()
{
returnToString(DayStyle.ChineseFullName);
}
}
publicclassMonday:Day
{
protectedoverridestringToChineseFullName()
{
return"星期一";
}
protectedoverridestringToChineseShortName()
{
return"一";
}
protectedoverridestringToEnglishFullName()
{
return"Monday";
}
protectedoverridestringToEnglishShortName()
{
return"Mon";
}
protectedoverridestringToEnglishSingleLetter()
{
return"M";
}
}
[Flags]
publicenumDayStyle
{
ChineseFullName=0x0001,
ChineseShortName=0x0002,
EnglishFullName=0x0004,
EnglishShortName=0x0010,
EnglishSingleLetter=0x0020,
}
}
#endregion
值得注意的是,我在 Day 中重载了 Object.ToString 方法,因为我不希望在任何可能隐式调用 Object.ToString 的地方得到一个类型的名字。你可以把这个重载后的版本看作是默认的样式表示转换,套用任何一个你喜欢的转换策略。
3. 后来的问题...
上面那个方案可行,但就是不好,因为它隐藏了一个,一旦这个爆炸,我们不但没借口向老板提出加工资,还要受老板责怪,并且要自己加班收拾残局。你能猜到这个是什么吗?
让我们回顾一下 DayStyle 枚举,这个枚举现在表明我们将可以处理两种语言,并且每种语言至少有两种表达方式,你是否想到了什么呢?如果我们现在要加多一种语言呢?问题就在这里了,这个枚举的成员不够稳定。现在我们已经要处理中文和英文两种语言了,将来难免要处理更多的语言,因为你可能希望你的程序面向国际市场。更糟糕的是,我们并不知道我们将要添加的语种有多少种表达方式,至少现在的情况是英语比汉语多了一个“首字母”表达方式,那么有谁又能知道将要添加的语中会带有哪些稀奇古怪的特殊表达方式呢?
很明显,DayStyle 枚举的不稳定将会扩散到与之相关的每一个角落,而维护这样的代码可能会变成一场噩梦。
如果你读过 Alan Shalloway 和 James R. Trott 的 Design Patterns Explained,你应该会记得他们对封装的精辟阐述:
Find what is varying and encapsulate it.
接下来,我将会尝试把变化的根源封装起来...
4. 反向方案:封装变化。
如果你读过我关于枚举的文章,你会知道我主张把枚举看作一种分类手段,并且仅当分类的细节相对稳定的情况下才考虑使用枚举,否则你应该考虑别的出路。
那么,在我们要处理的问题域中,哪些因素容易改变,哪些因素又较为稳定呢?很明显,DayStyle 绝对不会被归入稳定因素的行列,于是剩下的就是 Day 了,那么 Day 稳定吗?当然稳定,因为一个星期有且仅有7天,并且每天的名字也是固定的。那么,把 Day 表示成枚举就合适了:
//Code#07
enumDay
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
下面,我也将采用 TDD 的思维来一步一步探讨该问题的解决方案。由于我不知道 Johnsuna 会否喜欢上这个方案,我只能假设有一个喜欢这个方案的用户 Foo 了。既然 Foo 喜欢这个方案,他将很有可能使用如下方式来处理转换:
//Code#08
NameConverternc=newNameConverter();
nc.AddInnerNameConverters(
newChineseFullNameConverter(),
newChineseShortNameConverter(),
newEnglishFullNameConverter(),
newEnglishShortNameConverter(),
newEnglishSingleLetterConverter()
);
textBox1.Text=nc.ToString(Day,Saturday);
从上面的代码可以看出,NameConverter 就是整个转换工程的“总管事”,而那些 XXXConverter 就是负责具体转换工作的“员工”。
“总管事”在这里的工作只是询问客户需要哪些转换,然后安排相关“员工”来完成具体的工作。由于我们将来可能会扩展更多的 XXXConverter 以满足更多的需求,所以 NameConverter 不可能(预先)了解到所有的这些具体的转换细节。为了使得“总管事”能够更好的统一管理整个转换过程,我们需要实施“目标管理”,让“总管事”关注“员工”的工作成果而不是工作过程。
那么,如何实施“目标管理”呢?既然“总管事”关注结果,那么我们可以考虑制定一个“行为规范”,即接口约定:
//Code#09
interfaceINameConverter
{
stringToString(Dayday);
}
然后让所有准备参与工作的“员工”“学习”这一“行为规范”,例如:
//Code#10
classEnglishFullNameConverter:INameConverter
{
stringINameConverter.ToString(Dayday)
{
returnday.ToString();
}
}
好了,现在我们要改变关注点,接下来,我们来看看“总管事”是如何管理“员工”的。作为“总管事”,它的职责有如下三点:
- 获悉客户的需求,即询问客户需要哪些转换服务;
- 安排相关的“员工”来处理具体的转换工作;
- 把最后的工作成果交到客户的手里。
首先我们来看第一点,为了更好的管理客户的需求,我们得首先把这些需求以后种方式收集起来:
//Code#11
privateList<INameConverter>m_NameConverters=newList<INameConverter>();
然后就是接收客户的需求了:
相关推荐
在IT行业中,文件转换是一项常见的...总的来说,C#结合LibreOffice组件,可以方便地实现跨格式的文件转换,为各种业务场景提供了强大的支持。通过学习和实践,开发者能够利用这项技术提高工作效率,满足多样化的需求。
### C#操作技巧的数据类型之间的...综上所述,C#中数据类型的转换是多样的,不仅可以方便地实现int与String之间的转换,还可以灵活地进行其他类型的转换。理解并熟练掌握这些转换方法对于编写高质量的C#程序至关重要。
标题中的“可定时,整点报时的多样式C#时钟程序源码”表明这是一个使用C#编程语言开发的时钟应用,具备定时提醒和整点报时功能,并且支持多种显示样式。这个程序可能包含一个用户友好的界面,允许用户自定义报时设置...
在C#编程中,数据类型转换是常见的...总之,C#的数据类型转换是多样的,理解并熟练掌握这些转换方法对编写高效、安全的代码至关重要。在实际编程中,应根据需求选择适当的转换方式,并注意潜在的数据丢失或异常风险。
《Delphi代码转换至C#:工具解析与实践》 在软件开发领域,不同编程语言间的代码转换有时是必要的,特别是在维护旧项目或利用现有代码库进行新项目开发时。Delphi,以其高效的性能和丰富的组件库,曾是许多开发者的...
它可以将C#对象转换为JSON字符串,也可以将JSON字符串转换回C#对象,极大地方便了开发人员的工作。 3. JSON序列化: 序列化是将C#对象转换成JSON字符串的过程。在Json.NET中,可以使用`JsonConvert....
两者各有优势,但往往需要在不同场景下互相转换,以满足多样化的应用场景。 该转换器基于C#编程语言,利用Windows Forms(WinForm)平台构建,提供了直观的用户界面。项目中包含了多个解决方案文件,如RtfWindows...
### C#中字符串转换为计算公式并进行计算的方法(自定义公式的计算) ...通过以上步骤和代码示例,我们可以在C#中轻松实现将字符串转换为计算公式并进行计算的功能,从而为用户提供灵活多样的计算能力。
jQuery仿途牛多样式图片轮播代码.rar jQuery仿途牛多样式图片轮播代码.rar jQuery仿途牛多样式图片轮播代码.rar jQuery仿途牛多样式图片轮播代码.rar jQuery仿途牛多样式图片轮播代码.rar jQuery仿途牛多样式图片...
"C# 导出Chart图片Excel"这个主题涉及到将图表(Chart)对象转换为图像,并将其嵌入到Excel工作表中,以便于数据分析和报告生成。在本文中,我们将详细探讨如何实现这一目标,以及涉及的相关技术点。 首先,让我们...
标题 "C#多功能记事本(带皮肤超强简繁转换)" 指的是一款使用C#编程语言开发的记事本应用程序,它不仅具备基本的文本编辑功能,还集成了多种增强型特性,如简体与繁体中文的转换、自定义皮肤以及特定文字格式的转换。...
通过上述分析,我们可以看到C#中类型转换的多样性和复杂性。了解并掌握这些转换机制不仅有助于编写更高效的代码,还能避免许多常见的编程陷阱。在实际应用中,应根据具体情况选择合适的类型转换策略,以确保程序的...
通过将CSV文件转换为C#类,开发者可以更方便地在Unity项目中读取和操作这些数据,而无需编写大量的解析代码。 Unity引擎广泛应用于游戏开发,它支持C#编程语言,因此将CSV数据转换为C#类有助于提高开发效率和代码可...
《C#员工打卡系统:构建智能办公环境》 在当今数字化办公时代,员工打卡系统扮演着至关重要的角色,它能够有效提升工作效率,确保考勤管理的准确性和公正性。C#作为微软公司推出的面向对象的编程语言,因其强大的...
本篇文章将深入探讨C#中如何实现HSL调色板的计算以及HSL到RGB的转换,同时会提及仿QQ和Windows调色板的实现方法。 首先,HSL色彩模型由三个参数定义:色相(Hue)、饱和度(Saturation)和亮度(Lightness)。色相H...
在C#项目中,你可以通过创建一个进程并执行pdf2htmlEX来实现转换。以下是一个简单的示例: ```csharp using System.Diagnostics; public class PdfToHtmlConverter { public void ConvertPdfToHtml(string ...
在C#编程中,图标(Icon)是一种重要的视觉元素..."C# 精美图标大全"提供的图标资源,不仅样式齐全,且尺寸多样,能够满足开发者在创建用户界面时的各种需求。正确使用这些图标,可以有效提升应用的专业感和用户体验。
总的来说,Ant Design Library 3.0 是Axure设计师的得力工具,它将Ant Design的优秀设计原则与Axure的强大功能相结合,为原型设计提供了一站式的解决方案。通过这个组件库,设计师可以更专注于创新和用户体验,而...
本主题聚焦于"C#斑马打印机源程序",它揭示了如何利用C#来控制斑马(Zebra)品牌的条码打印机,进行多格式、多样化的通讯方式打印。斑马打印机因其在工业级打印领域的可靠性而广受欢迎,常用于制造、物流、零售等...
PDF全能转换器是一款功能强大的软件工具,专门设计用于将PDF文件转换为...软件提供的多样化功能,如转换、合并、拆分等,使其成为处理PDF文档的得力助手。而注册码的相关文件则是确保用户能够充分利用这些功能的关键。