C--面向对象编程入门指南-一-

110 阅读1小时+

C# 面向对象编程入门指南(一)

原文:Beginning C# object-oriented programming

协议:CC BY-NC-SA 4.0

零、简介

作为一名. NET 培训师和首席程序员,我的经验是,大多数人在掌握 C# 语言的语法方面没有问题。困扰和挫败许多人的是面向对象编程方法和设计的更高层次的概念。更糟糕的是,大多数介绍性的编程书籍和培训课程都忽略了这些概念,或者更糟糕的是,根本没有涉及到它们。我希望这本书能填补这一空白。我写这本书的目的有两个。我的第一个目标是为您提供理解 C# 编程基础所需的信息。更重要的是,我的第二个目标是为您提供掌握面向对象编程方法和设计的高级概念所需的信息。

这本书提供了你需要的知识来构建一个面向对象的编程解决方案,旨在解决一个业务问题。当您阅读本书时,您将首先学习如何分析应用的业务需求。接下来,您将对解决方案设计中涉及的对象和关系进行建模。最后,您将使用 C# 实现该解决方案。在这个过程中,您将学习软件设计的基础知识、统一建模语言(UML)、面向对象编程、C# 和 .NET 框架。

因为这是一本介绍性的书,所以它是你学习它所呈现的主题的起点。因此,这本书不是为了让你成为面向对象编程和 UML 的专家;也不是对 C# 和 .NET 框架;也不是对 Visual Studio 的深入研究。精通这些领域中的任何一个都需要相当多的时间和努力。我希望通过阅读这本书,你在面向对象编程方面的第一次经历将会是愉快的和容易理解的——并且这些经历将会灌输进一步学习的愿望。

目标受众

本书的目标读者是初学 C# 的程序员,他们希望在掌握 C# 语言基础的同时,掌握面向对象编程的基础。从面向过程编程模型过渡到面向对象模型的程序员也将从本书中受益。另外,还有很多 Visual Basic (VB)程序员想过渡到 C#。在过渡到 C# 之前,您必须理解面向对象编程的基础。

因为“初学者”的经验水平可能会有很大的差异,所以我在附录 A 中包含了一本讨论一些基本编程概念以及如何用 C# 实现它们的入门书。如果你是编程新手,我建议你复习一下这些概念。

活动和软件要求

学习最重要的一个方面是做。不骑自行车就学不会骑自行车,不出代码就学不会编程。任何成功的培训计划都需要包括理论和实践两个部分。

我在本书中包含了这两个部分。我希望你会认真对待我在每一章中添加的活动,并彻底完成它们——甚至是反复完成。与一些学生认为这些活动是“打字练习”相反,在这些活动中,你有机会将理论具体化,并对概念进行真正的模拟。我也鼓励你在一项活动中边工作边玩耍。不要害怕修改一些代码,看看会发生什么。一些最好的学习经历发生在学生“超越界限”的时候

前面章节中的 UML 建模活动是为使用 UMLet 的人设计的。我选择这个程序是因为它是一个很好的学习绘图工具。它允许您创建 UML 图,而无需添加许多与高端 CASE 工具相关的高级特性。UMLet 是一个免费的开源工具,可以从www.umlet.com下载。您也可以使用 Visio 等其他工具来完成这些活动。然而,你不需要一个工具来完成这些活动;纸和铅笔就可以了。

一旦你开始编码,你将需要安装 C# 的 Visual Studio 2012。我鼓励您安装帮助文件,并在完成活动时充分利用它们。第十三章讲述了如何创建 Windows 应用商店应用,并要求在 Windows 8 操作系统上安装 Visual Studio。后面的章节要求安装了 Pubs 和 Northwind 数据库的 Microsoft SQL Server 2008 或更高版本。附录 C 包括下载和安装示例数据库的说明。你可以在www.msdn.microsoft.com找到 Visual Studio 和 SQL Server 的免费速成版和试用版,以及 Windows 8 的试用版。

一、面向对象编程概述

为了给你学习面向对象编程(OOP)和 C# 打下基础,本章将简要介绍面向对象编程的历史和面向对象编程语言的特点。您将看到为什么面向对象编程在工业级分布式软件系统的开发中变得如此重要。您还将研究 C# 是如何发展成为主流应用编程语言之一的。

阅读本章后,您将熟悉以下内容:

  • 什么是面向对象编程
  • 为什么面向对象编程在工业级应用的开发中变得如此重要
  • 使编程语言面向对象的特征
  • C# 的历史和演变

OOP 是什么?

面向对象编程是一种软件开发方法,在这种方法中,软件的结构基于对象之间的交互来完成任务。这种交互采取在对象之间来回传递消息的形式。作为对消息的响应,对象可以执行一个动作。

如果你看看你是如何在你周围的世界中完成任务的,你可以看到你在一个面向对象的世界中互动。例如,如果你想去商店,你可以与一个汽车对象进行交互。汽车对象由其他对象组成,这些对象相互交互以完成将您带到商店的任务。你把钥匙放在点火物体里,转动它。这反过来向启动器对象发送一条消息(通过电信号),启动器对象与引擎对象交互以启动汽车。作为一名司机,你与系统的对象如何一起工作来启动汽车的逻辑是隔离的。您只需通过用钥匙执行点火对象的 start 方法来启动事件序列。然后,您等待成功或失败的响应(消息)。

类似地,软件程序的用户与完成任务所需的逻辑相隔离。例如,当您在文字处理器中打印页面时,您可以通过单击打印按钮来启动该操作。你被从需要发生的内部处理中隔离出来;你只需等待一个告诉你是否打印的回复。在软件程序中,按钮对象与打印机对象交互,打印机对象与实际打印机交互以完成打印页面的任务。

面向对象的历史

OOP 概念在 20 世纪 60 年代中期随着一种叫做 Simula 的编程语言出现,并在 20 世纪 70 年代随着 Smalltalk 的出现而进一步发展。尽管软件开发人员并没有完全接受 OOP 语言的这些早期进步,但是面向对象的方法继续发展。在 20 世纪 80 年代中期,人们对面向对象的方法又有了新的兴趣。具体来说,C++和 Eiffel 等 OOP 语言开始受到主流计算机程序员的欢迎。OOP 在 20 世纪 90 年代继续流行,最显著的是 Java 的出现和它吸引的大量追随者。在 2002 年,随着。微软引入了一种新的面向对象语言 C#(发音为 C-sharp ),并改进了他们广泛流行的现有语言 Visual Basic,使其现在真正面向对象。今天,OOP 语言继续蓬勃发展,是现代编程的中流砥柱。

为什么要用 OOP?

为什么 OOP 已经发展成为当今解决业务问题的广泛使用的范例?在 20 世纪 70 年代和 80 年代,面向过程的编程语言如 C、Pascal 和 Fortran 被广泛用于开发面向商业的软件系统。过程语言以线性方式组织程序,它们从上到下运行。换句话说,程序是一系列一个接一个运行的步骤。这种类型的编程对于由几百行代码组成的小程序来说很好,但是随着程序变大,它们变得难以管理和调试。

为了管理不断增长的程序规模,引入了结构化编程来将代码分解成可管理的称为函数或过程的片段。这是一个进步,但是随着程序执行更复杂的业务功能并与其他系统交互,结构化编程的以下缺点开始显现:

  • 程序变得更难维护。
  • 在不对系统的所有功能产生负面影响的情况下,很难改变现有的功能。
  • 新程序基本上是从零开始构建的。因此,以前的投资回报很少。
  • 编程不利于团队发展。程序员必须知道一个程序如何工作的每一个方面,不能把他们的努力孤立在系统的一个方面。
  • 很难将业务模型转化为编程模型。
  • 结构化编程在孤立的情况下工作得很好,但是不能很好地与其他系统集成。

除了这些缺点之外,计算系统的一些发展对结构化程序方法造成了进一步的压力,例如:

  • 非程序员通过图形用户界面和他们的台式计算机的结合,要求并获得了对程序的直接访问。
  • 用户需要一种更直观、更少结构化的方法来与程序交互。
  • 计算机系统发展成一种分布式模型,其中业务逻辑、用户界面和后端数据库是松散耦合的,并通过 Internet 和 intranets 进行访问。

因此,许多商业软件开发人员转向了面向对象的方法和编程语言。这些好处包括如下:

  • 从业务分析模型到软件实现模型的更直观的转变
  • 更有效、更快速地维护和实施计划变更的能力
  • 使用团队过程更有效地创建软件系统的能力,允许专家处理系统的各个部分
  • 能够在其他程序中重用代码组件,并购买由第三方开发人员编写的组件,从而毫不费力地增加现有程序的功能
  • 与松散耦合的分布式计算系统更好地集成
  • 改进了与现代操作系统的集成
  • 为用户创建更直观的图形用户界面的能力

面向对象的特点

在这一节中,你将研究一些所有面向对象语言共有的基本概念和术语。不要担心这些概念是如何在任何特定的编程语言中实现的;那是以后的事。我的目标是让你熟悉这些概念,并把它们与你的日常经验联系起来,这样当你以后看 OOP 设计和实现时,它们就更有意义了。

目标

正如我前面提到的,我们生活在一个面向对象的世界里。你是一件物品。你与其他物体互动。其实你就是一个物体,有你的身高,发色等数据。你也有你执行的方法或在你身上执行的方法,例如吃和走。

那么什么是对象?用面向对象的术语来说,对象是一个用来合并数据和处理数据的过程的结构。例如,如果您对跟踪与产品库存相关的数据感兴趣,您可以创建一个 product 对象,负责维护和使用与产品相关的数据。如果您想在应用中拥有打印功能,您可以使用一个打印机对象,该对象负责用于与打印机交互的数据和方法。

抽象

当您与世界上的对象交互时,您通常只关心它们属性的子集。如果没有这种提取或过滤物体无关属性的能力,你会发现很难处理轰炸你的大量信息并专注于手头的任务。

作为抽象的结果,当两个不同的人与同一个对象交互时,他们经常处理不同的属性子集。例如,当我开车时,我需要知道汽车的速度和方向。因为汽车使用自动变速器,所以我不需要知道发动机的每分钟转数,所以我过滤掉这些信息。另一方面,这些信息对于赛车手来说至关重要,他们不会过滤掉这些信息。

当在 OOP 应用中构造对象时,结合这个抽象概念是很重要的。这些对象只包括与应用上下文相关的信息。如果您正在构建一个运输应用,您将构建一个具有诸如尺寸和重量等属性的产品对象。该项目的颜色是无关的信息,将被忽略。另一方面,在构建订单输入应用时,颜色可能很重要,并且会作为产品对象的属性包含在内。

包装

OOP 的另一个重要特征是封装。封装是不允许对数据进行直接访问的过程;反而是隐藏的。如果您想要访问数据,您必须与负责数据的对象进行交互。在前面的库存示例中,如果您想要查看或更新产品信息,您必须处理产品对象。要读取数据,您需要向产品对象发送一条消息。然后,产品对象将读取该值并发回一条消息,告诉您该值是多少。产品对象定义了可以对产品数据执行的操作。如果您发送一条消息来修改数据,并且产品对象确定它是一个有效的请求,它将为您执行操作并发送一条消息返回结果。

你在日常生活中无时无刻不在体验着被封闭。想想人力资源部门。它们封装(隐藏)了员工的信息。他们决定如何使用和操作这些数据。任何对员工数据的请求或更新数据的请求都必须通过他们。再比如网络安全。任何对安全信息的请求或对安全策略的更改都必须通过网络安全管理员进行。对网络用户来说,安全数据是封装的。

通过封装数据,您可以使系统的数据更加安全可靠。您知道数据是如何被访问的,以及对数据执行了什么操作。这使得程序维护更加容易,也大大简化了调试过程。您还可以修改用于处理数据的方法,并且,如果您不改变请求方法的方式和发送回的响应类型,您就不必改变使用该方法的其他对象。想想当你在邮件中寄信的时候。你向邮局请求投递这封信。邮局是如何做到这一点的,你无从知晓。如果它更改了用于邮寄信件的路线,它不会影响您开始发送信件的方式。你不必知道邮局传递信件的内部程序。

多态

多态是两个不同的对象以他们自己独特的方式响应同一请求消息的能力。例如,我可以训练我的狗回应命令吠叫,训练我的鸟回应命令唧唧喳喳。另一方面,我可以训练它们既响应命令又说话。通过多态,我知道狗会用叫声回应,而鸟会用唧唧声回应。

这和 OOP 有什么关系?您可以创建对象,这些对象以自己独特的实现方式响应相同的消息。例如,您可以向在打印机上打印文本的打印机对象发送打印消息,也可以向将文本打印到计算机屏幕上的窗口的屏幕对象发送相同的消息。

多态的另一个好例子是英语中单词的使用。单词有许多不同的意思,但通过句子的上下文,你可以推断出哪个意思是想要的。你知道有人会说“饶了我吧!”不是让你打断他的腿!

在 OOP 中,你通过一个叫做重载的过程来实现这种类型的多态。您可以实现同名对象的不同方法。然后,该对象可以根据消息的上下文(换句话说,传递的参数的数量和类型)来判断要实现哪个方法。例如,您可以创建 inventory 对象的两种方法来查找产品的价格。这两种方法都将被命名为 getPrice。另一个对象可以调用这个方法并传递产品名称或产品 ID。inventory 对象可以通过请求传递的是字符串值还是整数值来判断运行哪个 getPrice 方法。

遗产

大多数现实生活中的对象都可以分为不同的层次。例如,您可以将所有狗归为具有某些共同特征的一类,如有四条腿和皮毛。他们的品种进一步将他们分为具有共同属性的亚组,如体型和神态。你也可以根据物体的功能对其进行分类。比如有商务车,也有休闲车。有卡车和客车。你根据汽车的品牌和型号来分类。为了理解这个世界,您需要使用对象层次和分类。

你在 OOP 中使用继承来根据共同的特征和功能对程序中的对象进行分类。这使得处理对象更加容易和直观。它还使编程更容易,因为它使您能够将一般特征组合到父对象中,并在子对象中继承这些特征。例如,您可以定义一个 employee 对象,该对象定义公司中雇员的所有一般特征。然后,您可以定义一个经理对象,该对象继承 employee 对象的特征,但也添加了贵公司经理特有的特征。由于继承关系,经理对象将自动反映雇员对象特征的任何变化。

聚合

聚合是指一个对象由一起工作的其他对象组成。例如,您的割草机对象是轮子对象、引擎对象、刀片对象等的组合。事实上,引擎对象是许多其他对象的组合。在我们周围的世界里有许多聚合的例子。在 OOP 中使用聚合的能力是一个强大的特性,它使您能够在程序中准确地建模和实现业务流程。

c# 的历史

在 20 世纪 80 年代,大多数运行在 Windows 操作系统上的应用都是用 C++编写的。尽管 C++是一种面向对象的语言,但可以说它是一种很难掌握的语言,程序员负责处理诸如内存管理和安全性等日常事务。这些内务处理任务很难实现,并且经常被忽略,这导致了充满 bug 的应用很难测试和维护。

在 20 世纪 90 年代,Java 编程语言开始流行。因为它是一种托管编程语言,所以它运行在一组统一的类库之上,这些类库负责低级编程任务,如类型安全检查、内存管理和销毁不需要的对象。这使得程序员可以专注于业务逻辑,而不必担心容易出错的内务代码。因此,程序更紧凑、更可靠、更易于调试。

看到 Java 的成功和互联网的日益普及,微软开发了自己的一套托管编程语言。微软想让开发基于 Windows 和基于 Web 的应用变得更容易。这些托管语言依赖于 .NET Framework 提供了许多功能来执行所有应用中所需的内务处理代码。在开发过程中 .NET 框架,类库是用一种叫做 C# 的新语言编写的。C# 的主要设计者和首席架构师是安德斯·海尔斯伯格。Hejlsberg 之前参与了 Turbo Pascal 和 Delphi 的设计。他利用以前的经验设计了一种 OOP 语言,这种语言建立在这些语言的成功之上,并改进了它们的缺点。Hejlsberg 还将类似于 C 的语法结合到语言中,以吸引 C++和 Java 开发人员。创建 .NET 框架和 C# 语言将现代概念,如面向对象、类型安全、垃圾收集和结构化异常处理直接引入平台。

自从发布 C# 以来,微软一直在寻求为该语言添加额外的功能和增强。例如,2.0 版本增加了对泛型的支持(泛型在第九章中有所涉及),3.0 版本增加了 LINQ (更多信息请见第十章)以减少编程语言和用于检索和处理数据的数据库语言之间的阻抗不匹配。如今,C# 5.0 支持让并行异步编程更易于开发人员实现(参见第八章)。随着微软对 C# 不断改进和发展的承诺,它将继续成为世界上使用最广泛的编程语言之一。

微软也致力于提供 .NET 开发人员拥有必要的工具,以获得高效和直观的编程体验。虽然您可以使用文本编辑器创建 C# 程序,但大多数专业程序员发现集成开发环境(IDE)在易用性和提高生产率方面非常有价值。微软在 Visual Studio (VS)中开发了一个出色的 IDE。集成到 VS 中的许多特性使得 .NET 框架更直观(这些特性在第五章中有所介绍)。随着 Visual Studio 2012 的最新发布,微软继续增强设计时开发体验。VS 2012 包含了一些新特性,比如对并行编程的更好的调试支持和改进的代码测试体验。当你读完这本书时,我想你会逐渐体会到 Visual Studio 和 C# 语言所提供的强大功能和高效率。

摘要

在这一章中,你已经了解了面向对象的程序设计和 C# 的简史。既然您已经了解了 OOP 语言的组成以及 OOP 语言对于企业级应用开发如此重要的原因,那么您的下一步就是熟悉 OOP 应用是如何设计的。

