计算机系统设计与架构的实践方法
本文介绍了系统设计和架构。它引入了一个新概念,让初学者可以轻松地分解和设计复杂的软件系统。它采用基于模块化的开发方法,并结合了我们在日常生活中都在实践的一种模式。
目录
- 引言
- 概述
- 2.1. 今天的解决方案提供商做什么?
- 2.2. 为什么我们应该好好设计系统?我需要回答这个问题吗?
- 设计系统的途径
- 3.1. 用简单的概念分解系统
- 开始设计我们的文档管理系统
- 通用建议
- 5.1. 面向对象编程(OOP)概念
- 5.2. 设计模式
- 5.3. 基于模块化的开发和模块的重用
- 5.4. 快速开发数据访问层(DAL)
- 5.5. 将操作分组到类中
- 5.6. 保持前端层独立
- 5.7. 可视化系统
- 5.8. 命名约定
- 以全新的视角思考,并以新手的姿态对待
- 给系统设计师的额外提示
- 最后说明
- 巧合
- 摘要
- 历史
- 参考文献
1. 引言
我们的业务分析师上周去国外拜访了一位选择我们公司开发其内容管理系统的新客户。这位分析师花了两个星期的时间,带着一份详细的业务模型文档回来,他和同事仓促地准备了正式的需求规格说明。在此过程中,他们与客户进行了沟通以澄清一些需求。正式的需求文档发送给客户进行初步审批,并返回了一些小的修改。业务分析师亲自进行了这些小的修改,以获得客户的最终批准。这是一项不错的努力,我们在第一次见到客户三周后,获得了稳定的需求集。业务分析师将业务模型发送给了系统架构师。很快,非功能性需求文档就完成了,并获得了客户的无修改批准。接下来的直接流程是定义用例。系统架构师与系统分析师一起最终确定了系统设计。数据库管理员随后识别/定义了系统的实体及其关系以及DDL(数据定义语言)。UI(用户界面)设计师设计了系统用户界面,以完成初步设计工作。最后,定义了部署模型,并在获得客户的所有必需审批后,我们稳定地开始了系统实施。
我刚才描述的是一个虚构的系统开发过程,在现实世界中的适用性有限。在实际应用中,许多变量会让人觉得系统设计/开发方法需要根据客户和项目进行调整。本文是一项挑战性的尝试,旨在引入一个能适应当今所有极端情况的现代系统设计要求的概念(模型)。
设计系统的第一步需要对问题域进行深入研究。这意味着需要花费时间,然后是金钱,这会威胁到那些对正确设计系统的优势没有足够认识的客户。当今需求的多样性对个人经验的价值提出了质疑,这只会使情况变得更糟。经验会给你信心,但如果一个人很快地说“哦,这和我们去年交付的解决方案一样”,那么他很快就会因为交付错误的产品而失去客户。
市面上存在优秀的设计方法,但由于时间限制(客户催促快速发布,而开发人员在极其紧迫的截止日期下挣扎),解决方案提供商往往不敢应用它们。如今,解决方案提供商倾向于在没有充分设计的情况下实现系统。通常,开发是在粗略评估项目深度之后开始的。系统需求规格经常在开发过程中被更改。这是由于项目初期业务需求不够清晰,以及领域专业知识不足。客户通常要等到解决方案提供商发布系统后才能理解它。客户使用系统最初的几个版本来理解项目的方向并进行修正。不幸的是,其中一些更改会动摇系统的整个基础,迫使设计师重新思考最初的设计。但这些影响很难被客户非技术性地注意到。客户的资金和开发人员的24小时工作时间之间会发生一场战斗。
客户在项目初期就估算了正确进入市场的时间。软件市场的激烈竞争使得做出精确的决策变得至关重要。最重要的是及时进入市场。
2. 概述
2.1 今天的解决方案提供商做什么?
你不需要土木工程专业知识就能理解,在不知道成品建筑层数的情况下建造地基是不稳定的。同样的哲学也适用于软件架构。扩展一个为有限功能集开发的系统只会增加更多问题而非功能。很高比例的解决方案提供商推崇临时性的开发方法。他们使用极限编程来应对紧迫的截止日期。客户也在鼓励他们,做着一旦系统盈利就进行重构的美梦。客户将系统设计视为低优先级处理的另一个原因是设计师/分析师调查/设计系统所需的时间。根据一些术语,客户是上帝,定期期望解决方案提供商提供有形输出。但一个 proper system design process 可能会让他们等待数月。为了满足客户的需求,解决方案提供商通常从项目开始那天起就计划每两周或每月发布系统。这种极端的需要会在开发过程中耗尽资源。
开发人员习惯于从一个缩减版的系统开始开发过程,然后逐渐围绕它修补系统的其余部分。这会导致产品不稳定,代码重复且分组不当。此外,它会产生依赖程序员的代码模块,因为每个模块都是由不同技能集的个人编写的。不一致的代码使调试系统成为一场噩梦。这会在许多直接/间接的方面花费时间和金钱,同时使其无法扩展。
2.2. 为什么我们应该好好设计系统?我需要回答这个问题吗?
精心设计的系统在应对软件开发生命周期中的常见问题时,总能获得战略优势。一个设计良好的系统在处理变更和升级时,比拼凑起来的系统更便宜。架构的严谨性规范了代码,同时支持和管理实施,具有可预测的短开发周期。设计的连贯性允许管理者以最少的培训从项目的不同部分调动/调离资源。这些是精心设计的系统带来的一些好处。
为了顺利推动系统开发工作,设计者应在流程的早期阶段引入稳定的设计/编码模式,使每个开发人员都能熟练掌握。这也有助于消除项目后期对架构师的依赖。
3. 设计系统的途径
我首先要强调的是,我在设计系统时不会绘制著名的用例图和序列图。事实上,我有一种方法可以让你更好地理解系统,并能直接绘制类图。但在绘制类图之前,我鼓励你绘制活动图和/或系统概述图和/或模块交互图和/或任何其他有助于你充分理解(感受)系统的图表。
正如我多次重申的,设计系统主要取决于设计师对问题域的理解。所以你理解得越好,系统设计就越容易。然而,在现代,很难假设设计师在开始设计之前有足够的时间来充分调查系统。
作为第一步,设计者必须是问题域的大师。一份写得好的系统需求规格(SRS)文档可以用作系统的介绍。系统设计师应该反复阅读它,并仔细理解文档中隐藏在奇怪角落里的每一个重要特性。但在大多数情况下,设计师在系统设计之前得不到一个 proper SRS。(至少,服务型公司是这种情况。)相反,他被迫通过初步的业务会议和电话会议来获取需求。这种不幸的情况是设计师考虑一次性原型甚至两个原型的理想情况。原型是在你投入未知领域之前定义产品规格的最佳且最便宜的方式。在与原型系统发生分歧的情况下,设计师应该有策略地引导客户为他撰写规格。传统的建议是等到深入了解系统后再开始设计。但实际上,你可以通过参考在线资源(如文章、开源项目和其他类似产品)来加快速度,而不过度依赖SRS文档(注意:新手总是需要资深人士的帮助来正确识别资源池,因为新人很难一眼看出复杂的业务需求)。获得的领域专业知识不仅能保证良好的设计,还能帮助更好地预测系统的未来,并为后续扩展留出足够的空间。此外,新手必须注意不要低估重要的业务需求,而仅从技术角度评估它们。这是另一个关键领域,客户可能会非常固执,甚至决定拒绝该系统,抱怨解决方案提供商交付了错误的产品。
通过提问来探索系统。我有一个技术,自从我设计的第一个系统开始就一直在使用。我在这里把它作为设计新系统的第二步。打开一个记事本或拿一张纸,然后开始提问关于系统的问题。在这个努力中,你可以自由地提出任何问题,但为了更好地开始,请从下面列出的三个主要问题(关于系统的输入、处理和输出)开始。
- 系统的输入是什么?
- 系统的处理是什么?
- 系统的输出是什么?
为了优化吞吐量,开始用全新的思维方式重新思考系统,忘掉你通过各种资源收集到的所有信息。这种方法将帮助你理解产品的定义,即使产品规格不是很清晰。当你练习这种技术时,你会惊讶于它能打开系统的新领域。正如你可能已经知道的,根据这种方法,有趣的是,提问者必须是回答者,而这个回答者必须是你。有时你可能无法回答某个特定问题。但至少你会有要找答案的问题列表。我见过设计师在项目需求定义得很宽泛(模糊)时感到挣扎。他们不知道从哪里开始,也不知道该问什么或不该问什么。如果你也遇到同样的问题,试试这个技术,看看它是否有效。
一个好的问题是能够根据答案产生几个派生问题的问题。所以对一个问题的回答可能产生另一个问题(一个或多个),或者一个叶子级别的操作,或者系统的功能(系统的用例)。这项技术没有规则或定义。提问和回答过程可以根据从答案产生的问题自由地向多个方向发展。无法回答的问题可能需要转达给客户。而那些客户没有回答的问题,系统设计师必须创造性地处理,以免影响系统的稳健性。你可以创造性地将它们分组到单独的模块中,以便稍后实现。
例如,让我们尝试应用这种方法来发现一个简单的文档管理系统(DMS)的功能列表。提问将从非常抽象的层面开始,包含三个主要问题。
- 这个有什么输入?
Word、txt、媒体等文件。(我认为是任何类型的文件。) - 系统的处理是什么?
这是一个文档管理系统,它将帮助我的客户妥善管理他的文档。 - 有什么输出?
允许用户在线查看文件,并允许他们签出/删除文件。
如果你对以上三个问题都有相当好的回答,我想说你已经理解了系统。上述三个问题几乎可以用来发现任何系统。你对这三个问题的回答将产生更多派生问题。这个过程将帮助我们发现所有的用例,或者用简单的术语来说,系统的叶子级别函数。(叶子级别函数是一个独立的、细粒度的操作,描述了系统的特定功能。)它还有助于我们发现所有的外部/内部(系统)参与者。提问和写答案的过程将继续下去,直到我们达到无法再问答案的叶子级别函数。
让我们更深入地分析第一个问题(即关于输入的问题),看看它能给我们带来什么。只需允许谁、什么、何时、为何、如何等来为你创建问题。在下面的样本中,我使用了“*”来表示创建问题的词语。你也可以遵循编号顺序,以便更好地识别其漂移方式。
1. 这个有什么输入?
Word、txt、媒体等文件。(我认为是任何类型的文件。)
2. 用户如何上传文档?
用户通过一个Web界面来浏览、选择和上传文档。
2.1 用户如何访问这个Web界面?
用户必须注册系统才能通过提供的凭据访问它。
2.1.1 用户如何注册系统?
所有员工都将由DMS系统管理员注册。这将在他们部署系统时发生。2.1.1.1 管理员如何知道员工的个人资料,员工如何获得凭据?
这两个都将是手动过程,管理员将从人力资源部门获取员工个人资料数据,每位员工将通过手动致电系统管理员获得访问代码。注意:这里我们发现系统管理员*只能*创建用户,显然他也可以删除/更新用户。
2.1.1.1.1 员工的隐私怎么办?
系统应该允许员工更改他们的密码。2.1.2 用户注册页面是否是普通用户的受限页面?
是的,只有系统管理员能看到此页面。
2.2 用户到底是谁?
本系统的用户*可以来自四个不同的部门*,即行政部、会计部、市场部和开发部。每个用户只能属于这四个部门中的一个。所以这意味着该系统的每个用户将具有不同的权限和访问权限。
2.2.1 不同部门的用户是否需要不同的权限?
是的。每个部门的用户将具有不同的权限。市场部或开发部的用户将拥有*默认权限*,他们可以上传和管理他们的文档,但会计部的用户可以*查看*市场部的文档,行政部除了执行系统的默认功能外,还可以查看任何其他部门的文档。(每个部门的权力和权限是:行政部 > 会计部 > 市场部 = 开发部)。2.2.1.1 用户的默认权限是什么?
这包括上传文档、在线查看文档、签出文档进行编辑以及删除文档。同样,上传的任何文档将自动在同一部门的用户之间共享。一个部门的文档只有在明确授予权限后才能与其他部门共享。该系统不支持私人文档(经客户软件需求规格确认)。注意:我不会由此创建问题。2.2.1.2 查看用户是什么意思?
这是在不需要文档所有者任何许可的情况下,访问其他用户上传的文档的权限。注意:我不会由此创建问题。注意:以上答案向我们介绍了两个模块,分别称为用户管理、安全和权限。为了进一步分析它们,你必须将它们分开处理,提问从每个模块的输入、处理和输出开始。我们现在不深入研究那个部分。
3. 文档如何在系统中流转?
用户*选择*并*上传*文档 > 系统将验证输入文件 > 文件*添加到“待审批”部分* > 管理员*审批并发布*文件 > 文件可供用户使用。
3.1 我们需要一个工作流管理系统吗?
我认为不需要,让我们硬编码工作流,因为我们不期望它们被动态调整。这不是一个非常复杂的系统。所以没有必要有一个单独的模块来管理工作流,我对此很满意。
现在,我们已经找到了一些属于文档上传部分的细粒度操作。我们将其列出如下
客户端
从本地目录选择文档
将字节流读入系统内存
通过Web发送到服务器
服务器端
从内存中读取字节流
将其写入本地临时位置以进行验证
输入验证模块验证输入文件
将验证后的文件移动到用户正确的共享位置
将此新文件添加到存储库系统,作为“待审批文件”
加载待管理员审批的文件以供其查看
系统管理员验证文件内容并批准其在线查看
将文件元数据状态从“待审批”更改为“已审批”
注意:图示的理想位置。
3.2 我们将在此处进行版本控制吗?
是的,我们需要版本控制。所以对现有文件的任何更新都将通过一个版本处理器,该处理器会分配正确的版本号。注意:这可以进一步深入分析。
3.3 当新文档成功发布时,是否需要通知用户?
拥有(上传)文档的用户将收到一封关于拒绝/接受状态的电子邮件。此外,我们还需要向订阅了通知的用户发送另一条通知,说明此操作的发生。注意:这会向系统引入一个名为通知模块的新模块。我也不深入研究这个部分。
3.3 用户可以撤销更新吗?
我需要和客户谈谈这个问题。
3.4 多个用户可以同时签出同一个文档吗?
现在先停下吧……
看看它是如何扩展的,只要你提问,它就会不断扩展。我省略了很多问题,因为文章越来越长,但我相信这个示例问答集能提供足够的指导来理解概念。需要多次重复此过程(一遍又一遍地进行问答过程),才能完全探索系统。首先,你必须识别系统的主要模块。我已经确定了几个模块,包括输入验证模块、用户管理模块、安全模块、通知模块,以及最重要的文档管理模块。模块重组是这项技术的重要组成部分。如果你能更快地识别系统的模块,那么设计就会更容易。没有经验的人在最初识别系统模块时总是会觉得更困难。但随着你多次练习这项技术,你就会获得这种能力。(这来自于经验,并且要更快地获得它,你可以对同一个系统反复应用这项技术,直到你对模块分解感到满意)。
在我早期的时候,我总是先识别出系统的所有叶子级别函数(细粒度的用例)。然后我将函数分组形成类(类会包含同类型的功能/操作)。这些类与上面图中的工作者很相似。随着组织中工作者数量的增加,你会添加经理来管理工作者组。软件系统也会这样做。同样,随着经理人数的增加,该组会被向下推,并会引入一些高级经理来控制它们。然而,在这个过程中,你还需要仔细地将系统的类分组以形成模块。(模块可以通过分组相似的类来形成)。经理人数和经理池的深度可以在模块的复杂性(或分组到模块中的工作者类的数量)来决定。这些模块将被视为一个组织的独立部门,每个模块将由一个或多个经理控制,位置考虑系统的复杂性。这些模块控制经理将帮助模块相互交互。
一旦你设计了几个系统,模块分解就会变得更容易,概念也会变得更友好。这种经验还会让你发现,有些模块在你设计的其他系统中会反复出现,所以你可以很容易地识别/重用它们。
你可以遵循这个过程来识别DMS系统的所有叶子级别函数以及主/子模块。
3.1. 用简单的概念分解系统
想想一个国家的政府机构或一个私人组织,以及它们是如何管理的。这两个复杂的组织都有层级结构,有许多部门(分支)连接到顶部。更重要的发现是它们被证明是坚如磐石的。在这种应用程序设计技术中,我们将软件系统视为一个真实世界的组织。仔细研究发现,所有软件系统都可以轻松地映射到一个真实世界的组织。这种映射有助于轻松识别/定义模块,并在整个项目生命周期中控制系统设计工作。当你能够通过纯粹的手动真实世界组织(更友好)来可视化软件系统时,你可以快速准确地响应需求变更,而不会损害系统的稳定性。当提出对软件系统的更改时,先将其虚拟地应用于手动系统,这将有助于确定在软件系统中应用它的最佳方式/位置。这种方法更适用于复杂的系统,但显然也适用于简单的系统。为了成功地将软件系统映射到真实世界的组织,你需要正确地分析软件系统,以解释它在纯手动环境中将如何处理。下一步是用软件模型自动化该手动过程。强烈建议在手动系统和软件系统之间保持相似的模块、通信模式、规则和命名约定,以获得更好的可视化。
让我们看看一个典型的组织如下,并尝试看看它将如何响应一个典型请求。
- CEO – 该系统的首席运营官作为组织的前端(软件系统的用户界面)进行交互。CEO的主要职责是与外部世界互动,并识别/指导经理按正确的顺序完成任务。CEO将直接映射到我们示例文档管理系统(DMS)的前端界面。
- 高级、中级和初级经理 – 经理的职责是按正确的顺序利用资源(员工或下级经理)来完成特定的任务。一个复杂的组织可能有几个级别的经理(高级经理、中级经理和初级经理)。更高级别的经理将使用一个或多个低(直接)级别的经理来完成任务。根据我们的图表,我们有三位高级经理,分别称为会计经理、运营经理和交付经理,每位经理都配备了一组初级经理来执行他们的职责。正如你所见,所有初级经理都配备了一组员工。
- 员工 – 员工执行所有细粒度的操作,并且通常,员工的任务是完全独立的,他们可以在不依赖任何其他任务或员工的情况下执行这些任务。
组织规则
- 更高级别的经理了解下级员工群体的能力。因此,他们知道选择哪个员工来完成任务。
- 相同级别的实体不允许相互通信,但如果两个或多个相同级别的经理需要一项任务来完成,那么该任务将由比需要完成该任务的两个或多个相同级别经理更高级别的经理处理。
- 一个实体只响应直接更高级别的实体,并且只利用直接较低级别的实体。
为了更好地理解组织,让我们看看这个组织将如何响应CEO提出的典型请求。
在这个示例中,该组织的CEO向“运营经理”(假设此操作独立于会计和交付经理)发出了一个名为“请求1”的请求。CEO知道他需要向哪个经理发出命令来成功处理这个特定请求。在这种情况下,“运营经理”收到了CEO的请求。他通过向“经理1”发出名为“请求1.1”的请求来继续处理。现在“运营经理”和CEO处于等待状态,而“经理1”正在处理请求的第一部分。“经理1”向“员工3”发出了一个名为“请求1.1.1”的请求来完成“任务3”。当“经理1”收到“任务3”的结果后,他会向同一个员工发出下一个请求来完成“任务1”。在收到结果后,“经理1”使用另一个名为“员工1”的员工来完成“任务3”。这完成了“请求1.1.2”,然后是“请求1.1”。“经理1”现在向“运营经理”响应“请求1.1”的结果。“运营经理”选择正确的初级经理来处理主请求的第二部分,即“请求1.2”。结果,“经理3”从“运营经理”那里收到了“请求1.2”,他通过向“员工4”发出“请求1.2.1”来完成“任务3”,从而响应了这个请求。最后,“经理3”向“运营经理”响应了“请求1.2”的结果,从而完成了“请求1”的全部处理。
在我们的系统设计方法中,我们将使用相同的概念来分解和设计软件系统。你将首先识别系统的细粒度/叶子级别函数(系统的任务),并将它们分组到类中,每个类负责相似类型的操作。这些类与上述组织图中的工作者非常相似。随着组织中工作者数量的增加,你会添加经理来管理工作者组。软件系统中也会这样做。同样,随着经理人数的增加,该组将被推下,并会引入一些高级经理来控制它们。然而,在这个过程中,你还需要小心地将系统的类分组以形成模块。(模块可以通过分组相似的类来形成)。经理人数和经理池的深度可以在模块的复杂性(或分组到模块中的工作者类的数量)来决定。这些模块将被视为一个组织的独立部门,每个模块将由一个或多个经理控制,位置考虑系统的复杂性。这些模块控制经理将帮助模块相互交互。
4. 开始设计我们的文档管理系统
对文档管理系统的初步分析发现了模块集及其叶子级别的函数。如前所述,在设计模块之前将其分解为更小的模块非常重要,系统模块化程度越高,系统维护就越容易。遵循这一概念,让我们水平和垂直地模块化我们的DMS系统。这将允许我们单独设计每个模块,将其视为主组织的一个独立部门。即使我们没有完全分析DMS系统,我们所发现的内容也足以用一个例子来解释这个概念。作为设计系统的第三步,我们将绘制如下系统架构图。此图将帮助我们直观地抽象系统并理解我们DMS系统中关键模块及其交互。
- 运营经理 – 运营经理作为所有部门的负责人,这些部门包括文档管理部、电子邮件/通知部以及用户管理器/安全部。运营经理将协调系统的所有功能,并根据需要帮助各部门之间进行交互。
- DMS、通知、用户管理器/安全 – 这些类似于一个通用组织的三个部门。我将叶子级别的员工分为三组:数据访问层、模板处理器和其他操作。DAL(数据访问层)负责所有与数据库相关的操作,如获取、添加、更新和删除数据,而“其他操作”部分负责任何其他所需的叶子级别操作。
- 通用操作 – 此模块执行通用操作。设计良好的通用操作模块可以轻松地在任何系统中重用。此部门由系统的所有组件或模块共享。在现实世界的组织中,你也会发现通用部门,如公司图书馆、公司食堂、接待处等。在大多数情况下,你可能会使用此模块来分组记录异常/事务、存储共享对象、处理错误等的类。
例如:文档发布后,需要发送多封电子邮件,所以运营经理会首先要求DMS部门的经理发布文档,并根据发布状态,运营经理会要求电子邮件/通知部门向文档所有者和其他订阅者发送正确的电子邮件。在此尝试中,系统使用了两个部门来完成任务,这两个部门都由运营经理控制。
让我们进一步分析我们文档管理系统的用户管理模块。让我们看看它如何支持用户管理模块的三个基本功能:添加、编辑和删除用户。(请参考下面的类图)。根据图表,首先,系统用户与DMS界面进行交互。在那里,我们只有一位名为“运营经理”的高级经理来控制整个系统,以及一位初级经理(负责用户管理模块)。名为“用户管理器”的经理直接与用户管理器模块中的DAL(数据访问层)的初级经理类通信。数据访问层仅通过名为“HandlerData”的抽象类响应相应的直接经理。在DAL内部,你可以看到有三个类来处理三种基本类型的数据相关操作,分别命名为Edit、Add和Remove data(在这个示例中,所有处理器都与上面描述的组织的工人相同)。类图的绘制完成了我们系统设计方法的第四步,也是最后一步。
例如:添加用户:用户被要求通过用户界面填写注册表单。当用户点击提交按钮时,UI端应执行所有必需的客户端验证以验证输入。然后,用户配置文件存储在名为User
的模型类中(它可以存储具有正确实体关系的用戶配置文件),并通过调用名为AddUser
的方法将其传递给名为ManagerOperation
的主DMS系统经理。主操作经理正确识别要通信的模块。因此,它调用名为UserManager
(在名为UserManager
的模块中)的经理的AddUser
方法,该方法通过初级经理类HandlerData
与DAL(数据访问层)通信,将数据存储在数据库中。完成此过程后,ManagerOperation
评估操作状态。然后,根据状态,它将调用通知模块的主经理,向新注册用户发送欢迎电子邮件。
对该系统的分析表明,一个通用的DMS系统可以很容易地映射到一个真实世界的组织。你可以继续构建完整的DMS,形成一个管理文档的组织。这种方法将产生一个一致的系统,该系统已将其功能分布到多个模块中,从而易于维护。此方法可用于设计任何系统,包括网站、Web服务、其他类型的服务(Windows服务等)、基于窗体的应用程序(Windows窗体等)或库。但是,如果你遵循任何其他架构/设计/编码模式,你可能会最终为一个网站使用一种方法,为创建基于窗体的应用程序使用另一种方法,而对于任何其他类型的应用程序,它可能完全是新的。
5. 通用建议
5.1. 面向对象编程(OOP)概念
面向对象编程(即OOP)是将独立对象定义和组合以形成软件系统的概念。它有四种基本技术:继承、封装、多态和抽象。如今,几乎所有流行的编程语言(如C++、C#、Java、PHP、Ruby、Python等)都支持OOP。
在我看来,我将OOP视为自然界的另一个未被注意到的理论,它等待着信息时代的合适时机被发现。它是在20世纪60年代被发现的,现在已成功地以自然界的虚拟形式(所谓的软件领域)使用。为了理解两者之间的关系,让我们考虑人体的某个部分(比如一只手)和一个设计良好的系统模块的功能。如果你仔细研究它们,你会意识到它们都提供了相似的通信接口,其中一组标准指令以所需方式驱动对象。OOP中没有什么新鲜事。它强烈强调软件中的模块化(就像自然界所做的那样)。这是已经知道和经历过的事情。
考虑到这些,欢迎设计人员在软件系统中适时使用任何面向对象的概念。它将实现复杂软件系统的灵活性和可维护性。请参考在线资源了解更多关于此主题的详细信息。
5.2. 设计模式
随着OOP概念在软件世界中变得越来越流行,设计师开始在他们做的每一个面向对象的设计中遇到类似类型的挑战。恰在此时,出现了一套被广泛接受的解决这些挑战性问题的方案,名为“设计模式”。设计模式描述了一套对常见软件设计问题反复出现的解决方案。这最初是由四位作者的书描述的,他们被称为“四人帮”或简称“GoF”。因此,他们的模式集被命名为GoF模式。而设计模式仍在不断发展。
设计模式将使设计更加出色,所以你应该使用它们。请参考在线资源了解更多关于设计模式的细节。
5.3. 基于模块化的开发和模块的重用
软件系统的模块必须被视为一个组织的部门。这意味着每个部门都必须有一个或多个经理,考虑模块的复杂性。“模块管理器”有助于模块间的通信,也有助于将它们绑定在一起以形成整个系统。一个更大的模块可以被分解成几个子模块,每个子模块必须被视为一个部门的子部门。建议在每个模块内设置单独的子模块来处理叶子级别的操作,如访问数据库、访问文件服务器等。模块的设计可以使用你在各种设计模式和面向对象编程概念方面的专业知识。
设计良好的模块也可以在其他系统中重用。一些著名的可重用模块包括:日志记录、通知、异常、文件目录IO等。当你开始采用这种方法时,一开始你会发现重用模块很困难。你会发现,你需要增强模块才能在每个新系统中重用它们。这会一直发生,直到你正确地定义了模块的规格,或者直到你学会设计真正的面向对象模块,但建议扩展模块的功能,直到它们足够丰富。这应该是一个持续的过程,将逐渐创建强大、更完整、功能丰富的模块,你可以在未来的项目中重用它们。
在这里,除了为客户交付一个智能产品外,如果你能为所有常用模块开发一个可扩展的框架,你还可以开辟一个新的市场。
5.4. 快速开发数据访问层(DAL)
分离数据访问层并快速确定下来非常重要。数据访问层(DAL)包含直接操作数据库的类,它是系统的引擎。这种分离将更好地模块化系统,也有助于开发人员编辑数据访问层(这会导致项目早期发生大量更改),而不会损害系统的其他部分。有关本节的更多信息,您可以参考以下两篇文章。
- 参考 1: https://codeproject.org.cn/cs/database/ModelCreator.asp
- 参考 2: https://codeproject.org.cn/cs/database/CSharp_Wrapper.asp
5.5. 将操作分组到类中
当识别出相似类型的函数或操作时,将它们分组在一起。在上面的示例中,我为Add、Edit、Delete操作创建了三个单独的类(参考图5)。这样,你就可以在类的各个方法中保持一致的编码,因为类的所有方法都在执行相似类型的操作。这样,你可以以相同的方式处理类中所有函数的异常/日志记录等。
5.6. 保持前端层独立
应用程序的用户界面或前端界面必须避免与应用程序逻辑相关的编码。这种方法的全部思想是能够在不损害系统其他部分的情况下替换前端层(用户界面)。
例如:以一个使用.NET/ASPX页面开发的网站为例。在这种情况下,应用程序的前端界面是ASPX页面及其代码隐藏文件的集合。如果你在实现系统时遵循了上述概念,那么你可能有一套不包含应用程序逻辑的ASPX页面,以及另一套包含核心应用程序逻辑的模块。这种简单的分解允许你引入Web服务接口(ASMX文件),将系统分布到两台机器上以扩展系统。这可以通过将ASPX文件替换为Web服务接口来实现。这将把应用程序的核心分离到一个单独的应用程序服务器,并将ASPX文件放到Web服务器上,正如上面的图表所解释的那样。
5.7. 可视化系统
这使得设计师能够直观地建模系统,以捕捉架构和组件的结构和行为。视觉抽象可以帮助你理解宏观图景,同时揭示系统的隐藏区域。这包括正确识别系统的组件;理解系统的每个组件如何组合在一起;理解每个组件如何相互通信;最后,使每个组件的设计保持一致。在我的实践中,如果系统非常复杂,我会绘制一个活动图和一个系统架构图(如图-4所示),但如果系统不那么复杂,我只会绘制后者。我鼓励你在开始设计之前可视化系统。
5.8. 命名约定
- 在命名变量、方法、类、模块以及任何其他内容时,请使用冗长且有意义/可读的名称。这也让你免于注释代码。
- 遵循技术所有者的命名约定;如果你的应用程序是使用Microsoft .NET开发的,那么就遵循Microsoft的标准,如果不是,那么就遵循它们的命名约定。这样做,你将能够直接在你的应用程序中使用它们的示例代码,而无需经过命名约定调整过程。
6. 以全新的视角思考,并以新手的姿态对待
如今,技术发展日新月异,每天都有新事物出现。设计师应该不断更新他们的知识,以了解最新的技术,这样他们就可以在设计中及早利用它们。但新技术可能带来的优势也可能导致设计师过度使用新技术。我曾听过一些设计师说:“我们按照X模型设计了我们的系统,它是最新的,而且它不建议这样做”,我的简单建议是不要因为你必须遵循最新技术或因为其他人都是这么做的,就在你的系统中制造瓶颈。没有放之四海而皆准的魔法公式,所以如果技术(或模型)不合适,要有勇气去改变。思考你需要什么。确定最适合你的。然后做出你的决定,让其他一切都靠边站。概念是为了帮助你并更好地指导你,而不是控制你。我邀请你发挥创造力,但我也想提醒你不要重新发明轮子。
7. 给系统设计师的额外提示
- 保持接口简单。接口应该捕获抽象的最小要素。不要泛化;泛化通常是错误的。同样,接口的承诺不能超过实现者知道如何提供的。
- 使其快速,而不是通用或强大。基本操作执行得快比更强大的操作执行得慢要好得多(当然,快速而强大的操作是最好的,如果你知道如何实现的话)。
- 不要隐藏强大功能。当低级别的抽象允许快速完成某事时,更高级别的不应该将这种能力掩盖在更通用的东西之下。
- 制作原型。如果系统功能中有任何新东西,第一次实现将不得不完全重做,以达到令人满意的(即可接受的小巧、快速且可维护)结果。如果你计划制作原型,成本会低很多。不幸的是,有时需要两个原型,尤其是在有很多创新时,但还是去做吧。
- 分而治之。这是一个解决难题的著名方法:将其分解为几个更容易的问题。
- 单独处理。通常要分别处理正常情况和最坏情况,因为两者的要求非常不同。正常情况必须快速。最坏情况必须考虑所有情况。
- 内存很便宜。因此,缓存昂贵计算的答案,而不是反复计算。
- 尽可能在后台计算。在交互式或实时系统中,在响应请求之前尽量少做工作是很好的。原因有两个:首先,快速响应对用户更好;其次,负载通常变化很大,稍后很可能会有空闲的处理器时间来做后台工作。
- 使操作原子化或可重启动。原子操作(通常称为事务)是指要么完成,要么没有效果的操作。
- 允许客户主导。如果需求尚未确定,请保持设计尽可能开放。你可以有策略地引导客户主导需求收集过程。
8. 最终说明
可能没有“最佳”的构建计算机系统的方法;更重要的是避免选择糟糕的方法。软件设计方法仍在发展中,可以被认为是相当新的。软件系统是已知手动过程的自动化,事实上,软件不能为物理世界中看不见/听不见的事物定义。每个过程在被重用时都会得到改进。物理世界的过程(手动过程)已经演变/重用了几代,并达到了完美。系统设计者可以通过观察可用的手动系统来设计自动化此类过程的软件系统。他们可以先研究手动系统,然后用软件系统自动化该手动系统。
因此,系统设计者可以采取的最安全的方法是,尽可能紧密地设计/实现与其在物理世界中平行的手动系统相对应的软件系统,当然,在可能的情况下进行改进。
9. 巧合
HIPO - Hierarchy plus Input-Process-Output,是一种用于系统自顶向下设计的技术,最早由IBM在20世纪70年代开发。上述提出的设计技术中的第二步在某种程度上等同于HIPO技术。但HIPO存在严重缺陷,导致其失宠。但现在出现了另一种新兴技术,名为HIPO-II,它在保持原有简洁性的同时,与最先进的设计方法竞争。
我在这里提出的技术是我自己的一项技术,不是我学过或听过的。我发现它对于应对当今的系统设计需求极其实用和有帮助。总之,它是一项一致、逻辑且可教授的技术。令人惊讶的是,这项技术的一部分与IBM的HIPO技术相匹配。我认为这是又一个例子,证明一切都在循环前进。
10. 总结
这里提出的系统设计方法可以分解为以下四个主要步骤。
- 识别、研究并精通问题域
- 通过提问来分析系统
- 识别细粒度用例或系统功能以及内部/外部参与者的列表。
- 通过将相似的功能分组来形成类。
- 通过将相似的类分组来形成模块。
- 识别模块的通信路径
- 通过图表可视化系统,显示模块交互
- 识别类的关系,并为每个模块单独绘制系统的类图
- 在模块之间正确放置“模块管理器”以将它们绑定在一起,从而形成完整的系统类图。
我有一些关于这个概念的书籍的想法和内容。但我宁愿将其保持在最低限度,担心这篇文章的下载时间。所以,我在此结束这篇文章,希望它写得足够好,让您理解这些概念。
11. 历史
- 19-11-2006
- 添加了摘要部分。
- 更新/改进了文章的整体措辞。
- 添加了图1。
- 更新了图2和图3。
- 更新了示例问答列表。
- 07-01-2007
- 添加了目录。
- 更新了OOP(第5.1节)和设计模式(第5.2节)下的段落。
- 29-07-2007
- 修订并更新。