简介 DSL(Domain Specified Language)领域特定语言是描述特定领域问题的语言,在软件开发中,其目的是解决领域专家与软件开发人员之间的沟通问题。领域专家通常不懂得编程,无法判断开发人员写的代码是否符合领域的要求,只能是等到软件编写完成,从软件运行表现出来的功能进行判断,而这时成本已经发生了,几个来回下来,进度超时,成本超支。DSL是针对特定领域的语言,使用的是领域相关的术语,领域专家可以理解,而语言本身基于某种宿主语言,比如C#,是可以编译运行的,所以开发人员也可以理解。所以恰当的DSL可以打通领域专家和开发人员之间的障碍,使软件的业务核心部分开发可靠并有效率。
与伪语言的不同 在需求描述的时候,经常使用各种图示或者伪语言对业务进行描述,伪语言一般是一种类似的结构化语言,这种貌似语言的东西往往是很有害的,因为只是大概描述了过程,很多实现细节被忽略或者隐藏了。由于不是严格的编程语言,无法生成可执行的代码,所以也就无法验证对错。
DSL是在某种宿主语言上的扩展,因此是严格的编程语言,可以通过编译执行进行验证,这是与伪语言最大的不同。
Flunt-API 使用Flunt Api可以使代码的可读性更好,更接近于领域专家。Flunt Api的另一个好处是易于扩展,可以通过扩展为核心业务类增加功能。这里举一个简单的例子进行说明,比如有下面的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 using System.Collections.Generic; namespace ZL.Stock { public class StockAnalyzer { public List<StockFirstTrade> StockFirstTrades { get; private set; } public StockAnalyzer(List<StockFirstTrade> stockFirstTrades) { StockFirstTrades = stockFirstTrades; } } }
我们为这个类增加一些方法,可以定义一个类,声明为static:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ZL.Stock { public static class StockAnalyzerExt { public static StockFirstTrade Detail(this StockAnalyzer analyzer, string stockcode) { return analyzer.StockFirstTrades.FirstOrDefault(o => o.TSCode == stockcode); } } }
这个类中声明一个静态方法,这个方法的第一个参数带有this关键字,那么这个方法就会扩展到this参数修饰的类上。可以这样调用这个扩展方法:
1 2 3 var lst = JsonConvert.DeserializeObject<List<StockFirstTrade>>(json); var analyzer = new StockAnalyzer(lst); var detail=analyzer.Detail("688001.SH");
使用中文 通常我们编写程序时不主张使用中文作为变量或者方法名称,尽管现代编程语言的编译器很多已经不限于只支持ASCII码,但我们仍然无法确保在某些情况下不出现问题(比如如果将中文命名的方法映射为Web Api接口,不支持中文的客户端可能无法调用这个Api)。然而作为领域特定语言的DSL就不用有这个限制,DSL的主要目的就是沟通,如果必须用英文或者汉语拼音进行编写,效果就会大打折扣。看一下下面的代码,是不是很好理解?
1 2 3 4 5 Console.WriteLine(analyzer.最高涨幅()); Console.WriteLine(analyzer.开盘价与最高价最小差()); Console.WriteLine(analyzer.开盘价与最低价最大差()); Console.WriteLine(analyzer.开盘价就是最高价()); Console.WriteLine(analyzer.开盘价就是最低价());
想象一下如果不使用中文,编写这些代码的效果吧。
简单的例子 我们需要分析科创板股票首日上市时的涨跌幅情况,看一下中签的散户,上市首日以什么价格出售可以获得相对高的收益。我们以现有的科创板股票作为样本,首日上市的数据保存在json文件中,结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "TSCode": "688001.SH", "TradeDate": "20190722", "FirstOpen": 55.4, "FirstHigh": 72.02, "FirstLow": 39.59, "FirstClose": 55.5, "FirstChange": 31.24, "FirstPreChange": 128.7716, "SecondOpen": 46.92, "SecondHigh": 52.65, "SecondLow": 45.01, "SecondClose": 48.73, "SecondChange": -6.77, "SecondPreChange": -12.1982 }
上面的数据结构对应的C#类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 namespace ZL.Stock { public class StockFirstTrade { public string TSCode { get; set; } public string TradeDate { get; set; } public decimal FirstOpen { get; set; } public decimal FirstHigh { get; set; } public decimal FirstLow { get; set; } public decimal FirstClose { get; set; } public decimal FirstChange { get; set; } public decimal FirstPreChange { get; set; } public decimal SecondOpen { get; set; } public decimal SecondHigh { get; set; } public decimal SecondLow { get; set; } public decimal SecondClose { get; set; } public decimal SecondChange { get; set; } public decimal SecondPreChange { get; set; } } }
我们使用反序列化的方法读入这些数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Text; namespace ZL.Stock { public class Utility { public static StockAnalyzer CreateAnalyzerFromJson(string json) { var lst = JsonConvert.DeserializeObject<List<StockFirstTrade>>(json); return new StockAnalyzer(lst); } } }
我们创建的分析类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System.Collections.Generic; namespace ZL.Stock { public class StockAnalyzer { public List<StockFirstTrade> StockFirstTrades { get; private set; } public StockAnalyzer(List<StockFirstTrade> stockFirstTrades) { StockFirstTrades = stockFirstTrades; } } }
我们使用Flunt API创建分析使用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ZL.Stock { public static class StockAnalyzerExt { public static StockFirstTrade 详细信息(this StockAnalyzer analyzer, string stockcode) { return analyzer.StockFirstTrades.FirstOrDefault(o => o.TSCode == stockcode); } public static decimal 最高涨幅(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Max(o => o.FirstPreChange); } public static decimal 最低涨幅(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Min(o => o.FirstPreChange); } public static decimal 开盘价与最高价最小差(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Min(o => (o.FirstHigh -o.FirstOpen)); } public static decimal 开盘价就是最高价(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Count(o => (o.FirstHigh == o.FirstOpen)); } public static List<StockFirstTrade> 开盘价就是最高价列表(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Where(o => (o.FirstHigh == o.FirstOpen)).ToList(); } public static decimal 开盘价就是最低价(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Count(o => (o.FirstOpen == o.FirstLow )); } public static List<StockFirstTrade> 开盘价就是最低价列表(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Where(o => (o.FirstOpen == o.FirstLow )).ToList(); } public static decimal 开盘价与最低价最大差(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Max(o => (o.FirstOpen-o.FirstLow)); } public static List<StockFirstTrade> 最高价比开盘价高百分之10列表(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Where(o => (((o.FirstHigh - o.FirstOpen))/o.FirstOpen)>0.1m).ToList(); } public static List<StockFirstTrade> 最高价比开盘价高百分之几列表(this StockAnalyzer analyzer,decimal pre) { return analyzer.StockFirstTrades.Where(o => (((o.FirstHigh - o.FirstOpen)) / o.FirstOpen) > pre).ToList(); } public static List<StockFirstTrade> 第二天开盘上涨列表(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Where(o => (o.FirstClose<o.SecondOpen)).ToList(); } public static List<StockFirstTrade> 第二天上涨列表(this StockAnalyzer analyzer) { return analyzer.StockFirstTrades.Where(o => (o.SecondPreChange>0)).ToList(); } } }