为了满足用户的需求,必须仔细规划和开发成功的应用。下一章是三章系列的第一章,旨在向您介绍设计面向对象应用时使用的一些技术。您将看到决定应用中需要包含哪些对象的过程,以及这些对象的哪些属性对该应用的功能很重要。

二、设计 OOP 解决方案:识别类结构

作为商业软件开发人员,你将参与的大多数软件项目都是团队工作。作为团队中的程序员,您将被要求将设计文档转换成实际的应用代码。此外,因为面向对象程序的设计是一个迭代过程,设计者依赖软件开发者的反馈来提炼和修改程序设计。随着您在开发面向对象软件系统方面获得经验,您甚至可能会被邀请参加设计会议,并对设计过程做出贡献。因此,作为软件开发人员,您应该熟悉各种设计文档的目的和结构,并对这些文档的开发方式有所了解。

本章向您介绍一些用于设计系统静态方面的常用文档。(你将在下一章了解系统的动态方面是如何建模的。)为了帮助您理解这些文档,本章包括一些基于有限案例研究的实践活动。你会在本书的大部分章节中找到与讨论主题相对应的类似活动。

阅读本章后,您将熟悉以下内容:

  • 软件设计的目标
  • 统一建模语言的基础
  • 软件需求规格的目的
  • 用例图如何建模系统将提供的服务
  • 类图如何对需要开发的对象类建模

软件设计的目标

在开发现代企业级面向对象程序时,组织良好的系统设计方法是必不可少的。设计阶段是软件开发周期中最重要的阶段之一。您可以将许多与失败的软件项目相关的问题追溯到糟糕的前期设计以及系统开发人员和系统消费者之间不充分的沟通。不幸的是,许多程序员和项目经理不喜欢参与系统的设计。他们认为任何不花在生产代码上的时间都是无效的。

更糟糕的是,随着“互联网时代”的到来,消费者期望开发周期越来越短。因此,为了满足不切实际的时间表和项目范围,开发人员倾向于放弃或缩短开发的系统设计阶段。这对系统的成功起反作用。在设计过程中投入时间将实现以下目标:

  • 提供一个机会来审查当前的业务流程,并修复任何发现的低效或缺陷
  • 教育客户软件开发过程是如何发生的,并让他们作为合作伙伴参与到这个过程中
  • 创建现实的项目范围和完成时间表
  • 为确定软件测试需求提供基础
  • 降低实施软件解决方案所需的成本和时间

软件设计的一个很好的类比是建造一个家的过程。没有建筑师提供的详细计划(蓝图),你不会想到建筑商会开始建造房子。你会期望建筑师在设计蓝图之前和你讨论房子的设计。建筑师的工作就是倾听你的要求,并把它们转化成建筑者可以用来建造房屋的平面图。一个好的架构师还会告诉你哪些特性对你的预算和预计的时间表是合理的。

理解统一建模语言

为了成功地设计面向对象的软件,您需要遵循一个经过验证的设计方法。如今,OOP 中使用的一种经过验证的设计方法是统一建模语言(UML)。UML 开发于 20 世纪 80 年代早期,作为对面向对象软件设计建模的标准、系统方法的需求的回应。该行业标准由对象管理小组(OMG)创建和管理。UML 已经随着软件行业的发展而成熟,当前版本 2.4.1 于 2011 年正式发布。

在设计过程中使用 UML 有很多好处。当正确实现时,UML 允许您在不同的细节层次上可视化软件系统。您可以与用户一起验证项目的需求和范围。它也可以作为测试系统的基础。UML 非常适合增量开发过程。UML 建模对于大型系统的并行开发也非常有益。使用该模型,每个团队都知道他们的部分如何适应系统,并可以传达可能影响其他团队的变化。

UML 由所提议的解决方案的一系列文本和图形模型组成。这些模型定义了系统范围、系统组件、用户与系统的交互,以及系统组件如何相互交互以实现系统功能。

UML 中使用的一些常见模型如下:

  • 软件需求说明书 (SRS) : 对系统整体职责和范围的文字描述。
  • 用例 : 从用户的角度对系统行为的文本/图形描述。用户可以是人类或其他系统。
  • 类图 : 将用于构建系统的对象的可视化蓝图。
  • 序列图 : 程序执行时对象交互序列的模型。重点放在交互的顺序以及它们如何随时间进行。
  • 协作图 : 程序执行时,对象如何组织在一起工作的视图。重点放在对象之间的通信上。
  • 活动图 : 流程或操作执行流程的可视化表示。

在这一章中,你将会看到 SRS 的开发,用例,以及类图。第三章涵盖了顺序图、协作图和活动图。

制定安全气囊系统

SRS 的目的是完成以下工作:

  • 定义系统的功能需求。
  • 确定系统的边界。
  • 识别系统的用户。
  • 描述系统和外部用户之间的交互。
  • 在客户和程序团队之间建立一种描述系统的通用语言。
  • 为用例建模提供基础。

要制作 SRS,您需要采访业务所有者和系统的最终用户。这些访谈的目标是清楚地记录所涉及的业务流程,并确定系统的范围。该过程的结果是一份详细说明系统功能要求的正式文件(SRS)。正式文档有助于确保客户和软件开发人员之间达成一致。随着开发的进行,SRS 还为解决任何关于感知系统范围的分歧提供了基础。

例如,假设一家小型通勤航空公司的所有者希望客户能够使用 Web 注册系统查看航班信息并预订机票。与业务经理和票务代理面谈后,软件设计师起草一份 SRS 文档,列出系统的功能需求。以下是其中的一些要求:

  • 未注册的网络用户可以浏览网站查看航班信息,但他们不能预订航班。
  • 想要预订航班的新客户必须填写登记表,提供他们的姓名、地址、公司名称、电话号码、传真号码和电子邮件地址。
  • 客户分为公司客户和零售客户。
  • 客户可以根据目的地和出发时间搜索航班。
  • 客户可以在预订航班时注明航班号和所需座位数。
  • 预订航班后,系统会通过电子邮件向客户发送确认信息。
  • 当公司客户的员工预订航班时,他们会获得常旅客里程。
  • 常旅客里程用来对未来的购买打折。
  • 机票预订可提前一周取消,并可获得 80%的退款。
  • 票务代理可以查看和更新航班信息。

在这个部分 SRS 文档中,您可以看到几个简洁的语句定义了系统范围。它们从系统用户的角度描述系统的功能,并确定将使用它的外部实体。需要注意的是,SRS 不包含对系统技术要求的参考。

一旦 SRS 被开发出来,它所包含的功能需求就被转化为一系列的用例图。

引入用例

用例描述了外部实体将如何使用系统。这些外部实体可以是人或者其他系统(在 UML 术语中称为参与者)。描述强调用户对系统的看法以及用户和系统之间的交互。用例有助于进一步定义系统范围和边界。它们通常以图表的形式,伴随着对正在发生的交互的文本描述。图 2-1 显示了一个通用图,它由两个参与者组成,用简笔画表示,用矩形表示系统,用椭圆表示系统边界内的用例。

9781430249351_Fig02-01.jpg

图 2-1 。有两个参与者和三个用例的通用用例图

用例是根据 SRS 文档开发的。参与者是与系统交互的任何外部实体。参与者可以是一个人类用户(例如,一个租赁代理),另一个软件系统(例如,一个软件计费系统),或者一个接口设备(例如,一个温度探测器)。参与者和系统之间发生的每个交互都被建模为一个用例。

图 2-2 中显示的示例用例是为上一节中介绍的机票预订应用开发的。它显示了需求“客户可以根据目的地和出发时间搜索航班”的用例图。

9781430249351_Fig02-02.jpg

图 2-2 。查看航班信息用例

除了用例的图形化描述,许多设计人员和软件开发人员发现提供用例的文本描述很有帮助。文字描述应该简洁明了,集中在正在发生的事情上,而不是它是如何发生的。有时,与用例相关的任何先决条件或后置条件也被识别。下文进一步描述了图 2-2 中所示的用例图。

  • 描述:顾客查看航班信息页面。客户输入航班搜索信息。提交搜索请求后,客户查看符合搜索条件的航班列表。
  • 前提条件:无。
  • 后置条件:客户有机会登录并进入机票预订页面。

再举一个例子,看看图 2-3 中显示的预留座位用例。

9781430249351_Fig02-03.jpg

图 2-3 。预订座位用例图

下文进一步描述了图 2-3 中所示的用例图。

  • 描述:顾客输入航班号,并指出所要求的座位。客户提交请求后,会显示一些确认信息。
  • 前提条件:客户已经查询了航班信息。客户已经登录并正在查看航班预订屏幕。
  • 后置条件:向客户发送一封确认电子邮件,概述航班详情和取消政策。

正如你从图 2-3 中看到的,在用例之间可以存在某些关系。预订座位用例包括查看航班信息用例。这种关系非常有用,因为您可以独立于预订航班用例使用查看航班信息用例。这叫包容。然而,您不能将预订座位用例与查看航班信息用例分开使用。这是将影响您如何对解决方案建模的重要信息。

用例相互关联的另一种方式是通过扩展。你可能有一个通用用例,它是其他用例的基础。其他用例扩展了基本用例。例如,您可能有一个注册客户用例,它描述了注册客户的核心过程。然后,您可以开发注册公司客户和注册零售客户用例,扩展基本用例。扩展和包含之间的区别在于,在扩展中,被扩展的基本用例不会单独使用。图 2-4 展示了你如何在用例图中建模。

9781430249351_Fig02-04.jpg

图 2-4 。扩展用例

开发用例时的一个常见错误是包含由系统本身发起的动作。用例的重点是外部实体和系统之间的交互。另一个常见的错误是包含系统技术需求的描述。请记住,用例并不关注系统将如何执行功能,而是关注从用户的角度来看,什么功能需要被整合到系统中。

在您开发了系统的用例之后,您可以开始识别将执行系统功能需求的内部系统对象。您可以使用类图来实现这一点。

活动 2-1。创建用例图

完成本活动后,您应该熟悉以下内容:

  • 如何生成用例图来定义系统的范围
  • 如何使用 UML 建模工具创建并记录用例图

检查安全气囊

你所属的软件用户组决定集中资源,创建一个借阅图书馆。借出的物品包括书籍、电影和电子游戏。您的任务是开发一个应用,该应用将跟踪借出物品的库存以及向小组成员借出物品的情况。在与小组成员和官员面谈后,您制定了一份 SRS 文件,其中包括以下功能要求:

  • 只有用户组的成员才能借阅物品。
  • 书可以借四周。
  • 电影和游戏可以借一个星期。
  • 如果没有人在等着借阅,可以续借。
  • 会员一次最多只能借四件物品。
  • 当项目过期时,会通过电子邮件向成员发送提醒。
  • 过期的项目要罚款。
  • 有未偿还的过期项目或罚款的成员不能借新项目。
  • 秘书负责维护项目库存和采购项目以添加到库存中。
  • 已经任命了一名图书管理员来跟踪借阅情况并发送过期通知。
  • 图书管理员还负责收集罚款和更新罚款信息。

接下来的步骤是分析 SRS 以识别参与者和用例。

  1. 通过检查 SRS 文档,确定以下哪些是与系统交互的主要参与者:
    • A.成员
    • B.图书管理员
    • C.书
    • D.出纳员
    • E.库存
    • F.电子邮件
    • G.秘书
  2. 一旦您确定了主要参与者,您就需要确定参与者的用例。确定与以下用例相关的参与者:
    • A.请求项目
    • B.目录项目
    • C.借出项目
    • D.加工精细

活动 2-1 答案见本章末尾。

创建用例图

尽管手工或在白板上创建 UML 图是可能的,但大多数程序员最终会求助于图表工具或计算机辅助软件工程(CASE)工具。 CASE 工具帮助您构建专业质量的图表,并使团队成员能够轻松地共享和扩充图表。市场上有许多 CASE 工具,包括 Microsoft Visio。在选择 CASE 工具之前,您应该彻底评估它是否满足您的需求,是否足够灵活。许多与高端 CASE 工具相关的高级特性很难使用,所以你花更多的时间去弄清楚 CASE 工具是如何工作的,而不是记录你的设计。

一个很好的学习绘图工具是 UMLet。UML let 使您能够创建 UML 图,而无需添加许多与高端 CASE 工具相关的高级特性。最棒的是,UMLet 是一个免费的开源工具,可以从www.umlet.com下载。

image 注意这些活动使用的是 UMLet 11.5.1 单机版。这也需要在www.java.com可用的 Java 1.6。

在下载并安装了 UMLet 之后,您可以完成以下步骤(如果您不想使用工具,您可以手工创建用例图):

  1. 双击 UMLet.jar 文件启动 UMLet。您将看到三个窗口。主窗口是设计图面,右上角的窗口包含 UML 对象模板,右下角的窗口是您更改或添加对象属性的地方。

  2. Locate the actor template in the upper right window (see Figure 2-5). Double click the actor template. An actor will appear in the upper left corner of the design surface.

    9781430249351_Fig02-05.jpg

    图 2-5 。定位演员模板

  3. 如果尚未选择,请在设计图面上选择参与者形状。在右下窗口中,将执行元形状的名称更改为 Member。

  4. 重复添加秘书和图书管理员执行元的过程。

  5. 从“模板”窗口中,双击“用例 1”形状,将其添加到设计图面。将用例的名称更改为 Request Item。

  6. 对另外两个用例重复步骤 5。包括当秘书向图书馆目录数据库添加新项目时将发生的目录项目用例。添加将在图书管理员处理项目请求时发生的借出项目用例。

  7. From the Template window, double click the Empty Package shape and change the name to Library Loan System. Right click on the shape in the design surface and change the background color to white. Move the use case shapes inside the Library Loan System shape (see Figure 2-6).

    9781430249351_Fig02-06.jpg

    图 2-6 。将用例放入系统边界

  8. From the Template window, double click on the Communications Link shape. It is the line with no arrow heads (see Figure 2-7). On the design surface, attach one end to the Member shape and the other end to the Request Item shape.

    9781430249351_Fig02-07.jpg

    图 2-7 。定位通信链路形状

  9. 重复步骤 8 两次,在“图书管理员”和“出借”项目形状之间创建一个“通信链接”形状,并在“秘书”和“目录项目”形状之间创建一个“通信链接”形状。

  10. 在模板窗口中,双击扩展关系箭头。将扩展箭头的尾端连接到借出项目用例,并将箭头的头部连接到请求项目用例。

  11. Your completed diagram should be similar to the one shown in Figure 2-8. Save the file as UMLAct2_1 and exit UMLet.

![9781430249351_Fig02-08.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/dddcb56940f0424eae2cbca741a94469~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=RLAHSAw%2BipQX69ZPd6BwH2gcWIs%3D)
图 2-8 。完整的用例图

理解类图

类和对象的概念是面向对象的基础。对象是用于合并数据和处理数据的过程的结构。这些对象实现了面向对象程序的功能。把一个类想象成一个对象的蓝图,把一个对象想象成一个类的实例。类定义了基于类类型的对象将包含的结构和方法。

设计者从 SRS 和用例图中识别出他们需要开发的类的潜在列表。识别类的一种方法是查看 SRS 文档中的名词短语和用例描述。如果您查看到目前为止为机票预订应用开发的文档,您可以开始确定组成系统的类。例如,您可以开发一个处理客户数据的Customer类和一个处理航班数据的Flight类。

一个类负责管理数据。在定义类结构时,你必须确定类负责维护什么数据。类属性定义了这些信息。例如,Flight类将具有标识航班号、出发时间和日期、飞行持续时间、目的地、容量和可用座位的属性。类结构还必须定义将对数据执行的任何操作。Flight类负责的操作的一个例子是当座位被预订时更新可用的座位。

类图可以帮助您可视化类的属性和操作。 图 2-9 是航班预订系统示例中使用的Flight类的类图示例。分成三部分的矩形代表该类。矩形的上半部分显示了类的名称,中间部分列出了类的属性,下半部分列出了类执行的操作。

9781430249351_Fig02-09.jpg

图 2-9 。飞行等级图

建模对象关系

在面向对象程序设计中,当程序执行时,各种对象协同工作来完成编程任务。例如,在机票预订应用中,为了预订航班座位,一个Reservation对象必须与Flight对象进行交互。两个对象之间存在关系,这种关系必须在程序的类结构中建模。组成程序的类之间的关系在类图中建模。分析 SRS 中的动词短语通常会揭示这些关系(这将在第三章的中详细讨论)。接下来的部分研究了类之间可能出现的一些常见关系,以及类图如何表示它们。

联合

当一个类引用或使用另一个类时,这些类就形成了一个关联。在两个类之间画一条线来表示关联,并添加一个标签来表示关联的名称。例如,在机票预订应用中,一个座位与一个航班相关联,如图图 2-10 所示。

9781430249351_Fig02-10.jpg

图 2-10 。类别关联

有时一个类的单个实例与另一个类的多个实例相关联。这在连接两个类的线上有所表示。例如,当客户进行预订时,Customer类和Reservation类之间存在关联。一个Customer类的实例可能与多个Reservation类的实例相关联。放置在Reservation类附近的 n 表示这种多重性,如图图 2-11 所示。

9781430249351_Fig02-11.jpg

图 2-11 。在类图中指示多重性

还可能存在一种情况,其中一个类的实例可能与同一类的多个实例相关联。例如,Pilot类的一个实例代表机长,而Pilot类的另一个实例代表副驾驶。飞行员管理副驾驶。这种场景被称为自关联,通过绘制从类到自身的关联线来建模,如图图 2-12 所示。

