《重构-改善既有代码设计》案例之C#版(5)

书接上文...

之前我把Performance类加了一个amount属性,后来我想了下.这个Performance属于输入类,最好是不要动它,因为一般我们的输入结构是不能够随我们自己的心意随意变动的.

如果我们确实需要给它加入属性的话.我们最好自己定一个中间转换类.所以我还原了这个Performance,另外加了一个PerformanceEnhance类继承原来的Performance类.仅仅为了加这个amount就加了一个类,我又不甘心,所以我又想了很久还有什么其他的属性或方法可以加入这个类.

突然就灵光乍现.如果一个Performance给定了剧目和观众数量的话.那么这个演出的价格和积分实际上就已经确定了.(软考里面不是学过函数依赖嘛,好像数据库范式也有提到字段依赖啥啥的)那么价格和积分可以作为这个PerformanceEnhance的属性

第一步如下

复制代码
 1     public class Performance
 2     {
 3         public string playID;
 4 
 5         public int audience;
 6     }
 7 
 8     public class PerformanceEnhance : Performance
 9     {
10         public  int Amount { get; set; }
11 
12         public  int VolumeCredits { get; set; }
13 
14         public  Play Play;
15     }
View Code
复制代码

第二步呢.就是一个新的非常重要的重构手法了:以多态取代条件表达式

面向对象的编程中其实有非常多的条件表达式可以用多态取代.

在这个案例中呢。我们注意到在计算金额的时候我们根据play.type做了switch处理.在计算积分的时候我们又根据play.type做了if判断.

试想一下,如果哪天又多了一个新的字段需要计算,比如演出需要支付演出税.我们又得再加条件表达式分别处理.

甚至如果哪天剧目的类型又加了一种 童话剧。那我们一改就要改3个地方条件表达式,非常容易改错以及遗漏。所以我们要 以多态取代条件表达式 重构

思路就是 把计算金额和积分的方法,移动到子类中,然后我们只需要在一开始创建对象之前,像工厂模式一样swtich case 创建好对象。后面就自然而然,不同的对象用不同的方法了。再也不要写额外的条件表达式了。有点抽象。还是具体看这个例子吧。

目前有两种类型,comedy和tragedy,所以我们至少需要新建两个子类,那么父类怎么抽出来呢。我又灵光一现,嗯,第一步我们不是引入了一个PerformanceEnhance类嘛,它可不可以作为父类呢。先来试试吧

复制代码
 1     public class PerformanceEnhance : Performance
 2     {
 3         public PerformanceEnhance(Performance performance, Dictionary<string, Play> _plays)
 4         {
 5             audience = performance.audience;
 6             playID = performance.playID;
 7             Play = _plays[performance.playID];
 8         }
 9 
10         public virtual int Amount { get; set; }
11 
12         public virtual int VolumeCredits { get; set; }
13 
14         public readonly Play Play;
15     }
16 
17     public class ComedyPerformance : PerformanceEnhance
18     {
19         public ComedyPerformance(Performance performance, Dictionary<string, Play> _plays) : base(performance, _plays)
20         {
21         }
22 
23         public override int Amount
24         {
25             get
26             {
27                 int result = 30000;
28                 if (audience > 20)
29                 {
30                     result += 1000 + 500 * (audience - 20);
31                 }
32 
33                 return result;
34             }
35         }
36 
37         public override int VolumeCredits
38         {
39             get
40             {
41                 int result = Math.Max(audience - 30, 0);
42 
43                 result += audience / 5;
44 
45                 return result;
46             }
47         }
48     }
49 
50     public class TragedyPerformance : PerformanceEnhance
51     {
52         public TragedyPerformance(Performance performance, Dictionary<string, Play> _plays) : base(performance, _plays)
53         {
54         }
55 
56         public override int Amount
57         {
58             get
59             {
60                 int result = 40000;
61                 if (audience > 30)
62                 {
63                     result += 1000 * (audience - 30);
64                 }
65 
66                 return result;
67             }
68         }
69 
70         public override int VolumeCredits
71         {
72             get { return Math.Max(audience - 30, 0); }
73         }
74     }
View Code
复制代码

这里步子有点大了。。。这是个不好的习惯,最好还是按照书上的方法一步一步来。

