我的计算器——6 用依赖注入改进
2008-06-11 18:32
721 查看
之前虽然实现了所有的功能,但对工厂类中的switch语句感到很不满意。每次添加一个计算方法,新建一个继承自TokenRecord的类的同时,还必须在工厂中的两个地方注册。这样就违背了开放封闭原则,而且会使代码不停的增长。参考《大话设计模式》中的例子,这里采用依赖注入的方式将switch语句给替代了。如果需要添加一个计算方法,新建一个继承自TokenRecord的类之后,只需要在配置文件中添加注册信息即可,其他代码根本不需要修改,很好的实现了封装变化。要实现依赖注入首先必须建立一个配置文件,这里采用XML文件,文件名为TokenRecord.xml,和exe文件存放在同一文件夹,文件内容如下:
<?xmlversion="1.0"encoding="utf-8"?> <TokenRecord> <TokenKeyword> <!--以下字符串处理函数记号对象--> <TokenWord="mid"Class="TokenMid"/> <TokenWord="left"Class="TokenLeft"/> <TokenWord="right"Class="TokenRight"/> <TokenWord="string"Class="TokenToString"/> <!--以下为数学运算记号对象--> <TokenWord="round"Class="TokenRound"/> <TokenWord="abs"Class="TokenAbs"/> <TokenWord="max"Class="TokenMax"/> <TokenWord="min"Class="TokenMin"/> <TokenWord="sin"Class="TokenSin"/> <TokenWord="cos"Class="TokenCos"/> <TokenWord="mod"Class="TokenMod"/> <TokenWord="pow"Class="TokenPow"/> <!--以下为逻辑运算记号对象--> <TokenWord="and"Class="TokenAnd"/> <TokenWord="or"Class="TokenOr"/> <TokenWord="not"Class="TokenNot"/> <TokenWord="xor"Class="TokenXor"/> <!--以下为常量记号对象--> <TokenWord="pi"Class="TokenValue"/> <TokenWord="e"Class="TokenValue"/> <TokenWord="true"Class="TokenValue"/> <TokenWord="false"Class="TokenValue"/> <TokenWord="if"Class="TokenIf"/> </TokenKeyword> <TokenSymbol> <!--以下为分隔符--> <TokenWord="("Class="TokenLeftBracket"/> <TokenWord=")"Class="TokenRightBracket"/> <TokenWord=","Class="TokenComma"/> <!--以下为数学运算符--> <TokenWord="+"Class="TokenPlus"/> <TokenWord="-"Class="TokenMinus"/> <TokenWord="*"Class="TokenMultiply"/> <TokenWord="/"Class="TokenDivide"/> <TokenWord="%"Class="TokenMod"/> <TokenWord="^"Class="TokenPow"/> <!--以下为比较运算符--> <TokenWord="="Class="TokenEqual"/> <TokenWord="=="Class="TokenEqual"/> <TokenWord="><"Class="TokenNotEqual"/> <TokenWord="!="Class="TokenNotEqual"/> <TokenWord=">"Class="TokenGreatThan"/> <TokenWord=">="Class="TokenGreatOrEqual"/> <TokenWord="<"Class="TokenLessThan"/> <TokenWord="<="Class="TokenLessOrEqual"/> <!--以下为逻辑运算符--> <TokenWord="!"Class="TokenNot"/> <TokenWord="&"Class="TokenAnd"/> <TokenWord="&&"Class="TokenAnd"/> <TokenWord="|"Class="TokenOr"/> <TokenWord="||"Class="TokenOr"/> </TokenSymbol> </TokenRecord>其中分成两类,TokenKeyword和TokenSymbol,添加新的计算方法时也必须按分类注册到对应的类型中。Word表示的是计算方法的关键字或者运算符,可能需要用转义字符,比如“>”转义成“>”。Class表示计算方法对应的类名称,这里所有的类均继承自ConExpress.Utility.TokenRecord。反射需要获取类的完整名称,而它们所处的命名空间都是ConExpress.Utility,所以这里可以省略命名空间,在程序内部添加。代码中需要修改的地方主要是工厂类,TokenKeywordFactory和TokenSymbolFactory。TokenKeywordFactory中获取关键字列表的原始代码为:
///<summary> ///关键字列表,只允许在GetKeywordList中修改 ///</summary> privatestaticList<string>m_ListKeyword=newList<string>(); ///<summary> ///获取关键字列表 ///</summary> ///<returns>关键字列表</returns> ///<remarks>Author:AlexLeo@ConExpress</remarks> publicstaticList<string>GetKeywordList() { if(m_ListKeyword.Count==0) { string[]ArrayString=newstring[]{"mid","left","right","string"};//字符串处理函数 string[]ArrayArithmetic=newstring[]{"abs","round","max","min","sin","cos","mod","pow"};//数学运算函数 string[]ArrayLogic=newstring[]{"and","or","not","xor"};//逻辑运算符号 string[]ArrayConstant=newstring[]{"pi","e","true","false"};//常量符号 string[]ArrayOther=newstring[]{"if"};//其他关键字 m_ListKeyword.AddRange(ArrayString); m_ListKeyword.AddRange(ArrayArithmetic); m_ListKeyword.AddRange(ArrayLogic); m_ListKeyword.AddRange(ArrayConstant); m_ListKeyword.AddRange(ArrayOther); } returnm_ListKeyword; }
改进后的代码为:
privatestaticDictionary<string,string>m_DictionaryKeyword=newDictionary<string,string>(); ///<summary> ///获取关键字字典 ///</summary> ///<returns>关键字字典</returns> ///<remarks>Author:AlexLeo@ConExpress;Date:2008-5-19;Remark:基于本地文件TokenRecord.xml;</remarks> privatestaticDictionary<string,string>GetKeywordDictionary() { if(m_DictionaryKeyword.Count==0) { XmlDocumentmyDoc=newXmlDocument(); myDoc.Load("./TokenRecord.xml"); XmlNodeListKeywordList=myDoc.SelectNodes("TokenRecord/TokenKeyword/Token"); foreach(XmlNodeNodeinKeywordList) { m_DictionaryKeyword.Add(Node.Attributes["Word"].Value,Node.Attributes["Class"].Value); } } returnm_DictionaryKeyword; }
采用配置文件后就可以将变化转移到配置文件中,程序里只需要读取配置文件即可。但必须保证配置文件处在程序的运行目录下,而且要保证文件格式和内容的正确性。当然,对保证配置文件的正确性是很容易做到的。
TokenKeywordFactory中产生TokenRecord对象的方法原始代码为:
///<summary>///产生记号对象///</summary>///<paramname="TokenWord">分析得到的单词</param>///<paramname="Index">当前序号</param>///<returns>记号对象</returns>///<remarks>Author:AlexLeo@ConExpress</remarks>protectedstaticnewTokenRecordProduceToken(stringTokenWord,intIndex){TokenRecordToken;//判断是否是关键字if(GetKeywordList().Contains(TokenWord.ToLower())){//判断关键字类型switch(TokenWord.ToLower()){case"if"://以下为其他关键字Token=newTokenIf(Index+1);break;case"mid"://以下字符串处理函数记号对象Token=newTokenMid(Index+1);break;case"left":Token=newTokenLeft(Index+1);break;case"right":Token=newTokenRight(Index+1);break;case"string":Token=newTokenToString(Index+1);break;case"round"://以下为数学运算记号对象Token=newTokenRound(Index+1);break;case"abs":Token=newTokenAbs(Index+1);break;case"max":Token=newTokenMax(Index+1);break;case"min":Token=newTokenMin(Index+1);break;case"sin":Token=newTokenSin(Index+1);break;case"cos":Token=newTokenCos(Index+1);break;case"mod":Token=newTokenMod(Index+1);break;case"pow":Token=newTokenPow(Index+1);break;case"and"://以下为逻辑运算记号对象Token=newTokenAnd(Index+1);break;case"or":Token=newTokenOr(Index+1);break;case"not":Token=newTokenNot(Index+1);break;case"xor":Token=newTokenXor(Index+1);break;case"pi"://以下为常量记号对象Token=newTokenValue(Index+1);Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=Math.PI;break;case"e":Token=newTokenValue(Index+1);Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=Math.E;break;case"true":Token=newTokenValue(Index+1);Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=1;break;case"false":Token=newTokenValue(Index+1);Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=0;break;default:thrownewException(string.Format("语法错误,列{0},未知表达式:{1}。",Convert.ToString(Index+1),TokenWord));}//switch}//ifelse{//错误字符串,抛出错误,语法错误thrownewException(string.Format("语法错误,列{0},未知表达式:{1}。",Convert.ToString(Index+1),TokenWord));}returnToken;}//ProduceToken改进后的代码为:
///<summary>///产生记号对象///</summary>///<paramname="TokenWord">分析得到的单词</param>///<paramname="Index">当前序号</param>///<returns>记号对象</returns>///<remarks>Author:AlexLeo@ConExpress</remarks>protectedstaticnewTokenRecordProduceToken(stringTokenWord,intIndex){TokenRecordToken;if(GetKeywordDictionary().ContainsKey(TokenWord.ToLower())){stringstrFullClassName="ConExpress.Calculator."+GetKeywordDictionary()[TokenWord.ToLower()];TypeTokenType=Type.GetType(strFullClassName);Token=(TokenRecord)Activator.CreateInstance(TokenType,newobject[]{Index+1});//对常数的特殊处理switch(TokenWord.ToLower()){case"pi"://以下为常量记号对象Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=Math.PI;break;case"e":Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=Math.E;break;case"true":Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=1;break;case"false":Token.TokenValueType=TokenValueTypeEnum.Number;Token.TokenNumber=0;break;default:break;}}else{//错误字符串,抛出错误,语法错误thrownewException(string.Format("语法错误,列{0},未知表达式:{1}。",Convert.ToString(Index+1),TokenWord));}returnToken;}//ProduceToken通过这个改进可以很明显的看到,改进后的代码行数减少很多,而且还可以避免新增关键字时忘记注册case。TokenSymbolFactory的修改和TokenKeywordFactory的修改类似,这里就不贴出详细代码了,具体的改动在程序里都会保留。最近又对程序进行了一些改进,添加了一个SyntaxException类,用于发生错误时给调用程序提供诸如错误信息,错误操作符起始位置,错误操作符长度等,这样就可以在界面上选中发生错误的操作符了,更加人性化。为了配合SyntaxException类,对程序中的其他相关之处也进行了修改,比如TokenRecord的构造函数添加了一个参数,这样就导致其他构造都需要发生变化,但并不影响理解。对主界面也进行了完善,可以选择多行执行还是单行执行。选择单行执行时,输入完成后直接回车即可进行计算,并且会把输入框中的代码合并成一行进行计算。如果选择多行,则将每一行进行单独的计算,计算结果在输出框中按多行显示,此时回车为换行操作,需要手动点击计算按钮。而且,这里模仿SQLServer,可以选中要计算的表达式,对部分代码进行计算。此外还添加了一些TokenRecord,主要是三角函数,其实就是对System.Math中的一些方法的封装。如果需要也可以添加更多的方法,甚至把System.Math中的所有方法都实现。最近在看委托,觉得可以用委托进一步重构。因为很多TokenRecord代码都一样,只不过实际的算法不同,差异只有一行而已。而采用委托就可以将算法动态改变,似乎是一个更理想的方法。所有分析就到这里了,这只是一个练习的小程序,并没有做漂亮的外观和强大的功能。如果需要添加其他算法,只需要参考响应的算法,从TokenRecord及其子类继承即可。如果发现程序中有什么问题,也欢迎指正。
相关文章推荐
- Angular 4 依赖注入
- Python中接口定义和依赖注入
- 开源项目源码解析-依赖注入
- Spring MVC中,基于XML配置和基于注解的依赖注入实例
- 浅谈依赖注入
- 控制反转(IoC)与依赖注入(DI)
- AngularJS 的Provider,Factory与Service实现依赖注入
- 全面理解控制反转和依赖注入
- AngularJS基础 之 依赖注入的几种方法
- 微软企业库5.0 学习之路——扩展学习篇、库中的依赖关系注入(重构 Microsoft Enterprise Library)[转]
- ASP.NET 依赖注入。
- 用最简单方式解释“依赖注入”及其如何实现
- 控制反转&nbsp;Ioc&nbsp;依赖注入DI
- php中的依赖注入
- Dagger——Android的依赖注入框架
- [译]在Java中使用JavaScript作为依赖注入容器
- 两个个很形象的依赖注入的比喻
- spring的四种依赖注入方式
- Angular 4 依赖注入教程之一 依赖注入简介
- 详解 Spring 3.0 基于 Annotation 的依赖注入实现