9781430249351_Fig02-12.jpg

图 2-12 。一个自我联想的班级

遗产

当多个类共享一些相同的操作和属性时,基类可以封装共性。然后子类从基类继承。这在类图中用一条实线表示,该实线带有一个指向基类的开放箭头。例如,CorporateCustomer类和RetailCustomer类可以从基础Customer类中继承公共属性和操作,如图图 2-13 所示。

9781430249351_Fig02-13.jpg

图 2-13 。记录继承

聚合

当一个类由其他类组合而成时,它们被归类为一个集合。这用连接分层结构中的类的实线来表示。在图中的类旁边的线上放置一个菱形表示层次结构的顶层。例如,一个为飞机维修部门设计的跟踪飞机零件的库存应用可以包含一个由各种零件类组成的Plane类,如图图 2-14 所示。

9781430249351_Fig02-14.jpg

图 2-14 。描绘聚合

关联类

随着程序的类和关联的发展,可能会出现这样的情况,一个属性不能被分配给任何一个类,而是类之间关联的结果。例如,前面提到的零件库存应用可能有一个Part类和一个Supplier类。因为一个零件可以有多个供应商,并且供应商供应多个零件,那么价格属性应该位于哪里呢?它不适合作为任何一个类的属性,也不应该在两个类中重复。解决方案是开发一个关联类,它管理作为关联产品的数据。在这种情况下,你可以开发一个PartPrice类。关联和关联类之间用虚线建模,如图图 2-15 所示。

9781430249351_Fig02-15.jpg

图 2-15 。联想类

图 2-16 显示了机票预订应用的演化类图。它包括已经为系统确定的类、属性和关系。与这些类相关的操作将在第三章中展开。

9781430249351_Fig02-16.jpg

图 2-16 。机票预订类图

活动 2-2。创建类图

完成本活动后,您应该熟悉以下内容:

  • 如何通过检查用例以及系统范围文档来确定需要构建的类
  • 如何使用 UML 建模工具创建类图

识别类别和属性

检查为用户组库应用的用例开发的以下场景:

查看可用借出项目列表后,成员请求借出一个项目。图书管理员输入会员号,并检索有关未偿还贷款和任何未付罚款的信息。如果成员的未偿还贷款少于四笔,并且没有任何未偿还的罚款,则处理贷款。图书管理员检索关于借出项目的信息,以确定它当前是否被借出。如果项目可用,它将被签出给成员。

  1. 通过识别用例场景中的名词和名词短语,您可以了解为了执行任务,您必须在系统中包含什么类。以下哪一项将成为系统的良好候选类别?
    • A.Member
    • B.Item
    • C.Librarian
    • D.Number
    • E.Fine
    • F.Checkout
    • G.Loan
  2. 此时,您可以开始识别与正在开发的类相关联的属性。将开发一个Loan类来封装与借出项目相关的数据。下面哪个可能是Loan类的属性?
    • A.MemberNumber
    • B.MemberPhone
    • C.ItemNumber
    • D.ReturnDate
    • E.ItemCost
    • F.ItemType

活动 2-2 答案见本章末尾。

创建类图

要使用 UML Modeler 创建一个类图,请遵循以下步骤(您也可以手工创建):

  1. 启动 UMLet。您将看到三个窗口。主窗口是设计图面,右上角的窗口包含 UML 对象模板,右下角的窗口是您更改或添加对象属性的地方。

  2. Locate the SimpleClass template in the upper right window (see Figure 2-17). Double click the SimpleClass template. A SimpleClass will appear in the upper left corner of the design surface.

    9781430249351_Fig02-17.jpg

    图 2-17 。添加类形状

  3. 在右下方的属性窗口中,将类名更改为Member

  4. LoanItemBookMovie级重复该程序。

  5. Locate the association template in the upper right window (see Figure 2-18). Double click the association template. An association will appear in the upper left corner of the design surface.

    9781430249351_Fig02-18.jpg

    图 2-18 。添加关联形状

  6. Attach the left end of the association shape to the Member class and the right end to the Loan class shape. Select the association shape and update the properties in the properties window so that they match Figure 2-19.

    9781430249351_Fig02-19.jpg

    图 2-19 。更新关联属性

  7. 重复步骤 5 和 6,在Loan类和Item类之间创建一个“包含一个”关联形状。这应该是一对一的关联。

  8. Locate the generalization arrow template in the upper right window (see Figure 2-20). Double click the generalization shape. A generalization shape will appear in the upper left corner of the design surface.

    9781430249351_Fig02-20.jpg

    图 2-20 。添加一个概括箭头

  9. Attach the tail end of the generalization arrow to the Book class and the head end to the Item class shape. Select the generalization arrow and update the properties in the properties widow so that they match Figure 2-21.

    9781430249351_Fig02-21.jpg

    图 2-21 。更新综合属性

  10. 重复第 8 步和第 9 步,显示Movie类继承自Item类。

  11. Click on the Member class in the design window. In the properties window, add the memberNumber, firstName, lastName, and eMail attributes as shown in Figure 2-22.

![9781430249351_Fig02-22.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/ed91c694e9854831bcfafe7e7075aa57~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=Fz%2FnaWgg8zZdEzHjRLsfF1s%2FM%2FQ%3D)
图 2-22 。添加类属性

12. Your completed diagram should be similar to Figure 2-23. Save the file as UMLAct2_2.

![9781430249351_Fig02-23.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/80638dafacef45daaf8a47935337b34d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=VbVMykWUp4N7jDUgwRm4KEJLB%2FQ%3D)

图 2-23 。完成的类图

摘要

在这一章中,你已经了解了面向对象设计过程和 UML 的目标。您学习了一些使用 UML 生成的设计文档和图表。这些包括 SRS,它定义了系统的范围;用例图,它定义了系统边界,并确定了将使用该系统的外部实体;和类图,它们对将要开发来实现系统的类的结构进行建模。

您看到了如何对应用的类结构进行建模,包括识别必要的类,识别这些类的属性,以及建立这些类之间所需的结构关系。在第三章中,你将继续学习面向对象设计。特别是,您将看到应用中的对象如何协作来实现应用的功能。

活动答案

活动 2–1 答案

  1. 演员是会员、图书管理员和秘书。
  2. A.会员,b 秘书,c 图书管理员,d 图书管理员。请求项用例与成员一起使用,目录项用例与秘书一起使用,借出项用例与图书管理员一起使用,流程精细用例与图书管理员一起使用。

活动 2–2 答案

  1. a、B、C、E、g,候选班有MemberItemLibrarianFineLoan
  2. Loan类相关的属性有MemberNumberItemNumberReturnDate

三、设计 OOP 解决方案:建模对象交互

前一章关注于 OOP 解决方案的静态(组织)方面的建模。它介绍并讨论了 UML 的方法论。它还研究了用例图和类图的目的和结构。这一章继续讨论 UML 建模技术,并重点关注 OOP 解决方案的动态(行为)方面的建模。本章的重点是系统中的对象必须如何相互作用,以及必须发生什么活动来实现解决方案。这是建模过程的一个重要方面。这些模型将作为对构成软件应用的类的各种方法(在第二章中确定)进行编码的基础。

阅读本章后,您应该熟悉以下内容:

  • 场景的目的以及它们如何扩展用例模型
  • 序列图如何对系统中对象的时间相关交互进行建模
  • 活动图如何映射应用处理期间的活动流
  • 图形用户界面设计的重要性以及它如何适应面向对象的设计过程

了解场景

场景帮助确定系统的对象(类实例)之间将要发生的动态交互。场景是对实现用例所记录的功能所需的内部处理的文本描述。记住用例从系统外部用户的角度描述系统的功能。一个场景详述了用例的执行。换句话说,它的目的是描述组成系统的对象必须在内部执行的步骤。

图 3-1 显示了一个视频租赁应用的流程电影租赁用例 。以下文本描述了使用案例:

  • 前提条件:客户向租赁人员请求租赁一部电影。该客户是视频俱乐部的会员,并向租赁店员提供了她的会员卡和个人身份号码(p in)。验证客户的会员资格。显示客户信息,并验证客户的帐户信誉良好。
  • 描述:电影确认有货。记录租赁信息,并通知客户到期日期。
  • 岗位条件:无。

9781430249351_Fig03-01.jpg

图 3-1 。处理电影租赁用例

以下场景描述了流程电影租赁用例的内部处理:

  • 电影经核实有现货。
  • 库存中的可用份数减少。
  • 到期日已确定。
  • 租赁信息被记录。这些信息包括电影标题、拷贝数、当前日期和到期日期。
  • 客户被告知租赁信息。

这个场景描述了用例的最佳可能执行。因为异常可能发生,一个用例可以产生多个场景。例如,为流程电影租赁用例创建的另一个场景可以描述当电影没有库存时会发生什么。

在您为一个用例规划出各种场景之后,您可以创建交互图来确定哪些类的对象将参与执行场景的功能。交互图还揭示了这些对象类需要什么操作。交互图有两种风格:序列图和协作图。

序列图介绍

一个序列图模拟了当系统运行时,对象类如何随着时间的推移而相互作用。序列图是正在发生的交互的可视化二维模型,并且基于一个场景。图 3-2 显示了一个通用顺序图。

9781430249351_Fig03-02.jpg

图 3-2 。通用序列图

如图 3-2 所示,从一个对象到另一个对象的消息流是水平的。交互发生的时间流程是垂直描述的,从顶部开始向下进行。根据调用顺序,对象从左到右并排放置。虚线从它们中的每一个向下延伸。这条虚线代表对象的生命线。生命线上的矩形代表对象的激活。矩形的高度表示对象激活的持续时间。

在 OOP 中,对象通过相互传递消息来进行交互。从发起对象开始到接收对象结束的箭头描述了交互。拉回到发起对象的虚线箭头表示返回消息。序列图中描述的消息将形成系统的类的方法的基础。图 3-3 显示了上一节中介绍的流程电影租赁场景的示例序列图。在这一点上,该图只模拟了电影有库存的情况。

9781430249351_Fig03-03.jpg

图 3-3 。流程电影租赁顺序图

当你分析序列图时,你会对执行程序处理所涉及的对象类别有所了解;您还将了解需要创建哪些方法并附加到这些类。您还应该在类图中对序列图中描述的类和方法进行建模。这些设计文件必须不断交叉引用,并在必要时进行修订。

图 3-3 中的序列图揭示了执行流程电影租赁场景将涉及四个对象。

  • Customer对象是Customer类的一个实例,负责封装和维护关于客户的信息。
  • RentalClerk对象是RentalClerk类的一个实例,负责管理租借电影所涉及的处理。
  • RentalItem对象是RentalItem类的一个实例,负责封装和维护与出租视频相关的信息。
  • Rental对象是Rental类的一个实例,负责封装和维护与当前租用的视频相关的信息。

消息类型

通过分析序列图,您可以确定哪些消息必须在处理中涉及的对象之间传递。在 OOP 中,消息是同步或异步传递的。

当消息以同步方式传递时,发送对象会暂停处理并等待响应,然后再继续。序列图中用闭合箭头画的线代表同步消息传递。

当对象发送异步消息时,该对象继续处理,并且不期望接收对象立即响应。序列图中用空心箭头画的线代表异步消息传递。虚线箭头通常表示响应消息。这些线如图 3-4 所示。

9781430249351_Fig03-04.jpg

图 3-4 。不同类型的消息

通过研究图 3-3 中所示的流程电影租赁场景的序列图,您可以看到必须传递的消息类型。例如,RentalClerk对象发起与RentalItem对象的同步消息,请求关于电影拷贝是否有货的信息。然后,RentalItem对象向RentalClerk对象发回一个响应,表明有一份拷贝。这需要同步,因为RentalClerk正在等待响应来处理请求。异步消息的一个例子是,当电影返回时,有人注册了电子邮件提醒。在这种情况下,将发送一条消息来启动电子邮件,但不需要响应。

递归消息

在 OOP 中,一个对象拥有一个调用它自己的另一个对象实例的操作并不罕见。这被称为递归。在序列图中,返回调用对象的消息箭头代表递归。箭头的末端指向一个更小的激活矩形,代表在原始激活矩形上绘制的第二个对象激活(见图 3-5 )。例如,Account对象计算逾期付款的复利。为了计算几个复利周期的利息,它需要调用自己几次。

9781430249351_Fig03-05.jpg

图 3-5 。绘制递归消息的图表

消息迭代

有时,会重复消息调用,直到满足某个条件。例如,在合计租金时,会重复调用和Add方法,直到向客户收取的所有租金都被加到总数中。在编程术语中,这就是迭代。在迭代消息周围画出的矩形代表序列图中的一次迭代。迭代的绑定条件显示在矩形的左上角。图 3-6 显示了序列图中描述的一个迭代的例子。

9781430249351_Fig03-06.jpg

图 3-6 。描绘了一个迭代的信息

消息约束

对象之间的消息调用可能有附加的条件约束。例如,顾客必须有良好的信誉才能被允许租借电影。将约束条件放在序列中的方括号([])内。仅当条件评估为真时,才会发送消息(参见图 3-7 )。

9781430249351_Fig03-07.jpg

图 3-7 。识别条件约束

消息分支

当条件约束被绑定到消息调用时,您经常会遇到分支情况,在这种情况下,根据条件的不同,可能会调用不同的消息。图 3-8 表示请求电影租赁时的条件约束。如果租赁项目的状态是“库存中”,则向Rental对象发送一条消息来创建租赁。如果租赁物品的状态是“缺货”,那么会向Reservation对象发送一条消息来创建预订。围绕信息画出的矩形(图 3-8 中标为 alt)显示了根据条件可能出现的替代路径。

9781430249351_Fig03-08.jpg

图 3-8 。序列图中的分支消息

活动 3-1。创建序列图

完成本活动后,您应该熟悉以下内容:

  • 生成序列图来模拟对象交互
  • 使用 UML 建模工具创建序列图
  • 向类图中添加方法

检查场景

以下场景是为活动 2-1 中介绍的用户组库应用中的一个用例创建的。它描述了当成员从图书馆借书时所涉及的处理过程。

当一名会员提出借阅请求时,图书管理员会检查该会员的记录,以确保不存在未支付的罚款。一旦成员通过了这些检查,就会检查该物品是否可用。一旦确认了物品的可用性,就会创建一个记录物品号、会员号、结帐日期和归还日期的借出。

  1. 通过检查场景中的名词短语,您可以确定哪些对象将参与执行处理。被识别的对象也应该有一个在类图中描述的相应的类,这个类图已经被创建了。从描述的场景中,找出五个将执行处理的对象。
  2. 在对象被识别并与类图交叉引用之后,下一步是识别这些对象之间必须发生的消息传递以执行任务。你可以看看场景中的动词短语来帮助识别这些信息。例如,“请求借阅项目”短语表示成员对象和图书管理员对象之间的消息交互。场景中描述的其他交互是什么?

活动 3-1 答案见本章末尾。

创建顺序图

按照以下步骤,使用 UMLet 创建一个序列图:

  1. Start UMLet. Locate the drop-down list at the top of the template window. Change the template type to Sequence (see Figure 3-9).

    9781430249351_Fig03-09.jpg

    图 3-9 。更改模板形状类型

  2. 双击模板窗口中的实例形状。一个实例形状将出现在设计图面的左上角。在“属性”窗口中,将形状的名称更改为Member

  3. From the shapes window, locate the lifeline and activation shapes (see Figure 3-10) and add them to the Member instance, as shown in Figure 3-11.

    9781430249351_Fig03-10.jpg

    图 3-10 。定位生命线和激活形状

    9781430249351_Fig03-11.jpg

    图 3-11 。向序列图添加形状

  4. Repeat steps 2 and 3 to add a Librarian, LoanHistory, Item, and Loan object to the diagram. Lay them out from left to right as shown in Figure 3-12.

    9781430249351_Fig03-12.jpg

    图 3-12 。序列图中的对象布局

  5. 在形状模板窗口中,双击序列消息箭头形状。将箭头的尾端连接到Member对象的生命线,并将箭头的头部连接到Librarian对象的生命线。在“属性”窗口中,将消息的名称更改为“请求项”

  6. To create a return arrow, double-click on the solid arrow with the open arrow head in the shapes template window. In the properties window, change the first line to lt=.< This should change the arrow from solid to dash. Attach the tail end to the Librarian object and the head end to the Member object. Change the name to “Return Loan Info.” Your diagram should look similar to Figure 3-13.

    9781430249351_Fig03-13.jpg

    图 3-13 。序列图中的消息布局

  7. 重复步骤 5 和 6,创建一条从Librarian对象到LoanHistory对象的消息。将调用消息(实线)命名为“检查历史”将返回消息(虚线)命名为“返回历史”

  8. 创建一条从Librarian对象到Item对象的消息。将呼叫消息命名为“检查可用性”将返回消息命名为“返回可用性信息”

  9. 创建一条从Librarian对象到Item对象的消息。将调用消息命名为“更新状态”将返回消息命名为“返回更新确认”

  10. 创建一条从Librarian对象到Loan对象的消息。将调用消息命名为“创建贷款”将返回消息命名为“归还贷款确认”

  11. Rearrange the shapes so that your diagram looks similar to Figure 3-14. Save the diagram as UML_Act3_1.

![9781430249351_Fig03-14.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/459844ad094040a08c0de4be4911cc5f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=p78fugM%2Fx%2FpxnI9kPsoANsrR61Y%3D)
图 3-14 。完整的序列图

向类图添加方法

