自己编写类似于枚举的类型(多例模式)

场景

枚举,其实就是一种特殊的整数,只不过只能取一系列特定的值。通过 enum Type : short 这样的语法,我们可以指定枚举在底层究竟使用哪种整数。

然而,有的时候我们希望自定义类型,其实例有着各种我们所需要的成员;但同时我们又希望这个类型只有有限个实例,用户只能从其中取一个使用。

比如,Anders Liu有一个系统,能够处理Word、Html和Text格式的文档,现在我希望定义一个DocumentType类型,表示所有支持的文档类型;但对于每一种类型,Anders Liu又偏偏希望它具有诸如Processor(处理这种类型文档的程序)和Extension(这种类型的文档的扩展名)等属性。

此时就出现了矛盾。如果使用枚举,必然无法实现这些实例属性。但是,如果自己编写一个类呢?

在这篇文章中,Anders Liu将带你一起实现一个用起来像枚举,但实际上是自定义类型的类。

我要的对象,它不是整数

首先,毕竟我们所需要的是对象,而不是枚举所提供的简单整数。所以,先定义好再说:

None.gif namespace  AndersLiu.Samples.EnumSimilarClass
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
InBlock.gif    
/// Indicates the surpported document types.
InBlock.gif    
/// You cannot inherits from this class.
InBlock.gif    
/// </summary>
InBlock.gif    
/// <remarks>
InBlock.gif    
/// This class looks very like an enum.
ExpandedSubBlockEnd.gif    
/// </remarks>

InBlock.gif    public sealed class DocumentType
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
ContractedSubBlock.gifExpandedSubBlockStart.gif        
instanse members#region instanse members
InBlock.gif        
string _name;
InBlock.gif        
string _processor;
InBlock.gif        
string _extension;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Initialize a <see cref="DocumentType"/> instance.
InBlock.gif        
/// This constructor is private, so that user cannot construct it from external.
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="name">Name of the document type.</param>
InBlock.gif        
/// <param name="processor">Processor of the document.</param>
ExpandedSubBlockEnd.gif        
/// <param name="extension">Extension of the document.</param>

InBlock.gif        private DocumentType(string name, string processor, string extension)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _name 
= name;
InBlock.gif            _processor 
= processor;
InBlock.gif            _extension 
= extension;
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Name of the document type.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public string Name
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return _name;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Processor of the document.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public string Processor
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return _processor;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Extension of the document.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public string Extension
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return _extension;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif        
#endregion

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

在这里,要注意的是,这个DocumentType类型的构造器被定义成了private,因为毕竟我们所提供的只是有限个对象,所以不希望客户代码创建它的实例。

这个类型非常简单,如果构造器是public的,我想它就已经完成了。

有限个实例,我给你准备好

既然我们只能支持已知的几种类型,不如先准备好,放在一个静态的集合中。

用哪种集合类型最好呢?Anders Liu偏爱Dictionary。因为Dictionary在检索成员时的时间复杂度是O(1)!

好,继续向这个Document类中添加成员:

ContractedBlock.gif ExpandedBlockStart.gif          foundation supports #region foundation supports
InBlock.gif        
static Dictionary<string, DocumentType> _allTypes;
InBlock.gif
InBlock.gif        
// Indicates the documents' type name.
InBlock.gif
        const string WordTypeName = "Word";
InBlock.gif        
const string HtmlTypeName = "Html";
InBlock.gif        
const string TextTypeName = "Text";
InBlock.gif
InBlock.gif        
// Indicates which application can be used to open the document.
InBlock.gif
        const string WordProcessor = "winword.exe";
InBlock.gif        
const string HtmlProcessor = "iexplorer.exe";
InBlock.gif        
const string TextProcessor = "notepad.exe";
InBlock.gif
InBlock.gif        
// Indicates the file extension of each document type.
InBlock.gif
        const string WordExtension = ".doc";
InBlock.gif        
const string HtmlExtension = ".html";
InBlock.gif        
const string TextExtension = ".txt";
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Static constructor. Initializes all supported document types.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        static DocumentType()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _allTypes 
= new Dictionary<string, DocumentType>();
InBlock.gif
InBlock.gif            _allTypes.Add(WordTypeName, 
new DocumentType(WordTypeName, WordProcessor, WordExtension));
InBlock.gif            _allTypes.Add(HtmlTypeName, 
new DocumentType(HtmlTypeName, HtmlProcessor, HtmlExtension));
InBlock.gif            _allTypes.Add(TextTypeName, 
new DocumentType(TextTypeName, TextProcessor, TextExtension));
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif        
#endregion

