第3 章 面向对象设计 软件系统离不开硬件系统,一个基于计算机的系统离不开使用该系统的客户和整个 社会。所有这些元素:硬件设备、操作系统、网络、应用系统、业务过程、各种机构和社会 形成了复杂的“社会技术系统”,如图3-1所示。这个系统的建立、维护是一个系统工程问 题。软件工程只是整个社会技术系统中的一部分。 图3-1 社会技术系统(萨默维尔,《软件工程》(第9版)) 处于社会技术系统中的软件设计应当展示坚固性(Firmness)、适用性(Commodity) 和愉悦(Delight),以满足“社会性”要求。坚固性指程序不应含有缺陷;适用性指程序应 当满足其愿景;愉悦指用户体验应当是愉悦的。 软件工程中的设计包括类/数据设计、体系结构设计、接口设计和组件设计。类设计 是对分析模型中的类模型进行补充和修订;体系结构设计定义软件主要构造元素之间的 关系;接口设计描述软件与协作系统之间、软件与用户之间如何进行交互;组件级设计对 软件组件进行过程性描述。所以,设计过程就是把分析模型映射到设计模型的过程,如 图3-2所示。 分析模型中包括从功能、结构和行为三个方面描述的系统逻辑模型,如果有必要,还 可以包括面向数据流的模型。分析模型是软件开发者与客户沟通的结果,也是软件设计 者的出发点,所以分析模型是需求与设计之间的桥梁。由于概念的一致性,面向对象的分 第 3 章 面向对象设计 47 图3- 2 从分析模型到设计模型的映射 析与设计是一个无缝衔接和过渡的过程。 软件体系结构(Architecture)也称为架构、构架,是计算组件以及组件之间的交互。 这里的组件可以是子系统、框架等。 一个复杂的软件系统,往往分解成若干个子系统,以完成相对独立的功能。多个子系 统相互配合满足整个系统的需求。一个大型企业往往部署了多套系统,通过系统间的互 操作,把这些业务系统集成起来,就形成了“集成系统”。子系统的基本组成单元是类,一 组相关的类通常被组织在类库中。 框架(Framework)是一个半成品,是一个可实例化的、部分完成的软件子系统,为构 造完成的系统提供了基础设施。在面向对象的环境中,框架由接口、抽象类和实现类组 成。所以,从设计角度看,框架是一组相互协作的类,是形成某类软件的可复用的设计。 开发者通过继承框架类中的类和组合其实例来定制该框架以得到特定的应用。框架也是 按照一定的架构开发的。 类库是类的集合,有些类之间可能是相互独立的。框架中的类并不是孤立的,存在协 作关系。 软件架构设计的内容包括:规划目标系统的子系统,为子系统分配不同的职责,并使 这些子系统通过协作完成功能需求;深入研究预 测运行期间系统应满足的质量需求,如响应时间、 吞吐量、并发、负载、可用性等运行时刻质量属性, 制定相应的设计决策;为满足目标系统可测试性、 可维护性、可扩展性、可复用性等开发时期质量属 性,做出相应设计决策。约束也是一类特殊的需 求,带有一定的强制性,架构的设计决策应满足这 些限制。 针对不同的需求类型,产生不同的架构视图, 如图3-3所示。 逻辑架构关注于业务功能需求,即目标系统 的行为以及目标系统行为的分解。逻辑架构关心的是如何将系统分解为弱耦合的不同部 分以及各部分之间如何交互,也就是规定软件系统由哪些宏观逻辑元素组成以及这些逻 辑元素之间的关系。逻辑元素有层、子系统、模块等。关系包括交互接口和交互机制等, 图3-3架构视图 48 UML 面向对象分析与设计 如使用JDBC 中间件连接数据库服务器、使用消息中间件Kafka等。 数据架构关注于持久化数据的组织、传递、复制和同步等策略。侧重于从业务数据流 的角度描述本系统数据与上下游系统数据之间的关系,以及针对本系统承载的业务处理, 设计了哪些与关键实体对象对应的实体数据表。要明确本系统处理的数据在整个业务数 据流链条上的位置,说明数据初始化方式、数据冗余策略、分库分表方法和数据库备份方 案等。 物理架构则关注于将通过编译的目标系统安装和部署到服务器、网络、嵌入式设备等 硬件设备上,建立软件单元和硬件单元的映射。物理架构关注系统分为几部分、各个部分 如何进行物理部署;部署系统各个部分之间的各服务器的协作关系;明确硬件服务器的型 号、数量等配置,如多核数目、内存容量、硬盘热插拔、网络端口数及网络带宽要求,软件方 面对操作系统的类型、版本要求,服务器软件版本要求、参数的调优设置,各个软件之间的 协同配置等;明确整体部署的网络区域,如外联区、DMZ 区或内网区等。物理架构还关注 可用性、可伸缩性、安全性等质量因素,需要描述清楚通过怎样的集群或热备部署保证可 用性、系统做了何种设计或优化以满足伸缩性要求、设计了何种校验机制保障安全。每类 非功能性应能够追溯到需求,与业务实际相匹配。 开发架构关注于软件的可扩展性、可移植性、可复用性、可测试性等质量属性,从技术 的角度描述本系统在实现过程中用到的关键技术、核心技术组件,包括成熟商业套件以及 开源技术产品。架构中的元素包括源文件、配置文件、包、第三方类库等。在程序员看来, 开发架构就是基于什么框架。可复用性是技术架构的关键,无论是历史遗留组件还是开 源框架,都是复用。要说明类库的版本、功能、适用场景。如果是商业套件,需要说明使用 限制、升级支持等;如果是开源框架,需要说明开源协议要求。通过描述所有与外部系统 的接口定义系统与外部系统之间的外部接口关系。详细说明每一个外部接口的名称(如 ×× 消息推送接口)、所交互的系统名称(如一卡通系统向kafka推送消息)、交互方式(如 WebService)、交互风格(如RESTful风格)、通信方式(如异步)、接口描述(如一卡通系 统通过此接口从kafka中获取×× 业务数据)。 运行架构则关注易用性、响应时间、负载等质量属性。 软件架构设计从大局着手,就技术方面重大问题做出决策,构造了一个具有一定抽象 层次的解决方案,而没有深入到细节,从而控制了“技术复杂性”。有了架构设计,不同的 开发小组就可以在不同的子系统上按照子系统间的契约并行开发,形成了大规模开发的 基础。负责界面开发的团队只需研究用户界面工具包,而不必关心负责数据库访问的 SQL 语句如何设计;数据库开发人员不再关心界面如何设计,而只需按照约定的应用程 序接口设计程序。 3.软件体系结构风格 1 风格是事物的形态特征,是文化、思想的表征。软件体系结构风格定义了组件和组件 连接的策略。针对复杂系统的体系结构,先后形成了Layer和Tier两种基本风格。 Layer风格面向同族系统,形成紧密的垂直访问体系;Tier风格面向异族系统,形成松散 第 3 章 面向对象设计 49 的水平协作体系。另外,还有面向服务的体系结构风格等。 3.1.1 Layer 风格 Layer风格是面向同族系统的一种软件体系结构风格。所谓同族系统,指内部各个 层次之间的关系对外部系统来说是透明的。外部系统只能与该系统的顶层交互。Layer 风格也可称为垂直型层次风格。 垂直型分层结构是一种广泛应用的软件体系结构风格。各个子系统按照层次的形式 组织,上层访问下层的各种服务,而下层对上层一无所知。每一层都对自己的上层隐藏其 下层的细节。一般是下层为上层提供服务,而且一般不会跨层服务。操作系统的设计就 采用了分层架构,如图3-4所示。其中定义了一系列不同的层次,每个层次各自完成操 作,这些操作逐步接近机器的指令集。在最外层,图形用户界面、命令行窗口等程序完成 用户界面的操作,与用户交互;浏览器、字处理软件、图像处理软件等应用软件形成应用程 序层;用户通过界面访问应用程序。应用程序访问通过操作系统提供的预定应用程序接 口(API)完成存盘、网络传输等功能;而这些API 则通过核心代码与硬件交互。 图3- 4 操作体系的层次体系结构 TCP/IP 协议簇是互联网中基本的协议,设计为一个四层的体系结构:应用层、传输 层、网络层和数据链路层。应用层的主要协议有远程登录(Telnet)、文件传输协议 (FTP )、简单邮件传输协议(SMTP)等,是用来接收来自传输层的数据或者按不同应用要 求与方式将数据传输至传输层;传输层的主要协议有传输控制协议(TCP )、用户数据报协 议(UDP), 可以实现面向连接的或无连接的端到端数据传输;网络层的主要协议有网际 报文控制协议(ICMP )、网际协议(互联网组管理协议( IP )、IGMP), 主要负责网络中数据 包的传输路径选择等;而网络访问层,也叫网络接口层或数据链路层,主要功能是提供点 到点的链路管理等。例如,地址解析协议(ARP )、反向地址解析协议(RARP)用来管理 IP 地址和物理地址映射。图3-5以应用层协议(FTP)为例,展示了这四个层次之间的关 系:FTP 客户端将数据打包成报文交给TCP;TCP 建立目标进程的连接,将报文拆分成 50 UML 面向对象分析与设计 “包”交给IP 存储转发; IP 层再根据数据链路层协议将包封装到数据帧中传递给下个节 点。目标计算机接收数据帧,恢复成IP 包再交给传输层。等所有包到齐后传输层恢复给 FTP 完整的报文。 图3- 5 TCP/IP 层次 3.1.2 Tier 风格 Tier风格是面向异族系统的一种体系结构风格。异族系统指系统中各个子系统之 间的关系对外部系统来说是不透明的,外部系统可以与任何子系统交互。Tier风格一般 采用水平型层次以体现这种松耦合的特征。Tier风格的案例包括客户机/服务器、三层 体系结构风格、浏览器/服务器等。 1. 客户机/服务器 客户机/服务器体系结构风格中,整个软件系统分解成两种角色:客户机和服务器。 客户机和服务器一般是多对一的关系,即 N 个客户机访问1个服务器。用户仅与客户机 交互,由客户机向服务器发出访问请求,服务器响应客户机请求完成相应功能。 基于局域网的管理信息系统广泛采用客户机/服务器体系结构,例如,会计核算系统、 工资管理系统。一般来说,服务器上部署的一个数据库管理系统,称为数据库服务器。业 务处理程序部署在桌面计算机上,作为客户机;若干客户机通过局域网与数据库服务器连 接。客户机所有访问数据库的需求都通过服务器完成:客户机发出请求(SQL 语句), 服 务器接收请求并执行,把执行结果返回给客户机。客户机/服务器架构如图3-6所示。 图3- 6 客户机/服务器架构 客户机和服务器实际上是角色的名称。服务器可能指具有接收和处理请求能力的计 算机硬件,也可能指进程。客户机也未必一定是一台计算机,也可能是一部手机、一个嵌 51 第 3 章 面向对象设计 入式设备,或者一个进程。为了一般起见,后面不再使用“客户机”,而使用“客户端”或者 简单将其简称为“客户”。最简单、最常见的客户/服务器体系结构有多个客户端和一个服 务器,如图3-7所示,称为多客户端-单服务器架构。 图3- 7 多客户端-单服务器架构 在TCP/IP 的应用层协议中,FTP 、SMTP 、HTTP 等都是多客户端-单服务器体系结 构风格。 客户/服务器体系结构中允许有多个服务器,每个客户端允许与多个服务器通信,同 时服务器之间也允许通信,形成“多客户端-多服务器”架构,如图3-8所示。 图3- 8 多客户端-多服务器架构 客户端与服务器之间的通信方式有:同步消息通信、异步消息通信、中介模式等。 “请求/响应”通信方式是一种典型的同步消息通信:客户端向服务器发送消息并等 待回复。单个客户端和单个服务器之间不需要消息队列,在多客户端和单服务器情形中, 服务器端会有一个消息队列。 在异步消息通信模式中,客户端向服务器发送消息后并不等待回复,继续做自己的事 情;如果消息到达服务器而服务器正忙,则消息进入等待队列。 如果客户/服务器体系结构中,客户能够承担服务器角色,服务器也能承担客户角色, 则形成了对等体系结构风格。 在多对多的客户/服务器体系结构中,有很多服务器提供多种服务,客户可能知道使 用哪个服务但是不知道需要的服务在哪里,中介模式即可解决这类问题:客户端首先向 中介发送服务请求;中介查询服务的位置并将请求转发;服务器处理请求并将结果回复给 中介;中介再把回复转发给客户。 如果客户端知道请求的服务类型而不是特定的服务实例,那么可以使用服务发现通 52 UML 面向对象分析与设计 信模式。在这种模式中,客户端发送一个查询请求给中介,请求给定类型的所有服务,中 介回复一个服务清单;客户端从中选择一个服务,然后与服务器直接通信。 2.浏览器/服务器 当Web中的服务器不仅能够响应浏览器发出的页面、图片、文件、视频、音频等静态 资源请求时,还能够响应浏览器对服务器端脚本的请求时,那么在这种服务器上就能够部 署完成应用功能的代码,形成基于Web的应用。这种应用的特点是:浏览器是统一的客 户端,对业务逻辑的处理主要集中在服务器上,只需部署一次,如图3-9所示。 图3- 9 浏览器/服务器架构 浏览器也是一种客户端。由于客户机/服务器体系中的客户端既负责用户界面,又负 责应用逻辑,承担较多的责任,所以一般把将客户机/服务器体系中的客户端称为“胖客户 端”;而浏览器只负责人机界面,所以称为“瘦客户端”。 3.三层体系结构风格 三层(3-tier)体系结构中有三个子系统:用户接口层(UserInterfaceLayer,UI),业 务逻辑层(BusinesLogicLayer,BLL)和数据访问层(DataAcesLayer,DAL )。用户接 口层也称为表现层。这种划分体现了“高内聚,低耦合”的思想。用户接口层负责与用户 交互,包括表单、脚本、样式等元素;业务逻辑层包含实体对象和控制逻辑,完成业务功能; 数据访问层实现对象的持久化和查询。该层直接操作数据库,对数据增添、删除、修改、更 新、查找等,如图3-10所示。 图3-10 三层体系结构 注意这里的层是tr而不是lyr,r指的是子系统之间的独立性,每个子系统都是 ieaetie 可替换的。每一层之间通过变量或对象作为参数传递数据,这样就构造了三层之间的联 系,完成了功能的实现。 53 第 3 章 面向对象设计 图3-11 展示了现实生活中的三层架构到软件体系结构的映射。在饭店就餐服务业 务中,服务员只负责接待客人;厨师只负责烹饪客人点的菜;采购员只管采购食材。顾客 直接和服务员(UI)打交道,顾客和服务员说:我要一个醋溜土豆丝。服务员就把请求传 递给厨师(BLL), 厨师需要土豆,就把请求传递给采购员(DAL), 采购员从仓库里取来土 豆传回给厨师,厨师切丝炒菜装盘后,传回给服务员,服务员把菜品上桌呈现给顾客。在 此过程中,土豆作为参数在三层中传递。这个模型的最大好处就是各层之间耦合度很低, 每一层的变化不影响其他层以及整个业务。例如,更换服务员不会影响厨师。 图3-11 现实世界中的三层架构 再例如,天气预报的手机APP 首先通过数据访问层得到温度、空气污染指数(API) 等数据;业务逻辑层按照一定的逻辑进行处理,如 把API 值映射到“优”“良”“轻度”等状态;用户接 口层则以图形、图标、颜色等形式显现给用户,如 图3-12 所示。 三层体系结构必须通过中间层完成,有时会导 致级联的修改,对系统的性能有负面影响。如果在 用户接口层中需要增加一个功能,为保证其设计符 合分层式结构,可能需要在相应的业务逻辑层和数 据访问层中都增加相应的代码,从而也会影响可维 护性。 4. 四层体系结构 四层体系结构由表示层、请求处理层、业务逻 辑层和数据访问层组成,如图3-13 所示。 表示层是负责终端上的界面渲染并显示的层, 图3-12 天气预报APP 当前主要技术包括Java脚本、HTML 、样式表等; 请求处理层(Web层)主要负责对访问请求进行转发,对各类基本参数校验,或者对业务 进行简单处理等;业务逻辑层(Service层)是相对具体的业务逻辑服务层;数据访问层 (DAO 层)与MySQL 、Oracle、HBase等进行数据交互。 54 UML 面向对象分析与设计 图3-13 四层体系结构 这种分层的结构很容易扩展,如增加开放接口层,把Service方法封装成远程过程调 用(RPC)接口或者封装成Web服务接口等,就可以让其他系统直接使用业务逻辑层的功 能和接口。若要访问外部接口或第三方平台包括其他部门RPC开放接口、基础平台、其 他公司的Web服务接口,则可以引入一个通用处理层(Manager层)对第三方平台封装, 处理返回结果及转换异常信息,通过缓存、中间件增加对Service层的通用能力。这样,通 用处理层向业务逻辑层提供了服务,也可以与DAO层交互,使用DAO层的服务。 3.1.3 模型-视图-控制器 模型-视图-控制器(Model-View-Controler,MVC)是一种体系结构风格,如图3-14 所示。视图是用户看到并与之交互的界面。对Web应用程序来说,视图就是由HTML 元素组成的界面。当用户单击Web页面中的超链接或者提交HTML表单时,控制器负 责接收请求,但本身不输出任何东西和做任何处理,它只是接收请求并决定调用哪个模型 处理请求,然后再确定用哪个视图显示返回的数据。所以,在MVC中,模型提供了要展 示的数据,以及访问数据的行为,是领域模型。也就是模型提供了模型数据查询和模型数 据的状态更新等功能。视图负责进行模型的展示,一般就是用户界面。控制器负责接收 图3-14 Web应用中的MVC风格 第3 章 面向对象设计 55 用户请求,委托给模型进行处理,处理完毕后把返回的模型数据返回给视图,由视图负责 展示。也就是说,控制器承担调度员角色。 下面使用一个简单的例子进行说明。假设有如下JSP界面。 <html> <head> <title>Menu</title> </head> <body> <a href = "$ {pageContext.request.contextPath}/AddServlet">列表</a><br> </body> </html> 这个页面就是一个视图。当用户在Web页面上单击“列表”超级链接时,这个请求就 发送给了控制器ListServlet,这个Servlet控制器定义如下。 @WebServlet("/ListServlet") public class ListServlet extends HttpServlet { //… protected void doGet(HttpServletRequest request , HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/WEB-INF/jsp/list.jsp") .forward(request, response); } //… } 该控制器是通过继承HttpServlet实现的,其中的doGet()方法把请求转发给了list. jsp,这就是“选择视图响应”。list.jsp页面通过DbAccess访问数据库,向模型发出“更新 请求”,模型访问数据库,返回结果是实体Professor 类的对象,这些对象由容器 professors管理。JSP把“来自模型的数据”遍历后,逐个在浏览器中显示所有对象,即把 “HTML数据”发送给浏览器渲染。 <html> <head> <meta http-equiv= "Content-Type" content= "text/html; charset= UTF-8"> <title>列表</title> </head> <body> <h1>Professors</h1> <% for (Professor p : DbAccess.professors) { out.println(p); out.write("<br>"); } 56 UML 面向对象分析与设计 % > <br> </html> 3.1.4 面向服务的架构 面向服务的架构(Service-OrientedArchitecture,SOA)是由若干自治的服务组成的 分布式软件体系结构。各个服务可以运行在不同平台上,可以用不同的语言实现。通过 标准的协议注册、发现和协调服务。SOA 是一种粗粒度、松耦合的服务架构,服务之间通 过简单、精确定义的接口进行通信,不涉及底层编程接口和通信模型。 尽管面向服务的架构在概念上与平台无关,但是Web服务的技术是面向服务架构的 成功实现。从客户角度看,Web服务是部署在Web上的对象,具备完好的封装性、松散 耦合、自包含、互操作、动态、独立于实现技术、可集成、使用标准协议等特征。从实施角度 看,Web服务把资源、计算能力提供给用户,需要以服务的形式完成,Web服务是Web上 可寻址的应用程序接口。如果把Web服务看作类库,那么类库不是位于本地的,而是位 于远程机器上。从设计角度看,Web服务通过WSDL描述,通过SOAP访问,在注册中 心发布,从而使客户可以搜索并定位到该服务。在Web服务体系结构中有三个角色:服 务提供者(ServiceProvider),服务请求者(Service Requester)和服务中介(Service Broker),图3-15显示了它们之间的关系。 图3-15 Web服务体系结构 Web服务提供者创建服务,发布Web服务,并且对服务请求进行响应。Web服务请 求者也就是Web服务功能的消费者,它通过Web服务中介查找到所需要的服务,再向 Web服务提供者发送请求以获得服务。Web服务中介,也称为服务代理,用来注册已经 发布的Web服务,并对其进行分类,提供搜索服务,把一个Web服务请求者和合适的 Web服务提供者进行匹配。 在这个架构中有三种操作:发布、发现和绑定。通过发布操作,可以使Web服务提 供者向Web服务中介注册自己的功能以及访问的接口。发现(查找)使得Web服务请求 者可以通过Web服务中介查找所需的Web服务。绑定就是实现让服务请求者能够使用 服务提供者提供的服务了。 Web服务架构中用到的协议、语言和规范有UDDI、WSDL、SOAP、XML、HTTP等。 第 3 章 面向对象设计 57 UDDI是统一描述、发现和集成(UniversalDescription,Discovery,andIntegration)的缩 写。它是一个基于XML的跨平台的描述规范,可以使世界范围内的企业在互联网上发 布自己所提供的服务。WSDL(WebServicesDescriptionLanguage,Web服务描述语言) 是一个基于XML的关于如何与Web服务通信和使用的服务描述语言。简单对象访问 协议(SOAP)是一种轻量的、简单的、基于XML的协议,可以和现存的许多因特网协议 结合使用,包括超文本传输协议(HTTP )、简单邮件传输协议(SMTP )、多用途网际邮件 扩充协议(MIME)等。 例如,国家气象局可以实时以Web服务的形式公开发布全国各地的天气状况,各种 应用系统、手机APP便可以通过这个Web服务来访问到天气状况。 3.1.5 微服务架构 微服务提倡将单一应用程序划分成更小的服务,每个服务运行在独立的自己的进程 中,服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTfulAPI )。 每个服务都围绕着具体业务进行构建,并且能够被独立地部署,如图3-16所示。 图3-16 微服务架构 在单体式(Monolithic)的架构中,如三层应用,所有的功能打包在一个WAR包里, 部署在一个JavaEE容器(Tomcat、JBos 、WebLogic等)里,包含数据访问、业务处理和 用户界面等所有程序,除了容器基本没有外部依赖。其优点是单点部署,没有分布式 的管理和调用代价。但缺点是扩展性不够,无法满足高并发下的业务变更需求。例 如,数据库模式被多个应用依赖,无法重构和优化。所有应用都在一个数据库上操作, 数据库出现性能瓶颈。开发、测试、部署、维护愈发困难。即使只改动一个小功能,也 需要整个应用一起构建和发布。基于微服务架构的设计目的是有效地拆分应用,实现 敏捷开发和部署。 58 UML 面向对象分析与设计 3.组消息通信模式 2 分布式系统中相互作用的进程集合称为组。一个发送进程在一次操作中将一个消息 发送给组内其他进程的通信,称为组消息通信。组内每个成员都是平等的。进程可以加 入或离开组。 3.2.1 消息队列 在消息队列模型中有两种角色:消息的生产者(Producer)和消费者(Consumer)。消 息生产者生产消息发送到消息队列中,然后消费者从消息队列中取出并且消费消息。消 息从队列中取出被消费以后,队列中不再有存储,所以这个模型中虽然存在多个消费者, 但是对一个消息而言,只会有一个消费者可以消费,如图3-17 所示。 图3-17 消息队列模型 3.2.2 发布/订阅 发布/订阅模型中有三个角色:消息发布者(Publisher)、中介代理(Broker)、消息订 阅者(Subscriber)。发布者发布消息到一个消息的中介代理,对消息感兴趣的订阅者向 该代理注册和订阅,由消息代理进行过滤。消息代理执行存储转发的功能将消息从发布 者发送到订阅者,如图3-18 所示。 图3-18 发布/订阅模式 第 3 章 面向对象设计 59 在发布-订阅系统中,发布者和订阅者之间不需要互操作;消息的发布者异步地发送 到所有对消息感兴趣的订阅者。例如,《读者》杂志社负责出版期刊《读者》,每月发布一 刊。但是读者并不直接从杂志社购买,而是通过中国邮政或者其他渠道订阅,一般订阅一 年,预交费用。中国邮政则是中介角色,负责接收读者订阅,从杂志社逐月批量获取杂志 (消息),然后按照订阅信息派送到读者手中。 JvJv 大多数消息系统同时支持消息队列模型和发布/订阅模型,例如,aa消息服务(aa MesaevcJMS )。JMS是一个Jv geSrie,aa平台中关于面向消息中间件的应用程序接口, 用于两个应用程序之间,或分布式系统中发送消息,进行异步通信。 微信订阅号(hs:wexn.c是一种微信公众号,是为媒体和个人提供 tp//mp.iiqq.om/) 的一种“发布/订阅”信息传播方式,主要功能是在微信侧给用户传达资讯,类似报纸杂志, 提供新闻信息或娱乐趣事。订阅号认证媒体1天内可群发1条消息。媒体就是“发布 者”,而微信订阅号则是“消息中介”,读者通过微信订阅号订阅感兴趣的消息,从而省去了 从互联网搜索引擎查找信息的负担。 3.设计模式 3 模式是某种事物的标准形式或使人可以照着做的标准形式。设计模式(Design Patern)是在软件开发中针对普遍发生的问题而总结的被学术界、企业界和教育界认同 的、反复使用的、经过分类编目的代码设计经验。设计模式是在特定环境下为解决某一通 用软件设计问题提供的一套有效的解决方案,该方案描述了对象和类之间的相互作用。 设计模式的应用提高了代码复用、可理解和可靠的程度。一个设计模式包括以下四个 要素。 (1)名称(PaternName):每一个模式都有自己的名字,起到对该模式的标识作用, 方便进行引用。 (2)问题(Problem):在面向对象的系统设计过程中频繁出现的特定场景,如在期望 系统运行期间希望只有一个实例。设计模式所解决的问题通常是可扩展性等非功能性 需求。 (3)解决方案(Solution):针对问题所设计的类、类之间的关系、职责划分和协作 方式。 (4)效果(Consequence):采用该模式对系统的扩充性、可移植性等方面的影响。 例如,适配器模式的描述如表3-1所示。 表3- 1 适配器模式 名称 问题描述 解决方案 效果 适配器 使用客户期望的接口访问遗留类(LegacyClas),但不期望修改遗留类 客户所期望接口的实现类(Adapter)访问遗留类并执行必要的转换 客户使用遗留类工作而无须修改遗留类; 针对不同的客户期望,可设计不同的适配器 60 UML 面向对象分析与设计 与面向对象相关的设计模式分为三类:创建型模式、结构型模式和行为型模式。创 建型模式着眼于对象的“创建、组合及表示”。结构型模式着眼于有关如何将类和对象组 织和集成起来,以创建更大结构的问题和解决方案。行为型模式解决与对象间任务分配 以及影响对象间通信方式的有关问题。 创建型模式有单例模式(Singleton Pattern)、简单工厂模式(SimpleFactory Pattern)、工厂方法模式(Factory MethodPattern)、抽象工厂模式(AbstractFactory Pattern)、建造者模式(BuilderPattern)、原型模式(PrototypePattern)等。 结构型模式有适配器模式(AdapterPattern)、桥接模式(BridgePattern)、组合模式 (CompositePattern)、装饰模式(DecoratorPattern)、外观模式(FacadePattern)、享元模 式(FlyweightPattern)、代理模式(ProxyPattern)等。 行为型模式有职责链模式(ChainofResponsibilityPattern)、命令模式(Command Pattern)、解释器模式(InterpreterPattern)、迭代器模式(IteratorPattern)、中介者模式 (MediatorPattern)、备忘录模式(MementoPattern)、观察者模式(ObserverPattern)、状 态模式(StatePattern)、策略模式(StrategyPattern)、模板方法模式(TemplateMethod Pattern)、访问者模式(VisitorPattern)等。 3.3.1 单例模式 单例模式指一个类有且仅有一个实例,并且自行实例化向整个系统提供。一种常见 的实现方案如下面的代码所示。 public class MySingleton { //静态私有成员变量 private static final MySingleton instance = new MySingleton(); //私有构造方法 private MySingleton() {} //静态公共方法 public static MySingleton getInstance() { return instance; } } 当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单 例类的唯一实例将被创建。需要该实例的客户通过公共的静态方法getInstance()获取。 在这个方案中,只要类被加载,实例就会被创建。下面的代码是一种“按需实例化”的 设计。 public class MySingleton { /*私有构造方法,防止被实例化*/ private MySingleton() { } /*此处使用一个内部类维护单例*/ private static class SingletonFactory { 第3 章 面向对象设计 61 private static MySingleton instance = new MySingleton(); } /*获取实例*/ public static MySingleton getInstance() { return SingletonFactory.instance; } } 在这种设计中,类加载时不会实例化对象MySingleton。因为第一次调用 getInstance()方法时才加载内部类,初始化在该内部类中定义的静态引用变量instance, 该成员变量只初始化一次。 3.3.2 抽象工厂模式 如果有一组提供相同功能的产品,使用产品的客户只关心产品功能而不关心具体产 品的实现细节,当增加新的产品后不愿意修改客户代码,这种场景下适合采用抽象工厂 模式。例 如,客户程序需要通过不同方式发送消息:电子邮件或者短信,因此需要一个负责 邮件发送的对象(MailSender)和一个负责短信发送(SmsSender)的对象,将来也有可能 又需要负责微信发送的对象。 把负责消息发送的对象称为产品,这些产品对客户的接口是相同的;不同的产品使用 不同的工厂生产,即创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类 就可以了,不需要修改现有的代码。 首先定义产品接口: public interface Sender { void send(); } 类MailSender实现该接口: public class MailSender implements Sender { @Override public void send() { System.out.println("this is a mail sender!"); //此处仅模拟发送行为 } } 类SmsSender也实现该接口: public class SmsSender implements Sender { @Override public void send() { System.out.println("this is a smssender!"); //此处仅模拟发送行为 } 62 UML 面向对象分析与设计 } 然后定义抽象工厂类: public abstract class Provider{ Sender produce(); } 生产MailSender对象的工厂类: public class MailFactory extends Provider { @Override public Sender produce() { return new MailSender(); } } 生产SmsSender对象的工厂类: public class SmsFactory extends Provider { @Override public Sender produce() { return new SmsSender(); } } 客户类首先创建工厂对象,然后让工厂对象生产产品(MailSender),最后通过该产品 实现消息发送(send)。 public class Client { public static void main(String[]args) { Factory factory = new MailFactory(); Sender sender = factory.produce(); sender.send(); } } 将来如果期望增加新的消息发送形式,如微信发送,那么新增一个产品类 WeChatSender实现接口Sender;新增工厂类WeChatFactory继承抽象工厂Factory即 可。对现有代码无须做任何改动。 3.3.3 工厂方法模式 工厂方法模式(FactoryMethodPattern)简称工厂模式(FactoryPattern),又可称作 虚拟构造器模式(VirtualConstructorPattern)、多态工厂模式(PolymorphicFactory Pattern)。工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的 产品对象。目的是将产品类的实例化操作延迟到工厂子类中完成。 第3 章 面向对象设计 63 例如,对于发送消息的客户来说,不管是使用电子邮件,还是使用短信,都使用实例方 法voidsend(Stringmessage),只要工厂子类生产出满足这个接口要求的产品即可。 客户程序如下。 public class Client{ public static void main(String[]args) { Sender sender = SendFactory.produceMail(); sender.send("message"); } } 这里的产品对象是电子邮件,Sender是其抽象类或者接口定义。 public interface Sender { public void Send(String msg); } 电子邮件消息发送器MailSender和短信消息发送器SmsSender是具体的实现类。 public class MailSender implements Sender { @Override public void Send(String msg) { //…; } }p ublic class SmsSender implements Sender { @Override public void Send(String msg) { //…; } } 有了具体的产品,让工厂使用静态方法实例化产品即可。 public class Factory { public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } 可以看到,在工厂方法模式中,关键是客户和工厂事先约定好产品规格,即产品的抽 象类或接口定义。然后客户按照这个定义使用产品;工厂按照这个定义生产产品。工厂 方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一 64 UML 面向对象分析与设计 细节。 3.3.4 原型模式 原型模式(PrototypePattern)首先创建一个对象作为原型实例,然后应用对象克隆 根据这个原型实例得到新的对象。通过克隆方法所创建的对象是全新的对象,它们在内 存中拥有新的地址,每一个克隆对象都是独立的。在需要一个类的大量对象的时候,使用 原型模式是最佳选择,因为原型模式是在内存中对这个对象进行复制,要比直接使用关键 字new创建这个对象性能好很多,在这种情况下,需要的对象越多,原型模式体现出的优 点越明显。 在Java语言中,Object类的clone()方法用于实现浅克隆,该方法使用起来很方便, 直接调用super.clone()方法即可实现克隆。浅克隆(ShallowClone)指当原型对象被复 制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复 制。而深克隆(DeepClone)则除了对象本身被复制外,对象所包含的所有成员变量也将 被复制。 假设孙悟空是类Monkey的对象,现在孙悟空需要克隆100万个跟自己一模一样的 猴子对付妖怪。那么首先需要让类Monkey实现Cloneable接口。 public class Monkey implements Cloneable { public Monkey() { System.out.println("An object of Monkey."); } @Override protected Monkey clone(){ Monkey a = null; try { a = (Monkey) super.clone(); }catch (CloneNotSupportedException e){ e.printStackTrace(); } return a; } } 然后孙悟空就可以变化了。 public class Test{ public static void main(String[]args){ Monkey wuKong = new Monkey(); //创建孙悟空 Monkey[]a = new Monkey[1000000]; int i= 1; while (i < 1000000){ a[i]= wuKong.clone(); //克隆猴子 第3 章 面向对象设计 65 } } } 3.3.5 建造者模式 建造者模式将复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同 的表示。当一个类构造方法的参数多于4个,例如,组装计算机类Computer需要5个参 数:cpu、ram、usbCount、keyboard和display,其中,cpu与ram 是必填参数,而其他3个 是可选参数,那么通常设计有如下构造方法。 public class Computer { //… public Computer(String cpu, String ram) { this(cpu, ram, 2); //默认两个USB 接口 } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "104"); //默认104 键盘 } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "17"); //默认17 英寸显示器 } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } } 这种设计要求程序员首先要决定使用哪一个构造方法,然后理解里面参数的含义,学 习成本较高。 在Computer类中创建一个静态内部类Builder,然后将构造Computer所需的参数 都作为Builder类的成员变量,并在Builder类中设计实例方法build()构建Computer类 的实例并返回。这样对客户程序而言就隐藏了各种零件实例化和组装细节。 客户程序创建内部类Computer.Builder 的实例,然后调用build()方法返回 Computer对象。代码如下。 public class Client{ public static void main(String[]args){ Computer computer= new Computer.Builder("2.0GHz","256G") .setDisplay("24 英寸")