在您开发了序列图之后,您开始了解必须包含在应用的各个类中的方法。通过从发起对象(客户机)到接收对象(服务器)的方法调用,可以实现序列图中描述的消息交互。被调用的方法是在服务器对象被实例化为的类中定义的。例如,序列图中的“检查可用性”消息表明 Item 类需要一个方法来处理这个消息调用。

按照以下步骤添加方法:

  1. 在 UMLet 中,选择 File image New 来创建一个新的图表。找到模板窗口顶部的下拉列表。将模板类型更改为 Class。

  2. 双击简单的类形状模板。在设计窗口中选择形状。

  3. 在“属性”窗口中,将类名更改为Item。在“属性”窗口中的名称下,输入两个破折号。这将在类形状中创建新的部分。这一部分是您输入类属性的地方。

  4. 在“属性”窗口中,将ItemNumber属性添加到该类中,后跟两个破折号。这将在类形状中创建第三个部分,用于添加该类的方法。

  5. Add a CheckAvailability and an UpdateStatus method to the class as shown in Figure 3-15.

    9781430249351_Fig03-15.jpg

    图 3-15 。向类中添加方法

  6. 将图表另存为 UML_Act3_1b。

理解活动图

活动图说明了在操作或流程中需要发生的活动的流程。您可以构建活动图来查看不同关注级别的工作流。

  • 高度的、系统级的关注将每个用例表示为一个活动,并在不同的用例之间绘制工作流程图。
  • 中级焦点图示了特定用例中发生的工作流。
  • 低层次的焦点图示了在系统的一个类的特定操作中发生的工作流。

活动图由实心圆表示的流程起点和表示从一个活动到下一个活动的流程或转换的转换箭头组成。圆角矩形代表活动,牛眼圆形代表流程的终点。例如,图 3-16 显示了一个通用的活动图,它代表了一个从活动 A 开始,进行到活动 B,然后结束的过程。

9781430249351_Fig03-16.jpg

图 3-16 。通用活动图

决策点和警戒条件

通常,一个活动会有条件地跟随另一个活动。例如,为了租借视频,PIN 验证成员资格。活动图通过流程线旁边的括号(见图 3-17 )用警戒条件(必须满足的条件)表示决策点的制约性。

9781430249351_Fig03-17.jpg

图 3-17 。指示决策点和保护条件

并行处理

在某些情况下,两个或多个活动可以并行运行,而不是顺序运行。垂直于过渡箭头绘制的粗实线表示路径的分割。拆分后,第二条粗实线代表合并。图 3-18 显示了电影退货处理的活动图。增量库存和移除租赁活动的发生顺序无关紧要。图中的并行路径代表了这种并行处理。

9781430249351_Fig03-18.jpg

图 3-18 。活动图中描述的并行处理

活动所有权

活动图的目的是在程序进行的过程中,对活动之间的控制流进行建模。迄今为止显示的图表并没有指出哪些对象负责这些活动。为了表示活动的对象所有权,您将活动图分割成一系列垂直分区(也称为泳道)。分区顶部的对象角色负责该分区中的活动。图 3-19 显示了处理电影租赁的活动图,包括泳道。

9781430249351_Fig03-19.jpg

图 3-19 。活动图中的泳道

活动 3-2。创建活动图

完成本活动后,您应该熟悉以下内容:

  • 使用活动图来模拟程序完成活动时的控制流
  • 使用 UML 建模工具创建活动类图

识别对象和活动

检查为用户组库应用的用例开发的以下场景:

查看可用借出项目列表后,成员请求借出一个项目。图书管理员输入会员号,并检索有关未偿还贷款和任何未付罚款的信息。如果成员的未偿还贷款少于四笔,并且没有任何未偿还的罚款,则处理贷款。图书管理员检索关于借出项目的信息,以确定它当前是否被借出。如果项目可用,它将被签出给成员。

通过识别用例场景中的名词和名词短语,您可以了解在执行活动时什么对象将执行任务。请记住,这些对象是类图中标识的类的实例。活动的开展将涉及到以下对象:MemberLibrarianLoanHistoryItemLoan

动词短语有助于识别对象执行的活动。这些活动应该对应于系统中类的方法。将下列活动与适当的物体配对:

  • A.请求电影
  • B.过程租赁
  • C.检查可用性
  • D.检查会员的贷款状况
  • E.更新项目状态
  • F.计算到期日
  • G.记录租赁信息
  • H.确认租赁

活动 3-2 答案见本章末尾。

创建活动图

按照以下步骤,使用 UMLet: 创建一个活动图

  1. 启动 UMLet。找到模板窗口顶部的下拉列表。将模板类型更改为活动。

  2. 双击模板窗口中的系统框形状。系统框形状将出现在设计图面的左上角。在“属性”窗口中,将该形状的名称更改为 Member,以表示成员分区。

  3. Repeat step 2 to add a partition for the Librarian, LoanHistory, Item, and Loan objects. Align the partitions from left to right as in Figure 3-20.

    9781430249351_Fig03-20.jpg

    图 3-20 。创建活动图分区

  4. 从“形状”窗口中,双击“初始状态”形状,并将其添加到成员分区中。在成员分区的初始状态下,添加一个状态形状。将状态重命名为“请求项目”添加从初始状态到请求项目操作状态的转换形状(箭头)。

  5. 在 Librarian 分区下,添加一个“处理借出”状态和一个从请求项目状态到处理借出状态的转换形状。

  6. 在 LoanHistory 分区下,添加“检查成员状态”操作状态和从“处理贷款”操作到“检查成员状态”操作状态的转换形状。

  7. From the Shapes window, double-click the Conditional Branch shape (diamond) and add it to the LoanHistory partition below the Check Member Status action state. Add a Transition shape from the Check Member Status state to the Conditional Branch. From the Conditional Branch, add a Transition shape to a Deny Loan state under the Librarian partition. Add a label to the Transition shape with a condition of “fail.” Also add a Transition shape to a Check Item Status action state under the Item partition with a label condition of “pass.” Your diagram should be similar to Figure 3-21.

    9781430249351_Fig03-21.jpg

    图 3-21 。添加分支条件

  8. 重复步骤 7,从检查项目状态创建条件分支。如果物品有库存,请在物品分区下将转换形状添加到“更新物品状态”状态。如果该项目缺货,请在 Librarian 分区下添加一个“拒绝借出”状态的转换形状。

  9. 从“更新项目状态”状态,向“贷款”分区下的“记录贷款信息”状态添加一个转换形状。

  10. 从“记录借出信息”状态,向“图书管理员”分区下的“确认借出”状态添加一个转换形状。

  11. 在“形状”窗口中,单击“最终状态”形状,并将其添加到成员分区的底部。添加从拒绝贷款状态到最终操作状态的转换形状。添加另一个从“确认贷款”状态到“最终操作”状态的转换形状。

您完成的图表应该类似于图 3-22 中所示的图表。将图表保存为 UMLAct3_2 并退出 UMLet。

9781430249351_Fig03-22.jpg

图 3-22 。完成的活动图

探索 GUI 设计

到目前为止,关于面向对象的分析和设计的讨论集中在应用的功能设计和内部处理的建模上。成功的现代软件应用依赖于一组丰富的图形用户界面(GUI)来向应用的用户展示该功能。

在现代软件系统中,应用最重要的一个方面是它与用户的交互有多好。用户通过在 DOS 提示符下键入神秘命令来与应用进行交互的日子已经一去不复返了。现代操作系统使用的图形用户界面在很大程度上是直观的。用户也已经习惯了商业办公应用的精致界面。用户已经开始期望 IT 部门内部开发的定制应用具有同样的易用性和直观性。

用户界面的设计不应该随意进行;相反,它应该与业务逻辑设计一起计划。大多数应用的成功是由用户对应用的反应来判断的。如果用户在与应用交互时不舒服,并且应用没有提高用户的生产力,那么它注定是失败的。对于用户来说,界面就是应用。业务逻辑代码有多原始和聪明并不重要;如果用户界面设计和实现得不好,应用将不会被用户接受。开发人员最好记住是用户驱动软件开发。

虽然 UML 不是专门为 GUI 设计而设计的,但是许多软件架构师和程序员使用 UML 图来帮助对应用的用户界面建模。

GUI 活动图

开发用户界面设计的第一步是进行任务分析,以发现用户将如何与系统交互。任务分析基于之前已经建模的用例及场景。然后,您可以开发活动图来模拟用户和系统之间的交互是如何发生的。使用前面的电影租赁应用示例,图 3-23 显示了一个活动图,该图模拟了租赁人员记录租赁信息所经历的活动。

9781430249351_Fig03-23.jpg

图 3-23 。用活动图进行 GUI 建模

界面原型

在您确定了必要的任务并对其进行了优先级排序之后,您就可以开发出构成用户界面的各种屏幕的原型草图了。图 3-24 显示了客户信息屏幕的原型草图。虽然你可以用纸和笔来开发你的图表,但是有一些很好的 GUI 原型工具可以提供通用的 GUI 设计模板和链接屏幕的能力,以及其他有用的特性。一些流行的商业原型工具包括微软的 SketchFlow、Balsamiq Mockups 和 AppMockupTools。一些流行的免费工具是铅笔项目,知更鸟和原型作曲家。图 3-24 所示的原型是使用铅笔投影工具创建的。

9781430249351_Fig03-24.jpg

图 3-24 。GUI 原型草图

界面流程图

一旦你原型化了不同的屏幕,你就可以使用界面流程图来建模组成用户界面的屏幕之间的关系和流程模式。图 3-25 显示了视频租赁应用的部分界面流程图。

9781430249351_Fig03-25.jpg

图 3-25 。界面流程图

应用原型

一旦你完成了屏幕布局和用户界面的设计,你就可以开发一个简单的原型。原型应该包含模拟系统功能的框架代码。在这一点上,您不应该花大力气将用户界面前端与应用的业务功能集成在一起。这个想法是让用户与一个工作原型交互,在用户界面上产生反馈。重要的是用户对界面设计感到舒适,并且使用起来直观。如果界面对用户来说很麻烦,不能提高他们的生产力,那么应用注定会失败。

改进和测试用户界面的过程将是反复的,很可能会持续几个周期。一旦应用的用户界面设计和内部功能设计已经完成并原型化,应用开发周期的下一步就是开始编码应用。

摘要

本章介绍了场景、序列图、协作图和活动图。您看到了如何使用这些图来建模对象交互。此外,您还了解了如何使用一些 UML 图来帮助对应用的用户界面进行建模。

这一章和前几章的目标是向你介绍一些软件设计和 UML 中涉及到的通用建模图和概念。在第四章的中,您将获得目前为止开发的概念,并使用它们来实现一个示例案例研究的解决方案设计。

活动答案

活动 3-1 答案

MemberLibrarianItemLoanLoanHistory。这五个对象包含在场景中描述的处理中。

该场景中描述的其他消息传递交互如下:

  1. Librarian对象用LoanHistory对象检查Member对象的借出历史。
  2. Librarian对象通过Item对象检查物品的可用性。
  3. Librarian对象通过Item对象更新物品的可用性。
  4. Librarian创建一个包含贷款信息的Loan对象。
  5. Librarian将贷款信息返回给Member对象。

活动 3-2 答案

A.Member、B. Librarian、C. Item、D. LoanHistory、E. Item、F. Loan、G. Loan、H. Librarian

Member对象负责请求电影活动。Librarian对象负责处理租赁和确认租赁活动。LoanHistory对象负责检查成员的贷款状态活动。Item对象负责检查可用性和更新物品状态活动。Loan对象负责计算到期日和记录租赁信息活动。

四、设计面向对象的解决方案:案例研究

为应用设计解决方案不是一件容易的事情。成为一名有成就的设计师需要时间和有意识的努力,这解释了为什么许多开发人员像躲避瘟疫一样躲避它。你可以研究理论,知道术语,但是真正发展你的建模技能的唯一方法是卷起袖子,弄脏你的手,开始建模。在本章中,您将了解办公用品订购系统的建模过程。虽然这不是一个复杂的应用,但是它包含了几个很好的用例,并且将由多个具有丰富的类交互的类组成。通过分析案例研究,您将更好地理解模型是如何开发的,以及各个部分是如何组合在一起的。

阅读本章后,您应该熟悉以下内容:

  • 如何使用 UML 为 OOP 解决方案建模
  • 要避免的一些常见 OOP 设计陷阱

开发面向对象的解决方案

在案例研究场景中,您的公司目前没有标准的部门订购办公用品的方式。每个部门分别实施自己的订购流程。因此,几乎不可能跟踪公司范围内的供应支出,这会影响预测预算和识别滥用的能力。当前系统的另一个问题是,它不允许单个联系人可以与各种供应商协商更好的交易。

因此,您被要求帮助开发一个公司范围的办公用品订购(OSO)应用。要对此系统进行建模,您需要完成以下步骤:

  • 创建软件需求规范(SRS)
  • 开发用例
  • 绘制用例图表
  • 给班级建模
  • 为用户界面设计建模

创建系统需求规格

在与提议系统的各种客户面谈之后,您开发 SRS。请记住第二章中的内容,SRS 确定了系统需求的范围,定义了系统边界,并确定了系统的用户。

您已经确定了以下系统用户:

  • 购买者:发起供货请求
  • 部门经理:跟踪并批准部门采购员的供货请求
  • 供应商处理申请:接收系统生成的订单文件
  • 采购经理:更新供应目录,跟踪供应请求,并登记交付的物品

您已经确定了以下系统要求:

  • 用户必须通过提供用户名和密码来登录系统。
  • 购买者将查看可供订购的供应品列表。
  • 购买者将能够按类别过滤供应品清单。
  • 采购员可以在一个采购请求中请求多个供应。
  • 部门经理可以为部门申请一般用品。
  • 部门经理必须在每周末批准或拒绝其部门的供应请求。
  • 如果部门经理拒绝请求,他们必须提供一个简短的解释,概述拒绝的原因。
  • 部门经理必须跟踪其部门内的支出,并确保有足够的资金用于批准的供应请求。
  • 采购经理维护供应目录,并确保其准确和最新。
  • 采购经理在收到供应品时进行登记,并组织供应品进行配送。
  • 已被请求但未被批准的供应请求标有待定状态。
  • 已批准的供应请求标有状态已批准,并生成订单。
  • 订单生成后,包含订单详细信息的文件将被放入订单队列。一旦订单被放入队列,它将被标记为状态已放置
  • 一个单独的供应商处理应用将从队列中检索订单文件,解析文档,并将行项目分发到适当的供应商队列。供应商处理应用会定期从供应商队列中检索订单,并将它们发送给供应商。(这是由一个单独的团队开发的。)
  • 当订单的所有项目都被登记时,订单被标记为已履行状态,买方被告知订单已准备好提货。

开发用例

在生成 SRS 并让适当的系统用户签字后,下一个任务是开发用例,它将从用户的角度定义系统将如何运行。开发用例的第一步是定义参与者。请记住第二章中的角色代表将与系统交互的外部实体(人类或其他系统)。从 SRS 中,您可以确定以下将与系统交互的参与者:

  • 买方
  • 部门经理
  • 采购经理
  • 供应商处理申请

既然您已经确定了参与者,下一步就是确定参与者将涉及的各种用例。通过检查 SRS 中的需求陈述,您可以识别各种用例。例如,“用户必须通过提供用户名和密码来登录系统”这句话表明需要一个登录用例。表 4-1 确定了 OSO 应用的用例。

表 4-1 。OSO 应用的用例

名字演员描述
注册采购员、部门经理、采购经理用户会看到登录屏幕。然后,他们输入用户名和密码。他们要么点击登录,要么点击取消。登录后,他们会看到一个包含产品信息的屏幕。
查看供应目录采购员、部门经理、采购经理用户会看到一个包含供应品列表的目录表。该表包含供应名称、类别、描述和成本等信息。用户可以按类别过滤供应品。
购买申请采购员、部门经理购买者在表格中选择商品,然后点击按钮将它们添加到购物车中。一个单独的表显示了他们购物车中的商品、所请求的每个商品的数量和成本,以及请求的总成本。
部门采购请求部门经理部门经理在表格中选择商品,然后单击按钮将它们添加到购物车中。一个单独的表显示了他们购物车中的商品、所请求的每个商品的数量和成本,以及请求的总成本。
请求审查部门经理部门经理会看到一个屏幕,其中列出了他们部门成员的所有待定供应请求。他们审查请求,并将其标记为批准或拒绝。如果他们拒绝请求,他们输入一个简短的解释。
跟踪支出部门经理部门经理会看到一个屏幕,上面列出了部门成员每月的支出以及部门的总支出。
维护目录采购经理采购经理能够更新产品信息、添加产品或将产品标记为停产。管理员还可以更新类别信息、添加类别,以及将类别标记为停止使用。
项目签入采购经理采购经理看到一个输入订单号的屏幕。然后,采购经理会看到为订单列出的行项目。已接收的物料被标记。当收到订单的所有项目时,它会被标记为已履行。
下单供应商处理应用供应商处理应用检查队列中的外发订单文件。文件被检索、解析并发送到适当的供应商队列。

绘制用例图表

既然您已经确定了各种用例以及参与者,那么您就准备好构建用例的图表了。图 4-1 显示了一个用 UMLet 开发的初步用例模型,它在第二章中有介绍。

9781430249351_Fig04-01.jpg

图 4-1 。初步的 OSO 用例图

在您绘制了用例之后,您现在要寻找用例之间可能存在的任何关系。可能存在的两种关系是包含关系和扩展关系。从第二章的讨论中记住,当一个用例包含另一个用例时,被包含的用例需要作为先决条件运行。例如,OSO 应用的登录用例需要包含在查看供应目录用例中。将登录作为一个单独的用例的原因是,登录用例可以被一个或多个其他用例重用。在 OSO 应用中,登录用例也将包含在跟踪支出用例中。图 4-2 描绘了这种包含关系。