None.gif

现在,所有支持的文档类型实例就都位于这个_allTypes中了。

为了让程序漂亮一些,所有用到的字符串都使用常量代替了。我想聪明的你应该会喜欢。

其实,如果我们将XxxTypeName常量设置为public的,再通过一个只读属性公开_allTypes集合,我想,我们又已经写好了一个类,而且完全可以投入使用了。

还不够,我还想要枚举

人的贪念是无限的。Anders Liu也是如此,Anders Liu吹毛求疵、追求那并不存在的完美。

为了使用起来更像枚举,我们再添加一系列静态属性:

ContractedBlock.gif ExpandedBlockStart.gif          enum similar members #region enum similar members
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Word document.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public static DocumentType Word
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn _allTypes[WordTypeName]; }
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Html document.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public static DocumentType Html
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn _allTypes[HtmlTypeName]; }
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Text document.
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public static DocumentType Text
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn _allTypes[TextTypeName]; }
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif        
#endregion

None.gif

这样就明朗了吧?我想聪明的读者已经可以看出这个类型的使用场景了。

枚举还有一个特征,那就是可以与其底层的整数进行相互转换,还可以通过枚举成员的名字来得到枚举对象——这一切都不是非常负责。那么我们这个“仿枚举”,也应该如此。

我变,变字符串,变回来

这个简单,用自定义类型转换运算符就可以完成:

ContractedBlock.gif ExpandedBlockStart.gif          type convert oeprators #region type convert oeprators
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Implicit convert <see cref="DocumentType"/> object to <see cref="string"/>.
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="type">A given document type.</param>
ExpandedSubBlockEnd.gif        
/// <returns>The type name.</returns>

InBlock.gif        public static implicit operator string(DocumentType type)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return type.Name;
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Explicit convert <see cref="string"/> object to <see cref="DocumentType"/>.
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="typeName">Given document type name.</param>
ExpandedSubBlockEnd.gif        
/// <returns>The corresponsive document type.</returns>

InBlock.gif        public static explicit operator DocumentType(string typeName)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if(_allTypes.ContainsKey(typeName))
InBlock.gif                
return _allTypes[typeName];
InBlock.gif            
else
InBlock.gif                
throw new InvalidOperationException(string.Format("'{0}' is not a valid document type.", typeName));
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif        
#endregion

None.gif

这些就不用多解释了,如果你不会编写自定义类型转换运算符,那可该补补C#了。 :)

——看啊,抛异常了,显式转换的时候抛异常了!
——这有什么大惊小怪?很多时候做类型转换都会抛异常的。
——可是,抛异常影响效率……
——这……你这个人怎么比Anders Liu还追求“完美”啊?!

再完美一点

加一个判断,用来检测一个字符串是不是有效的文档类型名字,省得转换时抛异常:

ContractedBlock.gif ExpandedBlockStart.gif          other functions #region other functions
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Indicates a given type name is valid or not.
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="typeName">Given type name.</param>
ExpandedSubBlockEnd.gif        
/// <returns>Whether the <see cref="typeName"/> is vliad.</returns>

InBlock.gif        public static bool IsValidDocumetTypeName(string typeName)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return _allTypes.ContainsKey(typeName);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// Override. Generate the string representation of the current object.
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <returns>The string representation.</returns>

InBlock.gif        public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return string.Format(
InBlock.gif                
"{0} (Processor=[{1}], Extension=[{2}])",
InBlock.gif                _name,
InBlock.gif                _processor,
InBlock.gif                _extension);
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif        
#endregion

None.gif

顺便附赠一个重写的ToString方法。重写ToStrig方法也是一个好习惯,毕竟我们不希望当客户代码将这个类型的对象写到控制台上的时候,都千篇一律地是类型的名字。

好了,写到这里,这个DocumentType类就圆满了。

是骡子是马,拉出来溜溜

建一个WinForm项目,写一个方法:

ExpandedBlockStart.gif ContractedBlock.gif          /**/ /// <summary>
InBlock.gif        
/// Process a document with given type.
InBlock.gif        
/// </summary>
ExpandedBlockEnd.gif        
/// <param name="doc">Type of the document.</param>