解释一下

我把AmountFor()和VolumeCreditsFor()这两个方法根据其中的条件表达式分别移入了ComedyPerformance和TragedyPerformance的Amount和VolumeCredits属性.这一步应该都能看懂没啥问题

为啥PerformanceEnhance的构造函数要引入performance,  _plays两个参数呢?嗨,这不是为了把原本performance里面的playid和audience取出来嘛,_plays 是另外用来给这个play字段赋值么

这里需要展示一下如何创建PerformanceEnhance的子类,并通过performance和_plays,给其赋值

复制代码
 1         private PerformanceEnhance GetEnhance(Performance perf, Dictionary<string, Play> _plays)
 2         {
 3             PerformanceEnhance performanceEnhance;
 4             switch (_plays[perf.playID].type)
 5             {
 6                 case "comedy":
 7                     performanceEnhance = new ComedyPerformance(perf, _plays);
 8                     break;
 9                 case "tragedy":
10                     performanceEnhance = new TragedyPerformance(perf, _plays);
11                     break;
12                 default:
13                     throw new Exception($"Unknown Type:{_plays[perf.playID].type}");
14             }
15 
16             return performanceEnhance;
17         }
View Code
复制代码

另外对于这个StatementData类我也有想法,

之前StatementData.Performances 字段用的是new List<Performance>(),我们已经换成new List<PerformanceEnhance>()

还专门建了一个EnrichStatementData方法用来给StatementData充入数据.其实duck不必

我们直接在StatementData的构造函数中干这些事就可以了呀 我们把TotalAmount()和TotalVolumeCredits()方法分别移入StatementData.TotalAmount属性和StatementData.TotalVolumeCredits属性中。然后在构造函数中给Customer和StatementData.Performances赋值

复制代码
 1     public class StatementData
 2     {
 3         public string Customer;
 4 
 5         public List<PerformanceEnhance> Performances = new List<PerformanceEnhance>();
 6 
 7         public int TotalAmount
 8         {
 9             get
10             {
11                 int result = 0;
12                 foreach (var perf in Performances)
13                 {
14                     result += perf.Amount;
15                 }
16 
17                 return result;
18             }
19         }
20 
21         public int TotalVolumeCredits
22         {
23             get
24             {
25                 int result = 0;
26                 foreach (var perf in Performances)
27                 {
28                     result += perf.VolumeCredits;
29                 }
30 
31                 return result;
32             }
33         }
34 
35         public StatementData(Invoice invoice, Dictionary<string, Play> _plays)
36         {
37             Customer = invoice.Customer;
38             foreach (var perf in invoice.performances)
39             {
40                 Performances.Add(GetEnhance(perf, _plays));
41             }
42         }
43 
44         private PerformanceEnhance GetEnhance(Performance perf, Dictionary<string, Play> _plays)
45         {
46             PerformanceEnhance performanceEnhance;
47             switch (_plays[perf.playID].type)
48             {
49                 case "comedy":
50                     performanceEnhance = new ComedyPerformance(perf, _plays);
51                     break;
52                 case "tragedy":
53                     performanceEnhance = new TragedyPerformance(perf, _plays);
54                     break;
55                 default:
56                     throw new Exception($"Unknown Type:{_plays[perf.playID].type}");
57             }
58 
59             return performanceEnhance;
60         }
61     }
View Code
复制代码

 

因此我们创建中转数据的时候也不用那个CreateStatementData()方法和EnrichStatementData()方法了

复制代码
 1         public string Statement()
 2         {
 3             StatementData statementData = new StatementData(_invoice, _plays);
 4             return RenderPlainText(statementData);
 5         }
 6 
 7         public string HtmlStatement()
 8         {
 9             StatementData statementData = new StatementData(_invoice, _plays);
10             return RenderHtml(statementData);
11         }
View Code
复制代码

这样调用多简单。完成

最后编译、启动

 

 

 OK 完成.这个案例到这里就结束啦。

 纯粹记录自己的学习

 如果你还看到了这里

 谢谢你花了时间读这个狗屁不通的帖子。

上一篇:《重构-改善既有代码设计》案例之C#版(4)

 

posted @   吾乃零陵上将军邢道荣  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示