9781430249351_Fig04-02.jpg

图 4-2 。包括登录用例

image 注意在一些建模工具中,包含关系可以在用例图中通过 uses 关键字来表示。

当一个用例将扩展初始用例的行为时,扩展关系存在于两个用例之间。在 OSO 应用中,当经理提出采购请求时,她可以表明她将为部门提出采购请求。在这种情况下,部门购买请求用例成为购买请求用例的扩展。 图 4-3 图示此扩展。

9781430249351_Fig04-03.jpg

图 4-3 。扩展购买请求用例

在分析了系统需求和用例之后,你可以通过分解应用并分阶段开发来使系统开发更易于管理。例如,您可以首先开发应用的购买请求部分。接下来,您可以开发请求检查部分,然后是项目签入部分。本章的其余部分集中在应用的购买请求部分。员工和部门经理将使用应用的这一部分来提出购买请求。图 4-4 显示了这个阶段的用例图。

9781430249351_Fig04-04.jpg

图 4-4 。采购申请用例图

开发班级模型

开发类模型包括几项任务。首先确定类,然后添加属性、关联和行为。

识别类别

在您确定了各种用例之后,您可以开始确定系统需要包含的类,以执行用例中描述的功能。为了识别这些类,您需要深入到每个用例中,并定义一系列执行用例所需的步骤。识别用例描述中的名词短语也很有帮助。名词短语通常是将需要的类的良好指示器。

例如,以下步骤描述了查看供应目录用例:

  • 用户已登录并被分配了用户状态级别。(这是前提条件。)
  • 向用户呈现包含供应品列表的目录表。该表包含供应名称、类别、描述和成本等信息。
  • 用户可以按类别过滤供应品。
  • 用户可以选择注销或提出购买请求。(这是岗位条件。)

从这个描述中,您可以确定一个负责从数据库中检索产品信息并过滤所显示产品的类。这个类的名称将是ProductCatalog类。

检查处理购买请求的用例描述中的名词短语揭示了 OSO 应用的候选类,如表 4-2 中所列。

表 4-2 。用于提出购买请求的候选类别

用例候选类别
注册用户、用户名、密码、成功、失败
查看供应目录用户、目录表、供应品列表、信息、供应品名称、类别、描述、成本
购买申请购买者、物品、购物车、编号、请求的物品、成本、总成本
部门采购请求部门经理、物品、购物车、编号、请求的物品、成本、总成本、部门采购请求

既然您已经确定了候选类,那么您需要排除那些表示冗余的类。例如,对项目和行项目的引用将代表相同的抽象。您还可以消除表示属性而不是对象的类。用户名、密码和费用是表示属性的名词短语的例子。有些类是模糊的,或者是其他类的概括。用户其实是采购员经理的概括。类也可能实际上引用相同的对象抽象,但是指示对象的不同状态。例如,供应请求和订单在批准前和批准后代表相同的抽象。您还应该过滤掉表示实现结构的类,如 list 和 table。例如,购物车实际上是组成订单的订单项目的集合。

使用这些排除标准,您可以将类别列表缩减到以下候选类别:

  • Employee
  • DepartmentManager
  • Order
  • OrderItem
  • ProductCatalog
  • Product

现在,您可以开始为 OSO 应用的购买请求部分设计类图了。图 4-5 显示了 OSO 应用的初步类图。

9781430249351_Fig04-05.jpg

图 4-5 。初步 OSO 类图

向类添加属性

开发类模型的下一个阶段是确定类必须实现的抽象层次。您可以确定哪些州信息与 OSO 应用相关。这个必需的状态信息将通过类的属性来实现。分析Employee类的系统需求揭示了对登录名、密码和部门的需求。您还需要一个标识符,比如员工 ID,来唯一地标识不同的员工。对经理的采访显示,需要包括雇员的名和姓,以便他们可以通过名字跟踪支出。表 4-3 总结了将包含在 OSO 类中的属性。

表 4-3。 OSO 类属性

属性类型
EmployeeEmployeeID
LoginName
Password
Department
FirstName
整数
DepartmentManagerEmployeeID
LoginName
Password
Department
FirstName
整数
OrderOrderNumber
OrderDate
日期
字符串
OrderItemProductNumber
Quantity
字符串
整数
小数
ProductProductNumber
ProductName
Description
UnitPrice
Category
字符串
字符串
字符串
小数
字符串
字符串
ProductCatalog没有人

图 4-6 显示了到目前为止已经确定了类属性的 OSO 类图。

9781430249351_Fig04-06.jpg

图 4-6 。添加了属性的购买请求组件类图

识别类别关联

开发过程的下一步是对 OSO 应用中存在的类关联进行建模。如果你研究了用例以及 SRS,你就能理解你需要在类结构设计中加入什么类型的关联。

image 注意你可能会发现你需要进一步细化 SRS 来暴露类关联。

例如,一个雇员将与一个订单相关联。通过检查关联的多样性,您会发现一个雇员可以有多个订单,但是一个订单只能与一个雇员相关联。图 4-7 模拟了这种关联。

9781430249351_Fig04-07.jpg

图 4-7 。描述雇员类和订单类之间的关联

当您开始识别类属性时,您会注意到Employee类和DepartmentManager类有许多相同的属性。这是有道理的,因为经理也是员工。在本应用中,经理代表具有特殊行为的员工。这种专门化由继承关系来表示,如图图 4-8 所示。

9781430249351_Fig04-08.jpg

图 4-8 。DepartmentManager 类继承自 Employee 类

以下陈述总结了 OSO 类结构中的关联:

  • 一个Order是一个OrderItem对象的集合。
  • 一个Employee可以有多个Order对象。
  • 一个Order与一个Employee相关联。
  • ProductCatalog与多个Product对象相关联。
  • 一个ProductProductCatalog相关联。
  • 一个OrderItem与一个Product相关联。
  • 一个Product可能与多个OrderItem对象相关联。
  • 一个DepartmentManager是一个具有专门行为的Employee

图 4-9 显示了这些不同的关联(为了清楚起见,不包括类属性)。

9781430249351_Fig04-09.jpg

图 4-9 。添加了关联的采购请求组件类图

类行为建模

既然您已经勾勒出了类的初步结构,那么您就可以对这些类如何交互和协作进行建模了。这个过程的第一步是深入到用例描述中,并创建一个用例将如何执行的更详细的场景。下面的场景描述了执行登录用例的一个可能的顺序。

  1. 用户会看到一个登录对话框。
  2. 用户输入登录名和密码。
  3. 用户提交信息。
  4. 检查并验证名称和密码。
  5. 向用户呈现供应请求屏幕。

尽管这个场景描述了登录用例中最常见的处理,但是您可能需要其他场景来描述预期的替代结果。以下场景描述了登录用例的另一种处理方式:

  1. 用户会看到一个登录对话框。
  2. 用户输入登录名和密码。
  3. 用户提交信息。
  4. 已检查名称和密码,但无法验证。
  5. 用户被告知不正确的登录信息。
  6. 再次向用户显示登录对话框。
  7. 用户要么重试,要么取消登录请求。

在这一点上,它可能有助于创建用例场景的可视化表示。记住第三章中的活动图经常被用来可视化用例处理。图 4-10 显示了为登录用例场景构建的活动图。

9781430249351_Fig04-10.jpg

图 4-10 。描述登录用例场景的活动图

在分析了用例场景中所涉及的过程之后,您现在可以将注意力转向为系统的类分配必要的行为。为了帮助识别需要发生的类行为和交互,你构建一个序列图,如第三章中所讨论的。

图 4-11 显示了登录用例场景的序列图。Purchaser (UI )类调用已经分配给Employee类的Login方法。该消息返回指示登录是否已被验证的信息。

9781430249351_Fig04-11.jpg

图 4-11 。描述登录用例场景的序列图

接下来,让我们分析一下视图供应目录用例。以下场景描述了用例:

  1. 用户已登录并通过验证。
  2. 用户查看包含产品信息的目录表,包括供应名称、类别、描述和价格。
  3. 用户选择按类别过滤表格,选择一个类别,然后刷新表格。

从这个场景中,您可以看到您需要一个ProductCatalog类的方法来返回产品类别列表。Purchaser类将调用这个方法。ProductCatalog类需要的另一个方法是返回按类别过滤的产品列表。图 4-12 中的序列图显示了Purchaser (UI)类和ProductCatalog类之间的交互。

9781430249351_Fig04-12.jpg

图 4-12 。描述查看供应目录方案的序列图

以下场景是为购买请求用例开发的:

  1. 一名采购人员已经登录并被确认为员工。
  2. 购买者从产品目录中选择商品,并将它们添加到订单请求(购物车)中,指明所请求的每个商品的数量。
  3. 在完成订单的项目选择之后,购买者提交订单。
  4. 订单请求信息被更新,订单 ID 被生成并返回给购买者。

从这个场景中,您可以确定需要创建的Order类的AddItem方法。该方法将接受产品 ID 和数量,然后返回订购产品的小计。Order类需要调用OrderItem类的方法,这将创建一个订单项的实例。您还需要一个Order类的SubmitOrder方法,该方法将提交请求和生成订单的退货订单 ID。图 4-13 显示了该场景的相关序列图。

9781430249351_Fig04-13.jpg

图 4-13 。描述购买请求场景的序列图

需要包括的一些其他场景是从购物车中删除商品、更改购物车中商品的数量以及取消订单流程。您还需要为部门采购请求用例包含类似的场景并创建类似的方法。在分析了需要发生的场景和交互之后,你可以为应用的购买请求部分开发一个类图,如图 4-14 所示。

9781430249351_Fig04-14.jpg

图 4-14 。采购申请类图

开发用户界面模型设计

在应用设计过程的这一点上,您不希望提交特定的 GUI 实现(换句话说,特定于技术的实现)。然而,对应用的 GUI 所需的一些公共元素和功能进行建模是有帮助的。这将帮助您创建一个原型用户界面,您可以用它来验证已经开发的业务逻辑设计。用户将能够与原型互动,并提供反馈和逻辑设计的验证。

您需要实现的第一个原型屏幕是用于登录的屏幕。您可以构建一个活动图来帮助定义用户登录系统时需要执行的活动,如图图 4-15 所示。

9781430249351_Fig04-15.jpg

图 4-15 。描述用户登录活动的活动图

分析活动图可以发现,您可以将登录屏幕实现为一个相当通用的界面。该屏幕应该允许用户输入用户名和密码。它应该包括一种方式来表明用户是作为雇员还是经理登录的。经理必须以员工身份登录才能提出购买请求,以经理身份登录才能批准请求。最后的要求是包括一个让用户中止登录过程的方法。图 4-16 显示了登录屏幕的原型。

9781430249351_Fig04-16.jpg

图 4-16 。登录屏幕原型

您需要考虑的下一个屏幕是产品目录屏幕。图 4-17 描述了查看和过滤产品的活动图。

9781430249351_Fig04-17.jpg

图 4-17 。描述产品浏览活动的活动图

活动图显示屏幕需要显示产品和产品信息的表格或列表。用户必须能够按类别过滤产品,这可以通过从类别列表中选择一个类别来启动。用户还需要能够发起订单请求或退出应用。图 4-18 显示了可用于查看产品的原型屏幕。单击添加到订单按钮将产品添加到订单,并启动订单详细信息屏幕,用户可以在其中调整订单数量。

9781430249351_Fig04-18.jpg

图 4-18 。查看产品屏幕原型

应用的这一部分需要原型化的最后一个屏幕是购物车界面。这将有助于从订单请求中添加和删除项目。它还需要允许用户提交订单或中止订单请求。图 4-19 显示了订单请求屏幕的原型。单击添加按钮将启动之前的产品目录屏幕,允许用户添加其他行项目。移除按钮将移除选中的行项目。用户可以通过单击向上和向下箭头来更改行项目的数量。

9781430249351_Fig04-19.jpg

图 4-19 。订单详细信息屏幕原型

这就完成了 OSO 应用这一阶段的初步设计。你应用了你在第二章和第三章中学到的知识来设计模型。接下来,让我们回顾一下在这个过程中要避免的一些常见错误。

避免一些常见的 OOP 设计陷阱

当你开始为自己的 OOP 设计建模时,你希望确保遵循最佳实践。以下是一些你应该避免的常见陷阱

  • 不要让用户参与过程:值得强调的是,用户是你产品的消费者。他们是定义系统的业务流程和功能需求的人。
  • 尝试一次性建模整个解决方案:当开发复杂系统时,将系统设计和开发分解成可管理的组件。计划分阶段生产软件。这将提供更快的建模、开发、测试和发布周期。
  • 努力打造完美模型:没有一个模型从一开始就是完美的。成功的建模者明白建模过程是迭代的,模型在整个应用开发周期中不断更新和修改。
  • 认为只有一种真正的建模方法:正如有许多不同的同样可行的 OOP 语言一样,也有许多同样有效的软件开发建模方法。选择一个最适合你和手头项目的。
  • 重新发明轮子:寻找模式和可重用性。如果您分析应用试图解决的许多业务流程,就会出现一组一致的建模模式。创建一个存储库,您可以在其中从一个项目到另一个项目,从一个程序员到另一个程序员利用这些现有的模式。
  • 让数据模型驱动业务逻辑模型:首先开发数据模型(数据库结构),然后在其上构建业务逻辑设计,这通常不是一个好主意。解决方案设计者应该首先询问需要解决什么业务问题,然后建立数据模型来解决问题。
  • 混淆问题域模型和实现模型:在设计应用时,你应该开发两个不同但互补的模型。领域模型设计描述了项目的范围和实现业务解决方案所涉及的处理。这包括将涉及哪些对象,它们的属性和行为,以及它们如何相互作用和关联。领域模型应该是与实现无关的。您应该能够使用同一个域模型作为几种不同的特定于架构的实现的基础。换句话说,您应该能够采用相同的域模型,并使用 Visual Basic 富客户端、两层架构或 C#(或 Java)n 层分布式 web 应用来实现它。

摘要

现在,您已经分析了 OOP 应用的领域模型,您已经准备好将设计转化为实际的实现了。本书的下一部分将向你介绍 C# 语言。你会看到 .NET 框架,并了解如何在框架之上构建 C# 应用。将向您介绍如何使用 Visual Studio IDE,并熟悉 C# 语言的语法。下一节还将演示在 C# 中实现 OOP 结构的过程,比如类结构、对象实例化、继承和多态。利用你新获得的技能,你将重温本章介绍的案例研究,并应用这些技能将应用设计转化为一个功能性的应用。

五、.NET 框架和 Visual Studio 简介

业务应用编程已经从两层、紧耦合的模型发展成多层、松耦合的模型,通常涉及通过互联网或公司内部网的数据传输。为了让程序员更有效率并处理这种模型的复杂性,微软开发了 .NET 框架。为了有效地用 C# 编程,你需要理解构建它的底层框架。

阅读本章后,您应该熟悉以下内容:

  • 那个 .NET 框架
  • 公共语言运行库(CLR)的功能
  • 实时(JIT)编译器的工作原理
  • 那个 .NET Framework 基类库
  • 命名空间和程序集
  • Visual Studio 集成开发环境的功能

介绍 .NET 框架

那个 .NET Framework 是基础类的集合,旨在提供运行应用所需的公共服务。让我们看看的目标 .NET 框架,然后查看它的组件。

的目标 .NET 框架

微软设计了 .NET 框架,并考虑到某些目标。下面几节研究这些目标以及 .NET 框架实现了它们。

行业标准的支持

微软想要的是 .NET 框架基于行业标准和实践。因此,该框架严重依赖于行业标准,如可扩展标记语言(XML)、HTML 5 和 OData。微软还向欧洲计算机制造商协会(ECMA)提交了一份公共语言基础设施(CLI)工作文件,该协会负责监督计算机行业的许多公共标准。

CLI 是创建符合 .NET 框架。第三方供应商可以使用这些规范来创建。符合. NET 的语言编译器;例如,交互式软件工程(ISE)已经为 Eiffel 创建了一个. NET 编译器。第三方供应商也可以创建一个 CLR 来允许 .NET 兼容的语言在不同的平台上运行。例如,Mono 是 CLR 的开源、跨平台实现,它赋予 C# 应用在 Linux 平台上运行的能力。

展开性

为了创建一个高效的编程环境,微软实现了 .NET 框架必须是可扩展的。于是,微软向开发者公开了框架类层次结构。通过继承和接口,您可以轻松地访问和扩展这些类的功能。例如,您可以创建一个按钮控件类,它不仅从 .NET Framework,而且以应用所需的独特方式扩展了基本功能。

微软还使底层操作系统的使用变得更加容易。通过在基于类的层次结构中重新打包和实现 Windows 操作系统应用编程接口(API)函数,微软使面向对象编程人员能够更直观、更容易地使用底层操作系统提供的功能。

统一编程模型

微软的另一个重要目标是 .NET 框架是跨语言独立和集成的。为了实现这个目标,所有支持公共语言规范(CLS)的语言都编译成相同的中间语言,支持相同的基本数据类型集,并公开相同的代码可访问性方法集。结果,不仅用不同的 CLS 兼容语言开发的类可以无缝地相互通信,而且你还可以跨语言实现 OOP 结构。例如,您可以开发一个用 C# 编写的类,该类继承自用 Visual Basic (VB)编写的类。微软已经开发了几种支持 .NET 框架。除了 C#,这些语言还有 VB.NET、托管 C++、JScript 和 F#。除了这些语言之外,许多第三方供应商还开发了其他流行语言的版本,用于在 .NET 框架,比如 Pascal 和 Python。