None.gif          private   void  process(DocumentType doc)
ExpandedBlockStart.gifContractedBlock.gif        
dot.gif {
InBlock.gif            
string msg = string.Format(
InBlock.gif                
"I'm working with a {0} document in {1}.",
InBlock.gif                (
string)doc, doc.Processor);  // Look, use it just like a normal object.
InBlock.gif

InBlock.gif            MessageBox.Show(msg);
ExpandedBlockEnd.gif        }

None.gif

加一个按钮,试一试:

None.gif          private   void  btnProcessWord_Click( object  sender, EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif        
dot.gif {
InBlock.gif            process(DocumentType.Word);  
// Look, it looks similarly an enum!
ExpandedBlockEnd.gif
        }

None.gif
None.gif        
private   void  btnProcessHtml_Click( object  sender, EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif        
dot.gif {
InBlock.gif            process(DocumentType.Html);
ExpandedBlockEnd.gif        }

None.gif
None.gif        
private   void  btnProcessText_Click( object  sender, EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif        
dot.gif {
InBlock.gif            process(DocumentType.Text);
ExpandedBlockEnd.gif        }

None.gif

哦,不好意思,因为太简单了,所以加多了,这是三个。

看看,是不是很像枚举?但我们并没有丧失对象的实例属性。

啊哈,新的设计模式

——这个标题明显夸张了,其实大家可能经常在按这种习惯写代码,只不过没察觉罢了。
——可是,所谓“设计模式”,不就是那些我们经常用到的“习惯”么?

现在,我们简化一下上面的例子。假设只支持一种文档类型——Text。去掉额外的代码(读者自己去掉把,我辛辛苦苦写的可不想删),你发现了什么?

好,如果你还没看出来,再把类型的名字改成TextDocumentType,然后把仅剩的静态属性改成“Instance”这样的名字。

OK,是不是“单例”模式出来了?

所以,我们这里写的类型,不过就是比单例模式多几个“例”而已。前文其实也一直在暗示——多个实例、有限个实例……

因此,这种模式就是——多例模式,你叫“N例模式”、“有限例模式”也可以啦。

Show Me The Code

如果没记错,这应该是3D游戏大师Carmark(卡马克)的名言吧【参见《DOOM启示录》】。

想看代码?这里有: http://www.codeplex.com/a/Release/ProjectReleases.aspx?ReleaseId=6242

你可以不信,但别反驳我

有人会有反对意见的——搞这么复杂干嘛?定义一个枚举,再定义一个类型,提供按照枚举取实例的方法多好。

Anders Liu要说的是,本文绝对来源于实践而非YY,我的确这样做了,发现写的时候是简单一些(只是一些而已),但用起来太麻烦了!所以才有了本文。

要记住,对于一个类型,永远存在着这样的公理:

使用一个类型的几率比定义这个类型的几率大很多。同理适用于类型的成员。

所以,在编写类型(及其成员)的时候,一切以“使用”为重。再多说一句,看看所谓设计模式,不都是希望“使用”起来容易一些么?
单例模式的扩展及应用。 编写一个类LimitInstanceClass,该类最多可以实例化指定个数实例。实例的个数用配置文件InstanceLimit.cfg指定。例如,如果InstanceLimit.cfg的内容为2,则LimitInstanceClass最多可以同时存在2个对象。LimitInstanceClass的对象有一个整型成员变量id,保存对象的编号;有一个boolean型变量isBusy,如果该变量的值为true,表示该对象正在被使用,否则该对象空闲;如果存在空闲的对象,则调用LimitInstanceClass的getInstance()方法会返回一个空闲对象,同时将该对象的isBusy置为true;如果不存在空闲对象则返回null。LimitInstanceClass有一个release()方法,该方法将对象的isBusy置为false。LimitInstanceClass还有一个String类型的成员变量accessMessage,以及一个成员方法writeAccessMessage(String message),该方法将参数message追加到accessMessage。LimitInstanceClass的printAccessMessage()方法输出accessMessage的内容。 编写一个线程类AccessLimitInstanceClassThread,在其run()方法中获取一个LimitInstanceClass对象,调用获得的对象的writeAccessMessage(String message)将自己的线程名写入accessMessage,随机休眠0-5秒,再调用printAccessMessage(),最后调用release()方法。 编写一个UseLimitInstanceClass类,在其main方法中实例化10个AccessLimitInstanceClassThread线程对象,并启动各个线程。 设置InstanceLimit.cfg的内容为3,写出你的程序的运行结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值