UnitTest in .NET(Part 5)


Photo :UnitTesting

文 | Edison Zhou

上一篇我们学习了单元测试的核心技术:存根、模拟对象和隔离框架,它们是我们进行高质量单元测试的技术基础。本篇会集中在管理和组织单元测试的技术,以及如何确保在真实项目中进行高质量的单元测试。

测试的组织与层次 

测试项目的两种目录结构

(1)集成测试和单元测试在同一个项目里,但放在不同的目录和命名空间里。基础类放在单独的文件夹里。

(2)集成测试和单元测试位于不同的项目中,有不同的命名空间。

实践中推荐使用第二种目录结构,因为如果我们不把这两种测试分开,人们可能就不会经常地运行这些测试。既然测试都写好了,为什么人们不愿意按照需要运行它们呢?一个原因是:开发人员有可能懒得运行测试,或者没有时间运行测试

构建绿色安全区

将集成测试和单元测试分开放置,其实就给团队的开发人员构建了绿色安全区,这个区只包含单元测试

因为集成测试的本质决定了它运行时间较长,开发人员很有可能每天运行多次单元测试,较少运行集成测试

单元测试全部通过至少可以使开发人员对代码质量比较有信心,专注于提高编码效率。而且我们应该将测试自动化,编写每日构建脚本,并借助持续集成工具帮助我们自动执行这些脚本。

将测试类映射到被测试代码

(1)将测试映射到项目

创建一个测试项目,用被测试项目的名字加上后缀.UnitTests来命名。

例如:EDC.MyLibrary → EDC.MyLibrary.UnitTests 和 EDC.MyLibrary.IntegrationTests,这种方法看起来简单直观,开发人员能够从项目名称找到对应的所有测试。

(2)将测试映射到类

① 每个被测试类或者被测试工作单元对应一个测试类:LogAnalyzer → LogAnalyzer.UnitTests

② 每个功能对应一个测试类:有一个LoginManager类,测试方法为ChangePassword(这个方法测试用例特别多,需要单独放在一个测试类里边) → 创建两个类 LoginManagerTests 和 LoginManagerTests-ChangePassword,前者只包含对ChangePassword方法的测试,后者包含该类其他所有测试。

(3)将测试映射到具体的工作单元入口

测试方法的命名应该有意义,这样人们可以很容易地找到所有相关的测试方法。

这里,回归一下第一篇中提到的测试方法名称的规范,一般包含三个部分:[UnitOfWorkName]_[ScenarioUnderTest]_[ExpectedBehavior]

  • UnitOfWorkName  被测试的方法、一组方法或者一组类

    Scenario  测试进行的假设条件,例如“登入失败”,“无效用户”或“密码正确”等

    ExpectedBehavior  在测试场景指定的条件下,你对被测试方法行为的预期 

示例:IsValidFileName_BadExtension_ReturnsFalse,IsValidFileName_EmptyName_Throws 等

注入横切关注点

当需要处理类似时间管理、异常或日志的横切关注点时,使用它们的地方会非常多,如果把它们实现成可注入的,产生的代码会很容易测试,但却很难阅读和理解。这里我们来看一个例子,假设应用程序使用当前时间进行写日志,相关代码如下:

    public static class TimeLogger{public static string CreateMessage(string info){return DateTime.Now.ToShortDateString() + " " + info;}}

为了使这段代码容易测试,如果使用之前的依赖注入技术,那么我们需要创建一个ITimeProvider接口,还必须在每个用到DateTime的地方使用到这个接口。这样做非常耗时,实际上,还有更直接的方法解决这个问题。