更容易部署

微软需要一种方法来简化应用部署。以前的发展 .NET Framework 中,当部署组件时,组件信息必须记录在系统注册表中。许多这些组件,尤其是系统组件,被几个不同的客户端应用使用。当客户端应用调用该组件时,会搜索注册表以确定使用该组件所需的元数据。如果部署了较新版本的组件,它将替换旧组件的注册表信息。通常,新组件与旧版本不兼容,导致现有客户端出现故障。您可能在安装了一个服务包后遇到过这个问题,该服务包导致的问题比它修复的问题还多!

那个 .NET Framework 通过将用于处理组件的元数据存储在一个名为清单的文件中来解决这个问题,该文件打包在包含组件代码的程序集中。程序集是包含运行应用所需的代码、资源和元数据的包。默认情况下,程序集被标记为私有,并与客户端程序集放在同一目录中。这确保了组件组合不会被无意中替换或修改,并且还允许更简单的部署,因为不需要使用注册表。如果需要共享一个组件,它的程序集被部署到一个称为全局程序集缓存(GAC)的特殊目录中。程序集的清单包含版本信息,因此组件的较新版本可以与 GAC 中的较旧版本并行部署。默认情况下,客户端程序集继续请求和使用它们打算使用的组件版本。当安装组件的新版本时,旧的客户端程序集将不再失败。

改进的内存管理

为 Windows 平台开发的程序的一个常见问题是内存管理。通常,这些程序会导致内存泄漏。当程序从操作系统分配内存,但在完成内存工作后未能释放内存时,就会发生内存泄漏。该内存不再可用于其他应用或操作系统,导致计算机运行缓慢,甚至停止响应。当程序打算长时间运行时,例如在后台运行的服务,这个问题就更复杂了。为了解决这个问题 .NET Framework 使用不确定的终结。框架使用垃圾收集对象,而不是依赖应用来释放未使用的内存。垃圾收集器定期扫描未使用的内存块,并将它们返回给操作系统。

改进的安全模型

在当今高度分布式、基于互联网的应用中实现安全性是一个极其重要的问题。过去,安全性主要集中在应用的用户身上。当用户登录到应用时,会检查安全身份,当应用调用远程服务器和数据库时,会传递用户的身份。对于当今企业级的松散耦合系统来说,这种类型的安全模型被证明是低效且难以实现的。为了使安全性更容易实现和更健壮 .NET Framework 使用代码标识和代码访问的概念。

创建程序集时,它会被赋予一个唯一的标识。创建服务器程序集时,您可以授予访问权限和权利。当客户端程序集调用服务器程序集时,运行库将检查客户端的权限和权利,然后相应地授予或拒绝对服务器代码的访问。因为每个程序集都有一个标识,所以还可以通过操作系统限制对程序集的访问。例如,如果用户从 Web 下载一个组件,您可以限制该组件在用户系统上读写文件的能力。

的组件 .NET 框架

现在您已经看到了的一些主要目标 .NET 框架,我们来看看它包含的组件。

公共语言运行时

的基本组件 .NET 框架是 CLR。CLR 管理正在执行的代码,并在代码和操作系统之间提供一个抽象层。CLR 内置了以下机制:

  • 将代码加载到内存中并准备执行
  • 将代码从中间语言转换为本机代码
  • 管理代码执行
  • 管理代码和用户级安全性
  • 自动释放和释放内存
  • 调试和跟踪代码执行
  • 提供结构化异常处理

框架基础类库

构建在 CLR 之上的是 .NET 框架基础类库。这个类库中包括引用类型和值类型,它们封装了对系统功能的访问。类型是数据结构。引用类型是一种复杂类型,例如类和接口。值类型是简单类型,例如整数或布尔。程序员使用这些基类和接口作为构建应用、组件和控件的基础。基本类库包括封装数据结构、执行基本输入/输出操作、调用安全管理、管理网络通信以及执行许多其他功能的类型。

数据类别

构建在基类之上的是支持数据管理的类。这组类通常被称为 ADO.NET。使用 ADO.NET 对象模型,程序员可以通过托管提供程序访问和管理存储在各种数据存储结构中的数据。微软已经编写并调整了 ADO.NET 类和对象模型,以便在松散耦合、互不连接的多层环境中高效工作。ADO.NET 不仅公开数据库中的数据,还公开与数据相关联的元数据。数据被公开为一种小型关系数据库。这意味着您可以在与数据源断开连接的情况下获取和使用数据,并在以后将数据与数据源同步。

微软已经为几个数据提供者提供了支持。存储在 Microsoft SQL Server 中的数据可以通过本机 SQL 数据提供程序进行访问。OLEDB 和开放式数据库连接管理(ODBC)提供程序是目前通过 OLEDB 或 ODBC 标准 API 公开的系统的两个通用提供程序。因为这些托管数据提供程序不直接与数据库引擎交互,而是与非托管提供程序进行通信,然后由非托管提供程序与数据库引擎进行通信,所以使用非本机数据提供程序比使用本机提供程序效率更低、功能更弱。由于 .NET 框架和微软对基于开放标准的承诺,许多数据存储供应商现在为他们的系统提供本地数据提供者。

建立在 ADO.NET 提供者模型之上的是 ADO.NET 实体框架。实体框架在数据库的关系数据结构和编程语言的面向对象结构之间架起了一座桥梁。它提供了一个对象/关系映射(ORM)框架,使得程序员无需为数据访问编写大部分管道代码。该框架提供诸如变更跟踪、身份解析和查询翻译等服务。程序员使用语言集成查询(LINQ)来检索数据,并将数据作为强类型对象来操作。第十章详细介绍 ADO.NET 和数据访问。

Windows 应用

在此之前。根据您是使用 C++还是 Visual Basic 进行开发,开发 Windows 图形用户界面(GUI)会有很大的不同。尽管用 VB 开发 GUI 很容易,而且可以很快完成,但 VB 开发人员与 Windows API 的底层功能隔离开来,没有完全接触到它。另一方面,尽管接触到了 Windows API 的全部功能,但用 C++开发 GUI 是非常乏味和耗时的。与 .NET Framework 中,微软引入了一组基类,在。与. NET 兼容的语言。这使得 Windows GUI 开发在各种 .NET 支持的编程语言,结合了开发的简易性和 API 的全部特性。

除了传统的 Windows 窗体和控件(自 Windows 操作系统出现以来就一直存在)之外 .NET Framework 包括一组类,统称为 Windows 演示基础(WPF)。WPF 集成了一个渲染引擎,旨在利用现代图形硬件和丰富的操作系统,如 Windows 7。它还包括应用开发功能,如控件、数据绑定、布局、图形和动画。有了 WPF 的类集,程序员可以创建提供非常引人注目的用户体验的应用。在第十一章中,你将更深入地了解如何构建基于 WPF 的应用。

网络应用

那个 .NET Framework 公开了一组基本类,可用于在 web 服务器上创建向启用 web 的客户端公开的用户界面和服务。这些类统称为 ASP.NET。使用 ASP.NET,您可以开发一个用户界面,它可以动态地响应发出请求的客户端设备的类型。在运行时 .NET Framework 负责发现发出请求的客户端的类型(浏览器类型和版本)并公开适当的接口。运行在 Windows 客户机上的 web 应用的 GUI 变得更加健壮,因为 .NET Framework 公开了许多 API 功能,这些功能以前只向传统的基于 Windows 窗体的 C++和 VB 应用公开。使用 .NET Framework 的一个优点是服务器端代码可以用任何。符合. NET 的语言。之前 .NET 中,服务器端代码必须用脚本语言编写,如 VBScript 或 JScript。

Visual Studio 2012 和 .NET framework 4.5 支持几种不同的创建 web 应用的模型。您可以基于 ASP.NET web 窗体项目、ASP.NET MVC 项目或 Silverlight 应用项目创建 Web 应用。第十二章涵盖了开发网络应用。

Windows 应用商店应用

一种新类型的 Windows 应用可用于 Windows 8 操作系统:Windows 商店应用。Windows 应用商店应用面向平板电脑和手机等利用触摸屏进行用户输入和持续互联网连接的设备。构建 Windows 应用商店应用有两种选择。您可以使用 .NET 语言(C#/VB/C++)与 XAML 结合使用,或者您可以将 JavaScript 与 HTML5 结合使用。Windows 应用商店应用是使用 WinRT API 构建的,这类似于针对。网络工作。微软甚至增加了一个. NET 框架 API 作为 WinRT API 的包装。这意味着您可以使用 .NET Framework 来使用您已经熟悉的编程语言和设计经验创建出色的 Windows 应用商店应用。事实上,正如你将在第十三章中看到的,使用 C# 和 XAML 创建 Windows Store 应用与构建 WPF 应用非常相似。

应用服务程序

包括在 .NET Framework 是基类和接口支持,用于公开可由其他应用使用的服务。在…之前 .NET 框架,用 C++和 VB 开发的应用使用了分布式组件对象模型(DCOM)技术。DCOM 是一种在分布于联网计算机上的软件组件之间进行通信的技术。因为 DCOM 是基于二进制标准的,所以通过防火墙和互联网的应用到应用的通信不容易实现。DCOM 的专有性质也限制了能够有效使用通过 COM 公开服务的应用并与之交互的客户端类型。

微软通过互联网标准公开服务来解决这些限制。包括在 .NET 框架是一组类,统称为 Windows 通信基础(WCF) 。使用 WCF,您可以将数据作为消息从一个应用发送到另一个应用。消息传输和内容可以根据消费者和环境轻松更改。例如,如果服务是通过 Web 公开的,那么可以使用基于 HTTP 的文本消息。另一方面,如果客户端在同一个公司网络上,则可以使用 TCP 上的二进制消息。

和。除了. NET 4.5 之外,微软还引入了一种新的创建 HTTP 服务的框架,称为 ASP.NET Web API。使用这个 API,您可以构建能够到达各种客户端的服务,比如 web 浏览器和移动设备。虽然您可以使用 WCF 创建这些服务,但新的 Web API 使其更容易实现,并包括内容协商、查询合成和灵活托管等功能。在第十四章中,你将使用 ASP.NET Web API 和 WCF 创建和消费应用服务。

使用 .NET 框架

使用 .NET Framework,您应该了解它是如何构造的,以及托管代码是如何编译和执行的。 .NET 应用被组织并打包成程序集。由执行的所有代码 .NET 运行库必须包含在程序集中。

了解程序集和清单

程序集包含运行应用所需的代码、资源和清单(关于程序集的元数据)。组件可以被组织成一个文件,其中所有的信息都被合并到一个动态链接库(DLL)文件或可执行(EXE)文件中,或者被组织成多个文件,其中信息被合并到单独的 DLL 文件、图形文件和清单文件中。程序集的主要功能之一是形成类型、引用和安全性的边界。大会的另一个重要功能是组成一个部署单位。

程序集最重要的部分之一是清单;事实上,每个程序集都必须包含一个清单。清单的目的是描述程序集。它包含程序集的标识、程序集向客户端公开的类和其他数据类型的说明、此程序集需要引用的任何其他程序集以及运行程序集所需的安全详细信息。

默认情况下,当创建程序集时,它被标记为私有。程序集的副本必须放在使用它的任何客户端程序集的同一目录或 bin 子目录中。如果程序集必须在多个客户端程序集之间共享,则它被放在全局程序集缓存(GAC) 中,这是一个特殊的 Windows 文件夹。若要将私有程序集转换为共享程序集,必须运行实用程序来创建加密密钥,并且必须用密钥对程序集进行签名。对程序集签名后,必须使用另一个实用工具将共享程序集添加到 GAC 中。通过制定创建和公开共享程序集的严格要求,Microsoft 试图确保不会发生恶意篡改共享程序集的情况。这也确保了同一程序集的新版本可以存在,而不会破坏使用旧程序集的当前应用。

引用程序集和命名空间

来制造 .NET 框架更易于管理,微软给了它一个层次结构。这种分层结构被组织成所谓的名称空间。通过将框架组织到名称空间中,命名冲突的机会大大减少了。将框架的相关功能组织到名称空间中也极大地增强了其对开发人员的可用性。例如,如果您想构建一个窗口的 GUI,那么您需要的功能很可能存在于System.Windows名称空间中。

所有的 .NET Framework 类驻留在系统命名空间中。系统命名空间按功能进一步细分。使用数据库所需的功能包含在System.Data名称空间中。一些名称空间有几层深度;例如,用于连接到 SQL Server 数据库的功能包含在System.Data.SqlClient名称空间中。

程序集可以组织成单个命名空间或多个命名空间。几个程序集也可以组织到同一个命名空间中。

中的类进行访问 .NET Framework,您需要在代码中引用包含命名空间的程序集。然后,您可以通过提供类的完全限定名来访问程序集中的类。例如,如果您想在表单中添加一个文本框,您可以创建一个System.Windows.Controls.TextBox类的实例,如下所示:

private System.Windows.Controls.TextBox newTextBox;

幸运的是,在 C# 中,您可以在代码文件的顶部使用 using 语句,这样就不需要在代码中不断引用完全限定名:

using System.Windows.Controls;
private TextBox newTextBox;

编译和执行托管代码

什么时候 .NET 代码被编译,它被转换成. NET 可移植可执行(PE)文件。编译器将源代码翻译成微软中间语言(MSIL)格式。MSIL 是独立于 CPU 的代码,这意味着它需要在执行前进一步转换为特定于 CPU 的本机代码。

除了 MSIL 代码,PE 文件还包括清单中包含的元数据信息。PE 文件中元数据的合并使得代码可以自我描述。不需要附加的类型库或接口定义语言(IDL)文件。

因为各种 .NET 兼容的语言编译成相同的 MSIL 和元数据格式 .NET 平台支持语言集成。这超越了微软的 COM 组件,例如,用 VB 编写的客户端代码可以实例化和使用用 C++编写的组件的方法。和 .NET 语言集成,您可以用 VB 编写一个. NET 类,它继承了用 C# 编写的类,然后重写它的一些方法。

在执行 PE 文件中的 MSIL 代码之前,. NET Framework 实时(JIT)编译器会将其转换为特定于 CPU 的本机代码。为了提高效率,JIT 编译器不会同时将所有 MSIL 代码转换为本机代码。MSIL 代码根据需要进行转换。当执行一个方法时,编译器检查代码是否已经被转换并放入缓存。如果有,则使用编译版本;否则,MSIL 码将被转换并存储在缓存中以备将来调用。

因为 JIT 编译器是针对不同的 CPU 和操作系统编写的,所以开发人员不需要重写他们的应用来针对各种平台。可以想象,你为 Windows 服务器平台编写的程序也可以在 UNIX 服务器上运行。所需要的只是一个用于 UNIX 架构的 JIT 编译器。

使用 Visual Studio 集成开发环境

您可以使用简单的文本编辑器编写 C# 代码,并使用命令行编译器编译它。然而,您会发现,使用文本编辑器编写企业级应用可能会令人沮丧且效率低下。大多数以编程为生的程序员发现集成开发环境(IDE)在易用性和提高生产率方面非常有价值。微软在 Visual Studio (VS) 中开发了一个出色的 IDE。集成到 VS 中的许多特性使得 .NET Framework 更直观、更简单、更高效。Visual Studio 的一些有用功能包括:

  • 编辑器功能,如自动语法检查、自动完成和颜色突出显示
  • 一个 IDE 供所有人使用。网络语言
  • 广泛的调试支持,包括设置断点、单步调试代码以及查看和修改变量的能力
  • 集成帮助文档
  • 拖放式 GUI 开发
  • XML 和 HTML 编辑
  • 与 Windows Installer 集成的自动化部署工具
  • 从 IDE 中查看和管理服务器的能力
  • 完全可定制和扩展的界面

下面的活动将向您介绍 VS IDE 中的一些特性。当您完成这些步骤时,不要担心编码细节。只需专注于习惯在 VS IDE 中工作。您将在接下来的章节中了解更多关于代码的内容。

image 注意如果没有安装 Visual Studio 2012,请参考附录 C 中的安装说明。

活动 5-1。游览 VISUAL STUDIO

在本活动中,您将熟悉以下内容:

  • 自定义 IDE
  • 创建. NET 项目并设置项目属性
  • 使用 VS IDE 中的各种编辑器窗口
  • 使用 VS IDE 的自动语法检查和自动完成功能
  • 用 VS IDE 编译程序集

定制 IDE

要定制 IDE,请按照下列步骤操作:

  1. Start Visual Studio 2012.

    image 注意如果这是你第一次启动 VS,你会被要求选择一个默认的开发设置。选择 Visual C# 开发设置。

  2. 您将看到起始页。起始页包含几个窗格,其中一个窗格链接到 MSDN (Microsoft Developer Network)网站上发布的有用文档。点击其中的一个链接将会启动一个托管在 VS 中的浏览器窗口,打开 MSDN 网站上的文档。花些时间研究一下信息和起始页上显示给你的各种链接。

  3. Microsoft has taken considerable effort to make VS a customizable design environment. You can customize just about every aspect of the layout, from the various windows and menus down to the color coding used in the code editor. Select Tools image Options to open the Options dialog box, shown in Figure 5-1, which allows you to customize many aspects of the IDE.

    9781430249351_Fig05-01.jpg

    图 5-1 。VS 选项对话框

  4. 在对话框左侧的“类别”列表中,单击“项目和解决方案”。您可以选择更改项目的默认位置,以及在构建和运行项目时会发生什么。选择“生成开始时显示输出窗口”选项。

  5. 调查其他一些可用的可定制选项。完成后,单击确定按钮关闭选项对话框。

创建新项目

要创建一个新项目,请遵循以下步骤:

  1. 在起始页上,单击“创建项目”链接,这将启动“新建项目”对话框。(您也可以选择文件image新建image项目来打开此对话框。)

  2. “新建项目”对话框允许您使用内置模板创建各种项目。有创建 Windows 项目、Web 项目、WCF 项目以及许多其他项目的模板,这取决于您在安装 VS 时选择的选项

  3. In the Templates pane, expand the Visual C# node and select the Windows node, as shown in Figure 5-2. Observe the various C# project templates. There are templates for creating various types of Windows applications, including Windows Forms-based applications, class libraries, and console applications.

    9781430249351_Fig05-02.jpg

    图 5-2 。VS 新建项目对话框

  4. 单击 Windows 窗体应用模板。将应用的名称更改为 DemoChapter5,然后单击 OK 按钮。

当项目打开时,您将看到一个添加到项目中的默认表单(名为 Form1)的表单设计器。在此窗口的右侧,您应该会看到解决方案资源管理器。

调查解决方案资源管理器和类视图

解决方案资源管理器显示作为当前解决方案一部分的项目和文件,如图 5-3 所示。默认情况下,创建项目时,会创建一个与项目同名的解决方案。该解决方案包含一些全局信息、项目链接信息和定制设置,如任务列表和调试信息。一个解决方案可以包含多个相关项目。

9781430249351_Fig05-03.jpg

图 5-3 。解决方案浏览器

解决方案节点下是项目节点。项目节点组织与项目相关的各种文件和设置。项目文件将这些信息组织在一个 XML 文档中,该文档包含对作为项目一部分的类文件的引用、项目所需的任何外部引用以及已设置的编译选项。项目节点下是属性节点、引用节点、App.config 文件、Form1类的类文件和Program类文件。

要练习使用解决方案资源管理器和一些 VS 特性和视图,请按照下列步骤操作:

  1. 在“解决方案资源管理器”窗口中,右击“属性”节点并选择“打开”。这将启动项目属性窗口。窗口左侧是几个选项卡,您可以使用它们来浏览和设置各种应用设置。

  2. Select the Application tab, as shown in Figure 5-4. Notice that, by default, the assembly name and default namespace are set to the name of the project.

    9781430249351_Fig05-04.jpg

    图 5-4 。项目属性窗口

  3. 在“项目属性”窗口中浏览其他一些选项卡。完成后,单击窗口选项卡中的 x 关闭窗口。

  4. 在"解决方案资源管理器"窗口中,展开"引用"节点。节点下是应用引用的外部程序集。请注意,默认情况下包含了几个引用。默认引用取决于项目的类型。例如,因为这是一个 Windows 应用项目,所以默认情况下包含对System.Windows.Forms名称空间的引用。

  5. 解决方案资源管理器的项目节点下的Form1类文件有一个. cs 扩展名,表示它是用 C# 代码编写的。默认情况下,文件名已设置为与表单相同的名称。在解决方案资源管理器窗口中双击该文件。该窗体显示在设计视图中。单击 Solution Explorer 顶部工具栏中的 View Code 按钮,将会打开Form1类的代码编辑器。

  6. 选择视图image类视图以启动类视图窗口。“类视图”窗口的顶部按照命名空间层次结构组织项目文件。展开 DemoChapter5 根节点会显示三个子节点:引用节点、DemoChapter5 命名空间节点和 DemoChapter5 属性节点。名称空间节点由节点名称左侧的{}符号指定。

  7. Listed under the DemoChapter5 namespace node are the classes that belong to the namespace. Expanding the Form1 node reveals a Base Types folder. Expanding Base Types shows the classes and interfaces inherited and implemented by the Form1 class, as shown in Figure 5-5. You can further expand the nodes to show the classes and interfaces inherited and implemented by the Form base class.

    9781430249351_Fig05-05.jpg

    图 5-5 。类视图中展开的节点

  8. “类视图”窗口的底部是该类的方法、属性和事件的列表。选择“类视图”窗口顶部的“窗体”节点。请注意窗口底部列出了大量的方法、属性和事件。

  9. Right-click the DemoChapter5 project node and select Add image Class. Name the class DemoClass1 and click the Add button. If the class code is not visible in the code editor, double-click the DemoClass1 node in the Class View window to display it. Wrap the class definition code in a namespace declaration as follows:

    namespace DemoChapter5

    {

    namespace MyDemoNamespace

    {

    class DemoClass1

    {

    }

    }

    }

  10. 请注意类视图中更新的层次结构。DemoClass1现在属于MyDemoNamespace,后者属于DemoChapter5名称空间。DemoClass1的全称现在是DemoChapter5.MyDemoNamespace.DemoClass1

  11. Add the following code to the DemoClass1 definition. As you add the code, notice the auto-selection drop-down list provided (see Figure 5-6). Pressing the Tab key will select the current item on the list.

`class DemoClass1: System.Collections.CaseInsensitiveComparer`
`{`

`}`

![9781430249351_Fig05-06.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/365f827843a94d79ac142767c90ee097~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=YK%2FaGPSIfFLKzVeVIwxBD2Ia27I%3D)

图 5-6 。代码选择下拉列表

12. 请注意类视图中更新的层次结构。展开 DemoClass1 节点下的 Base Types 节点,您将看到 base CaseInsensitiveComparer类节点。选择该节点,您将在“类视图”窗口的下半部分看到CaseInsensitiveComparer类的方法和属性。 13. Right-click the Compare method of the CaseInsensitiveComparer class node and choose Browse Definition. The Object Browser window is opened as a tab in the main window and information about the Compare method is displayed. Notice it takes two object arguments, compares them, and returns an integer value based on the result (see Figure 5-7).

![9781430249351_Fig05-07.jpg](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/acdb55601e304a3aabafbfb7d309b784~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771068303&x-signature=hT%2FAnR3BYDUHUSxRKtOfZlEnaIA%3D)

图 5-7 。对象浏览器

14. 对象浏览器使您能够浏览对象层次结构,并查看有关层次结构中的项和方法的信息。花些时间探索对象浏览器。完成后,关闭对象浏览器并关闭"类视图"窗口。

探索工具箱和属性窗口

要浏览 VS 工具箱和属性窗口,请遵循以下步骤:

  1. In the Solution Explorer window, double-click the Form1.cs node. This brings up the Form1 design tab in the main editing window. Locate the Toolbox tab to the left of the main editing window. Click the tab and the Toolbox window should expand, as shown in Figure 5-8. In the upper-right corner of the Toolbox, you should see the auto-hide icon, which looks like a thumbtack. Click the icon to turn off the auto-hide feature.

    9781430249351_Fig05-08.jpg

    图 5-8 。VS 工具箱

  2. 在“工具箱”的“所有 Windows 窗体”节点下是一些控件,您可以将它们拖放到窗体上以构建 GUI。还有其他包含非图形组件的节点,这些组件有助于简化一些常见编程任务的创建和管理。例如,数据节点包含用于访问和管理数据存储的控件。向下滚动工具箱窗口,观察设计器公开的各种控件。

  3. Under the All Windows Forms node, select the Label control. Move the cursor over the form; it should change to a crosshairs pointer. Draw a label on the form by clicking, dragging, and then releasing the mouse. In a similar fashion, draw a TextBox control and a Button control on the form. Figure 5-9 shows how the form should look.

    9781430249351_Fig05-09.jpg

    图 5-9 。示例表单布局

  4. 通过单击工具箱窗口右上角的自动隐藏(图钉)图标,重新打开工具箱的自动隐藏功能。

  5. 在“解决方案资源管理器”窗口下,找到主编辑窗口右侧的“属性”窗口。(您也可以在菜单步骤中选择查看image属性窗口,打开属性窗口。)属性窗口显示设计视图中当前选定对象的属性。您还可以通过此窗口编辑对象的许多属性。

  6. In the Form1 design window, click Label1. The Label1 control should be selected in the drop-down list at the top of the Properties window (see Figure 5-10). Locate the Text property and change it to “Enter your password:” (minus the quotes).

    9781430249351_Fig05-10.jpg

    图 5-10 。VS 属性窗口

    image 注意你可能需要重新排列表单上的控件才能看到所有的文本。

  7. 将 TextBox1 的Password Char属性设置为*。将 Button1 的Text属性改为 OK。(单击窗体上的控件或使用“属性”窗口顶部的下拉列表来查看控件的属性。)

  8. 通过选择文件image全部保存来保存项目。

构建和执行汇编

要构建和执行程序集,请遵循以下步骤:

  1. 在解决方案资源管理器中,双击 Form1 以打开设计窗口。

  2. 在窗体设计器中,双击 Button1 控件。Form1 的代码编辑器将显示在主编辑窗口中。代码编辑器中添加了一个处理按钮单击事件的方法。

  3. Add the following code to the method. This code will display the password entered in TextBox1 on the title bar of the form.

    private void button1_Click(object sender, EventArgs e)

    {

    this.Text = "Your password is " + textBox1.Text;

    }

  4. Select Build image Build Solution. The Output window shows the progress of compiling the assembly (see Figure 5-11). Once the assembly has been compiled, it is ready for execution. (If you can’t locate the Output window, select View menu image Output.)

    9781430249351_Fig05-11.jpg

    图 5-11 。输出窗口中显示的生成进度

  5. 选择调试image开始调试。这将在调试模式下运行程序集。表单加载后,输入密码并单击 OK 按钮。您应该会在表单的标题栏中看到包含密码的消息。单击右上角的 x 关闭表单。

  6. 选择文件image保存全部,然后通过选择文件image退出退出 VS。

活动 5-2。使用 VS 的调试特性

在本活动中,您将熟悉以下内容:

  • 设置断点并单步执行代码
  • 使用 VS IDE 中的各种调试窗口
  • 使用错误列表窗口定位并修复构建错误

步进代码

若要逐句通过您的代码,请按照下列步骤操作:

  1. 开始 VS .选择文件image新建image项目。

  2. 在 C# Windows 模板下,选择控制台应用。将项目活动重命名为 5_2。

  3. You will see a Program class file open in the code editor. The class file has a Main method that gets executed first when the application runs. Add the following code to the program class. This code contains a method that loads a list of numbers and displays the contents of the list in the console window.

    class Program

    {

    static List<int> numList = new List<int>();

    static void Main(string[] args)

    {

    LoadList(10);

    foreach (int i in numList)

    {

    Console.WriteLine(i);

    }

    Console.ReadLine();

    }

    static void LoadList(int iMax)

    {

    for (int i = 1; i <= iMax; i++)

    {

    numList.Add(i);

    }

    }

    }

  4. To set a breakpoint (which pauses program execution), place the cursor on the declaration line of the Main method, right-click, and choose Breakpoint image Insert Breakpoint. A red dot will appear in the left margin to indicate that a breakpoint has been set (see Figure 5-12).

    9781430249351_Fig05-12.jpg

    图 5-12 。在代码编辑器中设置断点

  5. 选择调试image开始调试。程序执行将在断点处暂停。黄色箭头表示将要执行的下一行代码。

  6. Select View image Toolbars and click the Debug toolbar. (A check next to the toolbar name indicates it is visible.) To step through the code one line at a time, select the Step Into button on the Debug toolbar (see Figure 5-13). (You can also press the F11 key.) Continue stepping through the code until you get to the LoadList.

    9781430249351_Fig05-13.jpg

    图 5-13 。使用调试工具栏

  7. 逐句通过代码,直到for循环循环几次。此时,您可能对这段代码的运行感到满意,并且想要跳出这个方法。在“调试”工具栏上,单击“跳出”按钮。您应该返回到 Main 方法。

  8. 继续遍历代码,直到for-each循环循环了几次。此时,您可能希望返回到运行时模式。为此,请单击“调试”工具栏上的“继续”按钮。当控制台窗口出现时,按 enter 键关闭窗口。

  9. 再次以调试模式启动应用。单步执行代码,直到到达方法调用LoadList(10);

  10. 在“调试”工具栏上,选择“单步执行”按钮。这将执行该方法,并在执行返回到调用代码后重新进入中断模式。单步执行该方法后,继续单步执行几行代码,然后选择"调试"工具栏上的"停止"按钮。单击左边距中的红点以删除断点。

设置条件断点

要设置条件断点,请遵循以下步骤:

  1. In the Program.cs file locate the LoadList method. Set a breakpoint on the following line of code:

    numList.Add(i);

  2. Open the Breakpoints window by selecting Debug image Windows image Breakpoints. You should see the breakpoint you just set listed in the Breakpoints window (see Figure 5-14).

    9781430249351_Fig05-14.jpg

    图 5-14 。断点窗口

  3. Right-click the breakpoint in the Breakpoints window and select Condition. You will see the Breakpoint Condition dialog box. Enter i == 3 as the condition expression and click the OK button (see Figure 5-15).

    9781430249351_Fig05-15.jpg

    图 5-15 。断点条件对话框

  4. 选择调试image开始。程序执行将暂停,您将看到一个黄色箭头,指示将执行的下一行。

  5. Select Debug image Windows image Locals. The Locals window is displayed at the bottom of the screen (see Figure 5-16). The value of i is displayed in the Locals window. Verify that it is 3. Step through the code using the Debug toolbar and watch the value of i change in the Locals window. Click the Stop Debugging button in the Debug toolbar.

    9781430249351_Fig05-16.jpg

    图 5-16 。局部窗口

  6. 在屏幕底部找到断点窗口。在“断点”窗口中右击断点,然后选择“条件”。通过清除“条件”复选框来清除当前条件,然后单击“确定”按钮。

  7. 在“断点”窗口中右击断点,然后选择“命中次数”。将断点设置为命中次数等于 4 时中断,然后单击“确定”。

  8. 选择调试image开始。程序执行将暂停,黄色箭头指示将执行的下一行代码。

  9. Right-click the numList statement and select Add Watch. A Watch window will be displayed with numList in it. Notice that numList is a System.Collections.Generics.List type. Click the plus sign next to numList. Verify that the list contains three items (see Figure 5-17). Step through the code and watch the array fill with items. Click the Stop button in the Debug toolbar.

    9781430249351_Fig05-17.jpg

    图 5-17 。观察窗

定位并修复构建错误

要定位并修复构建错误,请遵循以下步骤:

  1. In the Program class, locate the following line of code and comment it out by placing a two slashes in front of it, as shown here:

    //static List<int> numList = new List<int>();

  2. 注意代码中numList下面的红色曲线。这表明在应用运行之前必须修复一个生成错误。将鼠标悬停在该行上会显示关于错误的更多信息。

  3. Select Build image Build Solution. The Error List window will appear at the bottom of the screen, indicating a build error (see Figure 5-18).

    9781430249351_Fig05-18.jpg

    图 5-18 。使用“错误列表”窗口定位生成错误

  4. 在"错误列表"窗口中双击包含生成错误的行。相应的代码将在代码编辑器中可见。

  5. 通过删除斜线取消对步骤 1 中注释的行的注释。选择构建image构建解决方案。这一次,输出窗口显示在屏幕的底部,表明没有构建错误。

  6. 保存项目并退出 VS。

摘要

本章向您介绍了的基础知识 .NET 框架。您回顾了的一些基本目标 .NET 框架。你也看到了 .NET Framework 的结构以及 CLR 编译和执行代码的方式。这些概念都是相关且一致的 .NET 兼容的编程语言。此外,您还探索了 Visual Studio 集成开发环境的一些特性。虽然您不需要 IDE 来开发 .NET 应用,当涉及到生产力、调试和代码管理时,使用它是非常宝贵的。

使用 C# 可以开发许多类型的应用。这些应用包括使用 ASP.NET 的 Windows 桌面应用、使用 WPF 的 web 应用、使用 WinRT 的 Windows 应用商店应用以及使用 ASP.NET 或 WCF Web API 的应用服务应用。在以后的章节中,你将会看到这些技术,但是首先你需要学习如何使用 C# 编程。下一章是系列文章的第一章,着眼于 OOP 概念——比如类结构、继承和多态——是如何在 C# 代码中实现的。

六、创建类

在第五章中,你看了 .NET 框架的开发以及程序如何在框架下执行。那一章向您介绍了 Visual Studio IDE,并且您对使用它有了一些熟悉。您现在可以开始编码了!本章是向您介绍如何在 C# 中创建和使用类的系列文章的第一章。它涵盖了创建和使用类的基础知识。您将创建类,添加属性和方法,并在客户端代码中实例化这些类的对象实例。

阅读本章后,您应该熟悉以下内容:

  • OOP 中使用的对象如何依赖于类定义文件
  • 封装在面向对象程序设计中的重要作用
  • 如何定义类的属性和方法
  • 类构造函数的用途
  • 如何在客户端代码中使用类的实例
  • 重载类构造函数和方法的过程
  • 如何用 Visual Studio 创建和测试类定义文件

引入对象和类

在 OOP 中,你在程序中使用对象来封装与程序所处理的实体相关的数据。例如,人力资源应用需要与雇员一起工作。员工具有需要跟踪的相关属性。您可能对员工姓名、地址、部门等感兴趣。虽然您跟踪所有员工的相同属性,但是每个员工都有这些属性的唯一值。在人力资源应用中,Employee对象获取并修改与雇员相关的属性。在面向对象的程序设计中,对象的属性被称为特性。

除了雇员的属性,人力资源应用还需要一组由Employee对象公开的既定行为。例如,人力资源部门感兴趣的一个员工行为是请求休假的能力。在 OOP 中,对象通过方法公开行为。对象包含一个封装实现代码的RequestTimeOff方法。

OOP 中使用的对象的属性和方法是通过类定义的。类是一个蓝图,它定义了作为类的实例创建的对象的属性和行为。如果您已经完成了应用的正确分析和设计,那么您应该能够参考 UML 设计文档来确定需要构造哪些类以及这些类将包含哪些属性和方法。UML 类图包含了构建系统类所需的初始信息。

为了演示使用 C# 构建一个类,您将看到一个简单的Employee类的代码。Employee类将拥有封装和处理雇员数据的属性和方法,作为虚构的人力资源应用的一部分。

定义类别

让我们检查创建类定义所需的源代码。第一行代码将代码块定义为一个类定义,使用关键字class后跟类名。类定义的主体由一个左右花括号括起来。代码块的结构如下:

class Employee
{
}

创建类属性

定义了类代码块的起点和终点之后,下一步是定义类中包含的实例变量(通常称为字段)。这些变量保存类的实例将操作的数据。关键字private确保这些实例变量只能由类内部的代码操作。以下是实例变量定义:

private int _empID;
private string _loginName;
private string _password;
private string _department;
private string _name;

当该类(客户端代码)的用户需要查询或设置这些实例变量的值时,公共属性会向它们公开。在代码的属性块中有一个Get块和一个Set块。Get块将私有实例变量的值返回给该类的用户。这段代码提供了一个可读的属性。Set块提供写使能属性;它将客户端代码发送的值传递给相应的私有实例变量。下面是一个属性块的示例:

public string Name
{
    get { return _name; }
    set { _name = value; }
}

有时,您可能希望限制对属性的访问,以便客户端代码可以读取属性值,但不能更改它。通过删除属性块中的set块,您创建了一个只读属性。以下代码显示了如何将EmployeeID属性设为只读:

public int EmployeeID
{
    get { return _empID; }
}

image 注意私有和公共关键字影响代码的范围。有关范围的更多信息,请参见附录 a。

OOP 的新手经常会问,为什么有必要做这么多工作来获取和设置属性。难道不能创建用户可以直接读写的公共实例变量吗?答案在于 OOP 的基本原则之一:封装。封装意味着客户端代码不能直接访问数据。当处理数据时,客户端代码必须使用通过类的实例访问的明确定义的属性和方法。以下是以这种方式封装数据的一些好处:

  • 防止对数据的未授权访问
  • 通过错误检查确保数据完整性
  • 创建只读或只写属性
  • 将类的用户与实现代码中的更改隔离开来

例如,您可以通过以下代码检查以确保密码至少有六个字符长:

public string Password
{
    get { return _password; }
    set
    {
        if (value.Length >= 6)
        {
            _password = value;
        }
        else
        {
            throw new Exception("Password must be at least 6 characters");
        }
    }
}

创建类方法

类方法定义了类的行为。例如,下面为Employee类定义了一个验证雇员登录的方法:

public Boolean Login(string loginName, string password)
{
    if (loginName == "Jones" & password == "mj")
    {
        _empID = 1;
        Department = "HR";
        Name = "Mary Jones";
        return true;
    }
    else if (loginName == "Smith" & password == "js")
    {
        _empID = 2;
        Department = "IS";
        Name = "Jerry Smith";
        return true;
    }
    else
    {
        return false;
    }
}

当客户端代码调用该类的Login方法时,登录名和密码被传递给该方法(这些被称为输入参数)。检查参数。如果它们匹配一个当前的雇员,那么该类的实例将被该雇员的属性填充,一个布尔值true将被传递回调用代码。如果登录名和密码与当前雇员不匹配,一个布尔值false被传递回客户端代码。return关键字用于将控制权返回给具有指定值的客户端代码。

在前面的方法中,一个值被返回给客户端代码。这由 Boolean 关键字指示,该关键字将布尔值类型分配给返回值。下面的AddEmployee方法是Employee类的另一个方法。当一个雇员需要被添加到数据库中时,它被调用,并将新分配的雇员 ID(作为整数类型)返回给客户机。该方法还用新添加的雇员的属性填充了Employee类的对象实例。

public int AddEmployee(string loginName, string password,
            string department, string name)
{
    //Data normally saved to database.
    _empID = 3;
    LoginName = loginName;
    Password = password;
    Department = department;
    Name = name;
    return EmployeeID;
}

并非所有方法都向客户端返回值。在这种情况下,该方法用 void 关键字声明,以指示没有返回值。下面的方法更新密码,但不向客户端返回值。

public void UpdatePassword(string password)
{
    //Data normally saved to database.
    Password = password;
}

活动 6-1。创建雇员类

在本活动中,您将熟悉以下内容:

  • 如何使用 Visual Studio 创建 C# 类定义文件
  • 如何从客户端代码创建和使用类的实例

image 注意如果您还没有下载启动文件,请下载。有关说明,请参见附录 C。本实验是一个 Windows 窗体应用,它是可以用 Visual Studio 构建的 Windows 客户端类型应用之一。

定义员工类别

要创建 Employee 类,请遵循以下步骤:

  1. 启动 Visual Studio。选择文件image打开image项目。

  2. 导航到 Activity6_1Starter 文件夹,单击Activity6_1.sln文件,然后单击 Open。当项目打开时,它将包含一个登录表单。稍后您将使用这个表单来测试您创建的Employee类。

  3. Select Project image Add Class. In the Add New Item dialog box, rename the class file to Employee.cs, and then click Add. Visual Studio adds the Employee.cs file to the project and adds the following class definition code to the file (along with the name space definition and some using declarations.):

    class Employee

    {

    }

  4. Enter the following code between the opening and closing brackets to add the private instance variables to the class body in the definition file:

    private int _empID;

    private string _loginName;

    private string _password;

    private int _securityLevel;

  5. Next, add the following public properties to access the private instance variables defined in step 4:

    public int EmployeeID

    {

    get { return _empID; }

    }

    public string LoginName

    {

    get { return _loginName; }

    set { _loginName = value; }

    }

    public string Password

    {

    get { return _password; }

    set { _password = value; }

    }

    public int SecurityLevel

    {

    get { return _securityLevel; }

    }

  6. After the properties, add the following Login method to the class definition:

    public Boolean Login(string loginName, string password)

    {

    LoginName = loginName;

    Password = password;

    //Data nomally retrieved from database.

    //Hard coded for demo only.

    if (loginName == "Smith" & password == "js")

    {

    _empID = 1;

    _securityLevel = 2;

    return true;

    }

    else if (loginName == "Jones" & password == "mj")

    {

    _empID = 2;

    _securityLevel = 4;

    return true;

    }

    else

    {

    return false;

    }

    }

  7. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

测试雇员类

要测试Employee类,请遵循以下步骤:

  1. Open frmLogin in the code editor and locate the btnLogin click event code.

    image 提示双击表单设计器中的登录按钮也会在代码编辑器中调出事件代码。

  2. In the body of the btnLogin click event, declare and instantiate a variable of type Employee called oEmployee:

    Employee = new Employee();

  3. Next, call the Login method of the oEmployee object, passing in the values of the login name and the password from the text boxes on the form. Capture the return value in a Boolean variable called bResult:

    Boolean bResult = oEmployee.Login(txtName.Text,txtPassword.Text);

  4. After calling the Login method, if the result is true, show a message box stating the user’s security level, which is retrieved by reading the SecurityLevel property of the oEmployee object. If the result is false, show a login failed message.

    If (bResult == true)

    {

    MessageBox.Show("Your security level is " + oEmployee.SecurityLevel);

    }

    else

    {

    MessageBox.Show("Login Failed");

    }

  5. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

  6. 选择调试image开始运行项目。通过输入登录名 Smith 和密码 js 来测试登录表单。您应该会收到一条消息,指示安全级别为 2。尝试输入您的姓名和密码 pass。您应该会收到一条消息,表明登录失败。

  7. 测试登录过程后,关闭表单;这将停止调试器。

image 注意本章代码对无效登录使用异常。这只是为了演示的目的。抛出错误是一个开销很大的过程,不应该用于业务处理逻辑。有关正确使用抛出错误的更多信息,请参见附录 b。

使用构造函数

在 OOP 中,当类的对象实例被实例化时,使用构造函数来执行任何需要发生的处理。例如,您可以初始化对象实例的属性或建立数据库连接。类构造器方法的命名与类相同。当类的对象实例被客户端代码实例化时,将执行构造函数方法。下面的构造函数在Employee类中用来初始化Employee类的对象实例的属性。将雇员 ID 传递给构造函数,以便从数据存储中检索值,如下所示:

public Employee(int empID)
{
    //Retrieval of data hardcoded for demo
    if (empID == 1)
    {
        _empID = 1;
        LoginName = "Smith";
        Password = "js";
        Department = "IT";
        Name = "Jerry Smith";

    }
    else if (empID == 2)
    {
        _empID = 2;
        LoginName = "Jones";
        Password = "mj";
        Department = "HR";
        Name = "Mary Jones";
    }
    else
    {
        throw new Exception("Invalid EmployeeID");
    }
}

重载方法

重载方法的能力是 OOP 语言的一个有用的特性。通过定义多个同名但包含不同签名的方法,可以重载一个类中的方法。方法签名是方法名及其参数类型列表的组合。如果更改参数类型列表,则创建不同的方法签名。例如,参数类型列表可以包含不同数量的参数或不同的参数类型。编译器将通过检查客户端传入的参数类型列表来确定执行哪个方法。

image 注意改变参数的传递方式(换句话说,从byValbyRef)不会改变方法签名。改变方法的返回类型也不会创建唯一的方法签名。有关方法签名和传递参数的更详细讨论,请参考附录 a。

假设您想提供Employee类的两个方法,这两个方法将允许您向数据库中添加雇员。第一种方法是在添加员工时为员工分配用户名和密码。第二种方法添加雇员信息,但是将用户名和密码的分配推迟到以后。您可以通过重载Employee类的AddEmployee方法来轻松实现这一点,如以下代码所示:

public int AddEmployee(string loginName, string password,
           string department, string name)
{
    //Data normally saved to database.
    _empID = 3;
    LoginName = loginName;
    Password = password;
    Department = department;
    Name = name;
    return EmployeeID;
}

public int AddEmployee(string department, string name)
{
    //Data normally saved to database.
    _empID = 3;
    Department = department;
    Name = name;
    return EmployeeID;
}

因为第一个方法(string,string,string,string)的参数类型列表与第二个方法(string,string)的参数类型列表不同,所以编译器可以确定调用哪个方法。OOP 中一个常见的技术是重载类的构造函数。例如,当创建一个Employee类的实例时,一个构造函数可以用于新雇员,另一个可以用于当前雇员,方法是在客户机实例化类实例时传入雇员 ID。下面的代码显示了类构造函数的重载:

public Employee()
{
    _empID = -1;
}

public Employee(int empID)
{
    //Retrieval of data hard coded for demo
    if (empID == 1)
    {
        _empID = 1;
        LoginName = "Smith";
        Password = "js";
        Department = "IT";
        Name = "Jerry Smith";

    }
    else if (empID == 2)
    {
        _empID = 2;
        LoginName = "Jones";
        Password = "mj";
        Department = "HR";
        Name = "Mary Jones";
    }
    else
    {
        throw new Exception("Invalid EmployeeID");
    }
}

活动 6-2。创建构造函数和重载方法

在本活动中,您将熟悉以下内容:

  • 如何创建和重载类构造函数方法
  • 如何从客户端代码中使用类的重载构造函数
  • 如何控制一个类的方法
  • 如何从客户端代码中使用类的重载方法

创建和重载类构造函数

要创建和重载类构造函数,请遵循以下步骤:

  1. 启动 Visual Studio。选择文件image打开image项目。

  2. 导航到 Activity6_2Starter 文件夹,单击 Activity6_2.sln 文件,然后单击“打开”。当项目打开时,它将包含一个 frmEmployeeInfo 表单,您将使用它来测试Employee类。该项目还包括Employee.cs文件,其中包含了Employee类定义代码。

  3. 在代码编辑器中打开 Employee.cs 并检查代码。该类包含几个需要维护的与雇员相关的属性。

  4. After the property declaration code, add the following private method to the class. This method simulates the generation of a new employee ID.

    private int GetNextID()

    {

    //simulates the retrieval of next

    //available id from database

    return 100;

    }

  5. Create a default class constructor, and add code that calls the GetNextID method and assigns the return value to the private instance variable _empID:

    public Employee()

    {

    _empID = GetNextID();

    }

  6. Overload the default constructor method by adding a second constructor method that takes an integer parameter of empID, like so:

    public Employee(int empID)

    {

    //Constructor for existing employee

    }

  7. Add the following code to the overloaded constructor, which simulates extracting the employee data from a database and assigns the data to the instance properties of the class:

    //Simulates retrieval from database

    if (empID == 1)

    {

    _empID = empID;

    LoginName = "smith";

    PassWord = "js";

    SSN = 123456789;

    Department = "IS";

    }

    else if (empID == 2)

    {

    _empID = empID;

    LoginName = "jones";

    PassWord = "mj";

    SSN = 987654321;

    Department = "HR";

    }

    else

    {

    throw new Exception("Invalid Employee ID");

    }

  8. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

测试雇员类构造函数

要测试Employee类构造函数,请遵循以下步骤:

  1. 在窗体编辑器中打开 EmployeeInfoForm,双击“新雇员”按钮,在代码编辑器中调出 click 事件代码。

  2. In the click event method body, declare and instantiate a variable of type Employee called oEmployee:

    Employee oEmployee = new Employee();

  3. Next, update the employeeID text box with the employee ID, disable the employee ID text box, and clear the remaining textboxes:

    txtEmpID.Text = oEmployee.EmpID.ToString();

    txtEmpID.Enabled = false;

    txtLoginName.Text = "";

    txtPassword.Text = "";

    txtSSN.Text = "";

    txtDepartment.Text = "";

  4. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

  5. 在窗体编辑器中打开 EmployeeInfoForm,双击现有雇员按钮,在代码编辑器中调出 click 事件代码。

  6. In the click event method body, declare and instantiate a variable of type Employee called oEmployee. Retrieve the employee ID from the txtEmpID text box and pass it as an argument in the constructor. The int.Parse method converts the text to an integer data type:

    Employee oEmployee = new Employee(int.Parse(txtEmpID.Text));

  7. Next, disable the employee ID textbox and fill in the remaining text boxes with the values of the Employee object’s properties:

    txtEmpID.Enabled = false;

    txtLoginName.Text = oEmployee.LoginName;

    txtPassword.Text = oEmployee.PassWord;

    txtSSN.Text = oEmployee.SSN.ToString();

    txtDepartment.Text = oEmployee.Department;

  8. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

  9. 选择 Debug image Start 运行项目并测试代码。

  10. 当显示 EmployeeInfo 窗体时,单击“新建雇员”按钮。您应该看到在雇员 ID 文本框中已经生成了一个新的雇员 ID。

  11. 单击重置按钮清除并启用员工 ID 文本框。

  12. 为员工 ID 输入值 1,然后单击现有员工按钮。该员工的信息显示在表单上。

  13. 测试完构造函数后,关闭窗体,这将停止调试器。

重载一个类方法

要重载一个类方法,请遵循以下步骤:

  1. 在代码编辑器中打开 Employee.cs 代码。

  2. Add the following Update method to the Employee class. This method simulates the updating of the employee security information to a database:

    public string Update(string loginName, string password)

    {

    LoginName = loginName;

    PassWord = password;

    return "Security info updated.";

    }

  3. Add a second Update method to simulate the updating of the employee human resources data to a database:

    public string Update(int ssNumber, string department)

    {

    SSN = ssNumber;

    Department = department;

    return "HR info updated.";

    }

  4. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

测试重载更新方法

要测试重载的Update方法,请遵循以下步骤:

  1. 在窗体编辑器中打开 EmployeeInfo 窗体,双击“更新 SI”按钮。在代码编辑器窗口中,您将看到 click 事件代码。

  2. In the click event method, declare and instantiate a variable of type Employee called oEmployee. Retrieve the employee ID from the txtEmpID text box and pass it as an argument in the constructor:

    Employee oEmployee = new Employee(int.Parse(txtEmpID.Text));

  3. Next, call the Update method, passing the values of the login name and password from the text boxes. Show the method return message to the user in a message box:

    MessageBox.Show(oEmployee.Update(txtLoginName.Text, txtPassword.Text));

  4. Update the login name and password text boxes with the property values of the Employee object:

    txtLoginName.Text = oEmployee.LoginName;

    txtPassword.Text = oEmployee.PassWord;

  5. Repeat this process to add similar code to the Update HR button click event method to simulate updating the human resources information. Add the following code to the click event method:

    Employee oEmployee = new Employee(int.Parse(txtEmpID.Text));

    MessageBox.Show(oEmployee.Update(int.Parse(txtSSN.Text), txtDepartment.Text));

    txtSSN.Text = oEmployee.SSN.ToString();

    txtDepartment.Text = oEmployee.Department;

  6. 选择构建image构建解决方案。确保"错误列表"窗口中没有生成错误。如果有,修复它们,然后重新构建。

  7. 选择 Debug image Start 运行项目并测试代码。

  8. 为员工 ID 输入值 1,然后单击现有员工按钮。

  9. 更改安全信息的值,然后单击“更新”按钮。

  10. 更改人力资源信息的值,然后单击“更新”按钮。

  11. 您应该看到正确的Update方法是根据传递给它的参数调用的。测试完Update方法后,关闭表单。

摘要

本章为你在 C# 代码中创建和使用类打下了坚实的基础。既然您已经熟悉了构造和使用类,那么您就可以开始研究实现 OOP 的一些更高级的特性了。在第七章中,你将专注于继承和多态是如何在 C# 代码中实现的。作为一名面向对象的程序员,熟悉这些概念并学习如何在程序中实现它们是很重要的。