Step1.创建一个名为SystemTime的定制类,在所有的产品代码里边使用这个定制类,而非标准的内建类DateTime。

    public class SystemTime{private static DateTime _date;public static void Set(DateTime custom){_date = custom;}public static void Reset(){_date = DateTime.MinValue;}public static DateTime Now{get{// 如果设置了时间,SystemTime就返回假时间,否则返回真时间if (_date != DateTime.MinValue){return _date;}return DateTime.Now;}}}

阅读这段代码,其中有一个小技巧:SystemTime类提供一个特殊方法Set,它会修改系统中的当前时间,也就是说,每个使用这个SystemTime类的人看到的都是你指定的日期和时间。有了这样的代码,每个使用这个SystemTime类的人看到的都会是你指定的日期和时间。

Step2.在测试项目中使用SystemTime进行测试。

    [TestFixture]public class TimeLoggerTests{[Test]public void SettingSystemTime_Always_ChangesTime(){SystemTime.Set(new DateTime(2000, 1, 1));string output = TimeLogger.CreateMessage("a");StringAssert.Contains("2000/1/1", output);}/// <summary>/// 在每个测试结束时重置日期/// </summary>[TearDown]public void AfterEachTest(){SystemTime.Reset();}}

在测试中,我们首先假定设置一个日期,然后进行断言。并且借助TearDown方法,确保当前测试不会改变其他测试的值。

Note : 这样做的好处就在于不用注入一大堆接口,我们所付出的代价仅仅在于在测试类中加入一个简单的[TearDown]方法,确保当前测试不会改变其他测试的值。

使用测试使测试代码可重用

推荐大家在测试代码中使用继承机制,通过实现基类,可以较好地展现面向对象的魔力。在实践中,一般有三种模式会被使用到:

(1)抽象测试基础结构类模式

    /// <summary>/// 测试类集成模式/// </summary>[TestFixture]public class BaseTestsClass{/// <summary>/// 重构为通用可读的工具方法,由派生类使用/// </summary>/// <returns>FakeLogger</returns>public ILogger FakeTheLogger(){LoggingFacility.Logger = Substitute.For<ILogger>();return LoggingFacility.Logger;}[TearDown]public void ClearLogger(){// 测试之间要重置静态资源LoggingFacility.Logger = null;}}[TestFixture]public class LogAnalyzerTests : BaseTestsClass{[Test]public void Analyze_EmptyFile_ThrowsException(){// 调用基类的辅助方法FakeTheLogger();LogAnalyzer analyzer = new LogAnalyzer();analyzer.Analyze("myemptyfile.txt");// 测试方法的其余部分}}

使用此模式要注意继承最好不要超过一层,如果继承层数过多,不仅可读性急剧下降,编译也很容易出错。

(2)测试类类模板模式

    /// <summary>/// 测试模板类模式/// </summary>[TestFixture]public abstract class TemplateStringParserTests{[Test]public abstract void TestGetStringVersionFromHeader_SingleDigit_Found();[Test]public abstract void TestGetStringVersionFromHeader_WithMinorVersion_Found();[Test]public abstract void TestGetStringVersionFromHeader_WithRevision_Found();}[TestFixture]public class XMLStrignParserTests : TemplateStringParserTests{protected IStringParser GetParser(string input){return new XMLStringParser(input);}[Test]public override void TestGetStringVersionFromHeader_SingleDigit_Found(){IStringParser parser = GetParser("<Header>1</Header>");string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual("1", versionFromHeader);}[Test]public override void TestGetStringVersionFromHeader_WithMinorVersion_Found(){IStringParser parser = GetParser("<Header>1.1</Header>");string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual("1.1", versionFromHeader);}[Test]public override void TestGetStringVersionFromHeader_WithRevision_Found(){IStringParser parser = GetParser("<Header>1.1.1</Header>");string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual("1.1", versionFromHeader);}}

使用此模式可以确保开发者不会遗忘重要的测试,基类包含了抽象的测试方法,派生类必须实现这些抽象方法。

(3)抽象测试驱动类模式

/// <summary>
/// 抽象“填空”测试驱动类模式
/// </summary>
public abstract class FillInTheBlankStringParserTests
{// 返回接口的抽象方法protected abstract IStringParser GetParser(string input);// 抽象输入方法(属性),为派生类提供特定格式的数据protected abstract string HeaderVersion_SingleDigit { get; }protected abstract string HeaderVersion_WithMinorVersion { get; }protected abstract string HeaderVersion_WithRevision { get; }// 如果需要,预先为派生类定义预期的输出public const string EXPECTED_SINGLE_DIGIT = "1";public const string EXPECTED_WITH_MINORVERSION = "1.1";public const string EXPECTED_WITH_REVISION = "1.1.1";[Test]public void TestGetStringVersionFromHeader_SingleDigit_Found()
{string input = HeaderVersion_SingleDigit;IStringParser parser = GetParser(input);string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual(EXPECTED_SINGLE_DIGIT, versionFromHeader);}[Test]public void TestGetStringVersionFromHeader_WithMinorVersion_Found()
{string input = HeaderVersion_WithMinorVersion;IStringParser parser = GetParser(input);string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual(EXPECTED_WITH_MINORVERSION, versionFromHeader);}[Test]public void TestGetStringVersionFromHeader_WithRevision_Found()
{string input = HeaderVersion_WithRevision;IStringParser parser = GetParser(input);string versionFromHeader = parser.GetTextVersionFromHeader();Assert.AreEqual(EXPECTED_WITH_REVISION, versionFromHeader);}
}public class DBLogStringParserTests : GenericParserTests<DBLogStringParser>
{protected override string GetInputHeaderSingleDigit()
{return "Header;1";}protected override string GetInputHeaderWithMinorVersion()
{return "Header;1.1";}protected override string GetInputHeaderWithRevision()
{return "Header;1.1.1";}
}

此模式在基类中实现测试方法,并提供派生类可以实现的抽象方法钩子。当然,只是大部分的测试代码在基类中,派生类也可以加入自己的特殊测试。

此模式的要点在于:你不是具体地测试一个类,而是测试产品代码中的一个接口或者基类。

当然,在.NET中我们也可以通过泛型来实现此模式,例如下面的代码:

    public abstract class GenericParserTests<T> where T : IStringParser // 01.定义参数的泛型约束{protected abstract string GetInputHeaderSingleDigit();protected abstract string GetInputHeaderWithMinorVersion();protected abstract string GetInputHeaderWithRevision();// 02.返回泛型变量而非接口protected T GetParser(string input)
{// 03.返回泛型return (T)Activator.CreateInstance(typeof(T), input);}[Test]public void TestGetStringVersionFromHeader_SingleDigit_Found()
{string input = GetInputHeaderSingleDigit();T parser = GetParser(input);bool result = parser.HasCorrectHeader();Assert.AreEqual(false, result);}[Test]public void TestGetStringVersionFromHeader_WithMinorVersion_Found()
{string input = GetInputHeaderWithMinorVersion();T parser = GetParser(input);bool result = parser.HasCorrectHeader();Assert.AreEqual(false, result);}[Test]public void TestGetStringVersionFromHeader_WithRevision_Found()
{string input = GetInputHeaderWithRevision();T parser = GetParser(input);bool result = parser.HasCorrectHeader();Assert.AreEqual(false, result);}}public class DBLogStringParserTests : GenericParserTests<DBLogStringParser>{protected override string GetInputHeaderSingleDigit()
{return "Header;1";}protected override string GetInputHeaderWithMinorVersion()
{return "Header;1.1";}protected override string GetInputHeaderWithRevision()
{return "Header;1.1.1";}}

优秀单元测试的支柱 

要编写优秀的单元测试,它们应该同时具有 可靠性可维护性 及 可读性

编写可靠的单元测试

一个可靠的测试能让你觉得自己对事态了如指掌,能够从容应对。以下是一些指导原则和技术:

(1)决定何时删除或修改测试

一旦测试写好并通过,通常我们不应该修改或删除这些测试,因为它们是我们得绿色保护网。但是,有时候我们还是需要修改或者删除测试,所以需要理解什么情况下修改或删除测试会带来问题,什么情况下又是合理的。一般来说,如果有产品缺陷、测试缺陷、语义或者API更改或者是由于冲突或无效测试,我们需要修改和删除测试代码。

(2)避免测试中的逻辑

随着测试中逻辑的增多,出现测试缺陷的几率就会呈现指数倍的增长。如果单元测试中包含了下列语句就是包含了不应该有的逻辑:

  • switch、if或else语句;

  • foreach、for或while循环;

这种做法不值得推荐,因为这样的测试可读性较差,也比较脆弱。通常来说,一个单元测试应该是一系列方法的调用和断言,但是不包含控制流程语句,甚至不应该将断言语句放在try-catch中

(3)只测试一个关注点

如果我们的单元测试对多个对象进行了断言,那么这个测试有可能测试了多个关注点。在一个单元测试中验证多个关注点会使得事情变得复杂,却没有什么价值。你应该在分开的、独立的单元测试中验证多余的关注点,这样才能发现真正失败的地方。

(4)把单元测试和集成测试分开

掐面讨论了测试的绿色安全区,我们需要的就是准备一个单独的单元测试项目,项目中仅包含那些在内存中运行,结果稳定,可重复执行的测试。

(5)用代码审查确保代码覆盖率

如果覆盖率低于20%,说明我们缺少很多测试,我们不会知道下一个开发人员将怎么修改我们得代码。如果没有回失败的测试,可能就不会发现这些错误。

编写可维护的单元测试

可维护性是大多数开发者在编写单元测试时面对的核心问题之一。为此我们需要:

(1)只测试公共契约

(2)删除重复测试(去除重复代码)

(3)实施测试隔离

测试隔离的基本概念是:一个测试应该总是在它自己的小世界中运行,与其他类似或不同的工作的测试隔离,甚至不知道其他测试的存在。

编写可读的单元测试

不可读的测试几乎没有任何意义,它是我们向项目的下一代开发者讲述的故事,帮助开发者理解一个应用程序的组成及其开端。

(1)单元测试命名

这个前面我们讨论过,应该包括三部分:被测试方法名_测试场景_预期行为,如果开发人员都是用这种规范,其他的开发人员就能很容易进入项目,理解测试。

(2)变量命名

通过合理命名变量,你可以确保阅读测试的人可以尽快地理解你要验证什么(相对于理解产品代码中你想要实现什么)。请看下面的一个例子:

    [Test]public void BadlyNameTest()
{LogAnalyzer log = new LogAnalyzer();int result = log.GetLineCount("abc.txt");Assert.AreEqual(-100, result);}[Test]public void GoodNameTest()
{LogAnalyzer log = new LogAnalyzer();int result = log.GetLineCount("abc.txt");const int COULD_NOT_READ_FILE = -100;Assert.AreEqual(-COULD_NOT_READ_FILE, result);}

经过改进后,我们会很容易理解这个返回值的意义。

(3)有意义的断言

只有当测试确实需要,并且找不到别的办法使测试更清晰时,你才应该编写定制的断言信息。编写好的断言信息就像编写好的异常信息,一不小心就会犯错,使读者产生误解,浪费他们的时间。

(4)断言和操作分离

为了可读性,请不要把断言和方法调用写在同一行。

// 断言和操作写在了同一行
Assert.AreEqual(-COULD_NOT_READ_FILE, log.GetLineCount("abc.txt"));

小结 

这一篇我们学习了:

  • 尽量将测试自动化,尽可能多次地运行测试,尽可能持续地进行产品交付;

  • 把集成测试和单元测试分开,为整个团队构建一个绿色安全区,该区域中所有的测试都必须通过;

  • 按照项目和类型组织测试,把测试分别放在不同的目录、文件夹或者命名空间中;

  • 使用测试类层次,对一个层次中相关的几个类进行同一组测试,或者对共享一个通用接口或者基类的类型进行同一组测试;

  • 优秀单元测试具有三大支柱:可读性、可维护性与可靠性,它们相辅相成。

  • 如果人们能读懂你的测试,就能理解和维护测试,如果测试能够通过,它们也会信任测试。一旦实现这个目标,你就能知道系统是否正常工作,具有了处理变更和在需要时修改代码的能力;

参考资料  

(1)Roy Osherove 著,金迎 译,《单元测试的艺术(第2版)》

2020后记:虽然这是一篇发表于2015年的文章,但我至今觉得仍有价值。因为我发现在.NET圈,还是有很多童鞋不了解单元测试和不喜欢写单元测试,不懂其价值就不会形成增强回路。所谓增强回路,就是我单元测试写的越多,以后修改代码增加功能就不容易出现Bug(这里主要指SIT阶段、UAT阶段乃至线上),越不容易出现Bug我提交的代码质量就越高,就会增强我写单元测试的愿望,形成一个回路。在我现在的实践中,是把单元测试加入了持续集成构建任务中的,每次组员提交代码都会触发构建任务,去编译项目,去跑单元测试,只要单元测试没有跑过就会邮件或者通知发出来告诉我,我会知道是谁提交的代码居然没有跑单元测试就提交了,我就会找他改Bug了,呵呵。

The End

「 码字不易,也希望各位看官看完觉得还行就在本文右下方顺手点个“在看”,那就是对我最大的鼓励!如果觉得很好,也可以转发给你的朋友,让更多人看到,独乐乐不如众乐乐,是吧?

往期精彩回顾

.NET Core on K8S学习与实践系列文章索引目录

.NET Core 微服务学习与实践系列文章索引目录

【资料】2019 .NET China Conf 大会资料下载

【视频】2019 .NET China Conf 大会视频发布

2019 .NET China Conf 路一直都在,社区会更好

基于Jenkins的开发测试全流程持续集成实践

基于Jenkins Pipeline的.NET Core持续集成实践

【导读】我读经典,心旷神怡 - 经典书籍读后感汇总

【导读】我的诗和远方 - 也读唐诗与旅游游记汇总


点个【在看】如何?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 大小写_java中如何进行大小写字母转换?

展开全部1.创建工程&#xff0c;或使用已有工程&#xff0c;在工程下创建包&#xff0c;包内新建一个类&#xff0c;我e69da5e887aa3231313335323631343130323136353331333365653262命名为Cases类&#xff0c;大家根据自己喜好随便命名&#xff0c;但请保持类名与文件名一致。2…

大量SQL的解决方案——sdmap

大量SQL的解决方案——sdmap最近看到群里面经常讨论大型应用中 SQL的管理办法&#xff0c;有人说用 EF/ EFCore&#xff0c;但很多人不信任它生成 SQL的语句&#xff1b;有人说用 Dapper&#xff0c;但将 SQL写到代码中有些人觉得不合适&#xff1b;有人提出用存储过程&#xf…

java 最小堆_堆排序 最大堆 最小堆 Java 实现

堆一点疑惑&#xff0c;堆排序是就地排序&#xff0c;所以空间复杂度是 O(1)。但是&#xff0c;比如我有一个数组&#xff0c;建立一个最小堆&#xff0c;然后每次取出最小堆的顶点。建立最小堆需要额外空间&#xff1f;不深究了&#xff0c;归并排序需要额外空间。堆是完全二叉…

提高文档翻译效率神器:VS Code 插件之 Translator Helper

微软 Docs 网站上线之后&#xff0c;我发现很多中文内容是由机器翻译的&#xff0c;可读性比较差。2017 年开始我参与了中文文档的本地化工作&#xff0c;对机器翻译的文本进行校对。Docs 的内容全部托管在 GitHub 上&#xff0c;参与者可以 fork 仓库后进行修改&#xff0c;然…

java 导入导出 插件_Java最优的Excel导入/导出工具开发,你用过吗?

关注程序员7歌&#xff0c;一起用技术改变世界在我们实际开发中经常会遇到Excel的导入与导出功能&#xff0c;而目前Excel操作工具也是数不甚数啊&#xff0c;但是7歌用过很多&#xff0c;还是觉得最近发现的tool-excel好用&#xff0c;让你实现一语句代码就能完成Excel功能。首…

java gt_JAVA泛型知识--gt; lt;? extends Tgt;和lt;? super Tgt;

extends T> 和 super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念extends T> 是指 “上界通配符(Upper Bounds Wildcards)”super T> 是指 “下界通配符(Lower Bounds Wildcards)”1. 为什么要用通配符和边界&#xff1f;使用泛型的过程…

使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)

工作上有个业务&#xff0c;.Net Core WebAPI作为服务端&#xff0c;需要将运行过程中产生的日志分类&#xff0c;并实时推送到各种终端进行报警&#xff0c;终端有桌面(WPF)、移动(Xamarin.Forms)、网站(Angular.JS)等&#xff0c;使用SignalR进行警报日志推送。微信公众号&am…

java图形包_java流布局图形包

第一步&#xff1a;导包import java.awt.FlowLayout;import javax.swing.JButton;import javax.swing.JFrame;第二步&#xff1a;定义类public class TestFlayOut {public static void main(String[] args) {JFrame jf new JFrame("流布局DEMO"); //建立一个窗口Flo…

ASP.Net Core 3.1 中使用JWT认证

JWT认证简单介绍关于Jwt的介绍网上很多&#xff0c;此处不在赘述&#xff0c;我们主要看看jwt的结构。JWT主要由三部分组成&#xff0c;如下&#xff1a;HEADER.PAYLOAD.SIGNATUREHEADER包含token的元数据&#xff0c;主要是加密算法&#xff0c;和签名的类型&#xff0c;如下面…

C++继承的继承方式

继承方式一共有三种&#xff1a; 1.公共继承 2.保护继承 3.私有继承

与其每天重复,不如试着构建「正反馈闭环」

大家好&#xff0c;我是Z哥。我们程序员应该算是相对比较有毅力的一个群体了&#xff0c;毕竟入行的高门槛首先就刷掉了一批无法坚持到胜任coding工作的人。况且&#xff0c;新技术的更迭相比其它行业快的多&#xff0c;需要持续学习。即使这样&#xff0c;肯定每个程序员都还有…

2008至今,Chrome如何成长为霸主

2008 年&#xff0c;微软的 Internet Explorer&#xff08;IE&#xff09;浏览器几乎占据了全球浏览器市场份额的 60%&#xff1b;Mozilla 的 Firefox 紧随其后&#xff0c;市场份额约为三分之一&#xff1b;于当年 9 月 2 日初亮相的 Chrome 浏览器则仅占有 0.3% 的市场份额。…

C++继承中构造和析构顺序

子类继承父类后&#xff0c;当创建子类对象&#xff0c;也会调用父类的构造函数 问题&#xff1a;父类和子类的构造和析构顺序是谁先谁后&#xff1f; 代码如下&#xff1a; #include <iostream> using namespace std; //继承中的构造和析构顺序class Base {public:Ba…

linux java -xms_java.lang.OutOfMemoryError及解决方法

主要有3种比较常见的OutOfMemory Error&#xff1a;Java.lang.OutOfMemoryError: Java heap spacejava.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: GC overhead limit exceeded1. java.lang.OutOfMemoryError: Java heap spaceJava heap space&#xff…

Excel模板导出之导出教材订购表

说明本教程主要说明如果使用Magicodes.IE.Excel完成教材订购表的Excel模板导出。要点本教程使用Magicodes.IE.Excel来完成Excel模板导出需要通过创建Dto来完成导出需要按要求准备Excel模板主要步骤1.安装包Magicodes.IE.Excel在本篇教程中&#xff0c;我们仅演示使用Excel来完成…

BeetleX网关非法Url请求拦截插件

一旦网站部署到互联网上&#xff0c;就会受到一些非法的请求&#xff0c;而这些请求的Url都是一些特定的路径或带上一些无关请求的字符用于探测一些服务存在的问题&#xff1b;还有这些请求会落到日志中&#xff0c;导致日志臃肿和转发到后台服务带来处理上的损耗。为了应对拦截…

java安装版本哪种好_我怎么知道我安装了哪个版本的Java?

问题描述我想开始玩java(最终到了可以为android或web编写基本小程序的地步)&#xff0c;但是我已经在我的计算机上(从过去的实验中)弄糟了java。我不确定我拥有哪个版本的Java&#xff0c;并且想知道是否有命令查看已安装且处于活动状态的Java版本。另外&#xff0c;哪个版本效…

UnitTest in .NET 系列文章目录

Photo &#xff1a;.NET单元测试的艺术文 | Edison Zhou这几天陆陆续续更新了UnitTest in .NET这个系列的文章&#xff0c;现将其总结成一个小目录。此外&#xff0c;特别推荐阅读Roy Osherove的《单元测试的艺术》一书&#xff0c;此文也是该书的精华内容的学习笔记总结。文章…

java蝇量模式_Head First设计模式——蝇量和解释器模式

蝇量蝇量模式&#xff1a;如果让某个类的一个实例能用来提供许多“虚拟实例”&#xff0c;就使用蝇量模式。在一个设计房子的平台中&#xff0c;周围要加上一些树&#xff0c;树有一个坐标XY坐标位置&#xff0c;而且可以根据树的年龄动态将自己绘制出来。如果我们创建许多树之…

如何运用领域驱动设计 - 聚合

概述DDD实战与进阶 - 值对象如何运用DDD - 实体如何运用领域驱动设计 - 领域服务在前几篇的博文中&#xff0c;我们已经学习到了如何运用实体和值对象。随着我们所在领域的不断深入&#xff0c;领域模型变得逐渐清晰&#xff0c;我们已经建立了足够丰富的实体和值对象。但随着实…