<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Architecture</title>
	<atom:link href="https://blog.gmem.cc/category/work/architecture/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 06 Apr 2026 12:46:48 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>2015年7月架构师培训笔记</title>
		<link>https://blog.gmem.cc/201507-architect-training-note</link>
		<comments>https://blog.gmem.cc/201507-architect-training-note#comments</comments>
		<pubDate>Sun, 26 Jul 2015 12:54:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7210</guid>
		<description><![CDATA[<p>本文对我最近参加的架构师培训内容进行整理记录。 同事提问以及老师的初步解决方案  问题 分析与初步解决方案  技术选型困难，新框架适用性、使用难点，对开源技术不了解 今天在整个行业中，一个明显的趋势是：正在从厂家的产品转向开源的应用。开源框架百花齐放、缺少完善的培训体系、厂商服务。在这种趋势下，对开发人员的技术能力要求提高，特别是对开源框架的把握能力。 开源软件的架构具有相似性，基本上来说，这些架构只有20多种，如果掌握这些架构，那么理解开源框架将变得容易 是使用开源技术还是自己开发 如果有能力的话，建议自己开发，这样有100%的可控性。理解了开源框架的架构以后，自己开发难度并不是想象的那么大 如何解耦 在模块间通信时，使用（语言无关）消息代替（函数）调用。跨模块的通信应该是比较少的，如果特别多，说明模块划分有问题 架构细致到什么粒度 对于OO语言来说，类设计不属于架构设计，而是属于详细设计，架构设计应该精确到组件级。老师认为组件往往落在不同jar、dll、so等文件中，不过我觉得这样可能导致过多的jar，加大工程管理的难度，可能是行业、参与软件的规模差异较大吧 软件越做越难维护，如何重构 代码级重构可以经常做，架构级重构应当谨慎，往往一两年一次，在大版本升级时考虑，重构架构要求你有很强的架构设计能力。重构不应该是被逼无奈，那样会很被动 模块必须做到自包含（其它模块不要狗逮老鼠，而应该调用），比如订单管理模块只做订单的管理，而不应该包含生成凭证的代码，特别是不该复制凭证模块的代码。起码要做到基于接口调用，最好是基于消息机制解耦 重构的内容：服务架构重构、消息架构重构（两者有交叉，区别老师没有讲解，我觉得两者没有本质的区别，如果服务指的是WebService、消息基于基于文本的消息，那么只是形式的不同，可能消息架构更倾向于异步）、部署架构重构（改小机为PC）、数据架构重构（水平分区、读写分离） 重构时如何保证数据一致性： 不是所有数据需要绝对的一致性，这样的数据可以使用文本（文档）数据库，例如MongoDB，性能至少提高40倍 读写分离（基于RDBMS二进制日志），可能1-2秒的延迟，如果这样都不允许，那么可以使用例如Oracle的RAC 由于重构不产生直接的业务价值，因此往往没必要向客户解释。 海量数据分析 <a class="read-more" href="https://blog.gmem.cc/201507-architect-training-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/201507-architect-training-note">2015年7月架构师培训笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>本文对我最近参加的架构师培训内容进行整理记录。</p>
<div class="blog_h2"><span class="graybg">同事提问以及老师的初步解决方案</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;"> 问题</td>
<td style="text-align: center;">分析与初步解决方案 </td>
</tr>
</thead>
<tbody>
<tr>
<td>技术选型困难，新框架适用性、使用难点，对开源技术不了解</td>
<td>
<p>今天在整个行业中，一个明显的趋势是：正在从<span style="background-color: #c0c0c0;">厂家的产品转向开源的应用</span>。开源框架百花齐放、缺少完善的培训体系、厂商服务。在这种趋势下，对开发人员的技术能力要求提高，特别是对开源框架的把握能力。</p>
<p>开源软件的架构具有相似性，基本上来说，这些架构只有20多种，如果掌握这些架构，那么理解开源框架将变得容易</p>
</td>
</tr>
<tr>
<td>是使用开源技术还是自己开发</td>
<td>
<p>如果有能力的话，建议自己开发，这样有100%的可控性。理解了开源框架的架构以后，自己开发难度并不是想象的那么大</p>
</td>
</tr>
<tr>
<td>如何解耦</td>
<td>在模块间通信时，使用<span style="text-decoration: underline; color: #c0c0c0;">（语言无关）</span>消息代替<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（函数）</span></span>调用。<span style="background-color: #c0c0c0;">跨模块的通信应该是比较少的</span>，如果特别多，说明模块划分有问题</td>
</tr>
<tr>
<td>架构细致到什么粒度</td>
<td>对于OO语言来说，类设计不属于架构设计，而是属于详细设计，<span style="background-color: #c0c0c0;">架构设计应该精确到组件级</span>。老师认为组件往往落在不同jar、dll、so等文件中，<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">不过我觉得这样可能导致过多的jar，加大工程管理的难度，可能是行业、参与软件的规模差异较大吧</span></span></td>
</tr>
<tr>
<td>软件越做越难维护，如何重构</td>
<td>
<p>代码级重构可以经常做，架构级重构应当谨慎，往往一两年一次，在大版本升级时考虑，重构架构要求你有很强的架构设计能力。重构不应该是被逼无奈，那样会很<span style="background-color: #c0c0c0;">被动</span></p>
<p><span style="background-color: #c0c0c0;">模块必须做到<span style="color: #ffffff;"><span style="background-color: #ff0000;">自包含</span></span>（其它模块不要狗逮老鼠，而应该调用）</span>，比如订单管理模块只做订单的管理，而不应该包含生成凭证的代码，特别是不该复制凭证模块的代码。起码要做到基于接口调用，最好是基于消息机制解耦</p>
<p>重构的内容：服务架构重构、消息架构重构<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（两者有交叉，区别老师没有讲解，我觉得两者没有本质的区别，如果服务指的是WebService、消息基于基于文本的消息，那么只是形式的不同，可能消息架构更倾向于异步）</span></span>、部署架构重构（改小机为PC）、数据架构重构（水平分区、读写分离）</p>
<p>重构时如何保证数据一致性：</p>
<ol>
<li><span style="background-color: #c0c0c0;">不是所有数据需要绝对的一致性</span>，这样的数据可以使用文本<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（文档）</span></span>数据库，例如MongoDB，性能至少提高40倍</li>
<li>读写分离（基于RDBMS二进制日志），可能1-2秒的延迟，如果这样都不允许，那么可以使用例如Oracle的RAC</li>
</ol>
<p>由于重构不产生直接的业务价值，因此往往没必要向客户解释。</p>
</td>
</tr>
<tr>
<td>海量数据分析</td>
<td>
<p>PC集群+Hadoop MapReduce</p>
</td>
</tr>
<tr>
<td>算法密集型系统的架构</td>
<td>
<p>面临的问题：计算密集型，单机（32G的基本工作站）部署，对单机的要求越来越高，硬件没法满足；算法实现难，团队难以扩大</p>
<p>能否对计算任务进行分解，进行分布式计算</p>
<p>对于安全和知识产权保护的问题，可以考虑SOA架构，对外出售服务而不是产品，可以搭建私有云</p>
<p>对于团队分工，建议将算法相关的部分独立出来由专家开发，应用部分由其他普通的、容易招聘的人员开发</p>
</td>
</tr>
<tr>
<td>平台的建设、产品线</td>
<td>
<p><span style="background-color: #c0c0c0;">软件的团队应当以产品来划分</span>。传统的以组织型方式（开发部、测试部……）的方式划分团队，灵活性与沟通成本较高</p>
</td>
</tr>
<tr>
<td>对架构缺乏系统的了解</td>
<td>
<p>架构应该分为两个部分：</p>
<ol>
<li>静态架构设计：业务模块划分、接口设计、分层设计等，主要完成功能需求</li>
<li>动态架构设计：更多的是指框架设计，主要完成非功能需求<span style="text-decoration: underline; color: #c0c0c0;">（这个命名我不太理解）</span></li>
</ol>
</td>
</tr>
<tr>
<td>可维护性差怎么办</td>
<td>
<p>牵一发而动全身，代码耦合性高，可能需要架构级重构，引入组件化开发+面向消息的架构</p>
</td>
</tr>
<tr>
<td>手机Apps新旧版本的兼容性和维护</td>
<td>
<p>新旧版本APP的业务逻辑不一样。这个问题没有特别好的解决方法，可以考虑引入对旧版本的Adapter，主要还是工作量的问题</p>
</td>
</tr>
<tr>
<td>新旧技术的更替</td>
<td>
<p>面临的问题：技术更替较快，使用老技术的模块测试、运行良好，但是又不停引入新技术，导致人员同时需要懂得新旧技术，人力成本高</p>
<p>使用老技术模块的如果较为稳定、很少修改，可以继续沿用老技术</p>
</td>
</tr>
<tr>
<td>架构可扩展性、前瞻性的问题</td>
<td>
<p>对于可扩展性，标准的解决方案是组件化开发</p>
</td>
</tr>
<tr>
<td>高并发与海量数据的可落地的最佳实践</td>
<td>
<p>高并发：我们主要使用内存体系来增加并发能力。<span style="background-color: #c0c0c0;">高并发分为并发接入、并发处理两个部分</span>，单台PC服务器同时接入几万个是没有问题的，核心问题是并发处理的能力，一般通过<span style="background-color: #c0c0c0;">内存体系</span>来提高吞吐能力，例如基于Redis的缓存</p>
<p>海量数据：今天比较常见的，非常复杂的需求，通常采用Linux集群，通常会同时存在分布式计算的问题，今天Hadoop是常用的框架技术。但不是所有海量数据都使用NoSQL，RDBMS也是有的</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">关于提问的总结</span></div>
<p>通过提问，老师总结同事们广泛关注的焦点内容包括以下几个方面：</p>
<div class="blog_h3"><span class="graybg">如何系统化的完成架构设计的过程</span></div>
<p>设计过程基本上分为两个步骤：</p>
<ol>
<li>静态架构设计：模块划分、模块结构的分析、组件结构的分析、并发的设计、部署的设计（主要针对分布式系统）、分层设计、接口设计（WebService、RPC、数据共享）、通讯设计（主要是协议的选型，当然也牵涉到前面的技术选型）。这基本上是很多架构师当前正在做的事，但是可能没有做的这么细。<span style="text-decoration: underline; color: #c0c0c0;">类似于温老师的架构五视图，个人觉得概念不够精炼，但是可能比较实用吧</span></li>
<li>动态架构设计：这个主要是牵涉到系统的底层框架，主要满足非功能需求，包括健壮性、高性能、可扩展性，这里会介绍接近20种架构框架<span style="text-decoration: underline; color: #c0c0c0;">（架构模式）</span>，一般都有对应的开源框架，这一部分老师认为是最重要的、也是缺少具体书籍的，主要靠经验积累</li>
</ol>
<div class="blog_h3"><span class="graybg">系统解耦问题</span></div>
<p>上述20种框架，其中2-3种与解耦有关，<span style="background-color: #c0c0c0;"><span style="color: #ffffff;"><span style="background-color: #ff0000;">解耦</span></span>对某些系统特别重要</span>，一旦代码量大了，可扩展性、可维护性问题会变得很严重，因此老师不管在考虑什么架构问题时，往往会首先想到解耦的问题。解耦问题解决了，很多东西会变得简单。</p>
<div class="blog_h3"><span class="graybg">性能问题</span></div>
<p>性能往往和数据有关，需要考虑如何处理数据让性能提高，这里包含5-6种框架，这些框架是比较重要的</p>
<div class="blog_h3"><span class="graybg">重构问题</span></div>
<p>其实很简单，如果掌握了静态、动态架构设计，你很容易看到系统中存在哪些缺陷。</p>
<p>重构的流程：<span style="background-color: #c0c0c0;">看原有架构的问题 ➪ 改进的设计 ➪ 新的架构方案 ➪ 风险评估 ➪ 执行重构  ➪ 功能非功能测试  ➪ 系统试运行</span></p>
<p>为什么觉得重构困难，主要是架构级重构需要对系统具有全面的把握能力。</p>
<p>重构不一定是架构级的，也可能是模块级、代码级的。</p>
<div class="blog_h2"><span class="graybg">培训课程的构成</span></div>
<ol>
<li>软件架构的体系结构：什么是架构</li>
<li>架构的静态设计</li>
<li>架构的动态设计：框架</li>
<li>架构的重构问题</li>
<li>架构的管理</li>
</ol>
<div class="blog_h2"><span class="graybg">软件架构的体系结构</span></div>
<p>架构是什么？很多时候是系统的结构的定义，很多时候我们都认为架构是功能性的，例如功能模块的划分、如何将其落到层里。往往<span style="background-color: #c0c0c0;">架构的<span style="color: #ffffff;"><span style="background-color: #ff0000;">非功能</span></span>方面</span>被忽略，这恰恰才是架构<span style="background-color: #c0c0c0;">最重要</span>的内容。例如在架构里面引入SSH，其实和功能是没有任何关系的，SSH解决的都是非功能问题。例如可扩展性、解耦。在比如房屋的建设，不是首先考虑那块作为卫生间、那边作为卧室，首先需要考虑的是房屋的框架结构，这些结构对房屋的功能无益、没有业务架构，甚至是有妨害的，例如立在房间中间的柱子。软件设计也类似，<span style="background-color: #c0c0c0;">首先需要满足非功能需求</span>，在满足非功能需求之后，才会满足功能需求。</p>
<p>架构的过程，是系统结构的定义，这个定义分为两个部分：</p>
<ol>
<li>关注点隔离：高层分割，按住<span style="background-color: #c0c0c0;">业务特性分成不同</span>的部分。这是依据人类自然而然的分类思维来进行的。这样的关注点分离可以简化问题（床与房间的例子）。<span style="background-color: #c0c0c0;">这种分割应该合理、易于别人理解</span>，这种分割很多时候按照功能性进行的，<span style="background-color: #c0c0c0;">其依据是需求</span>，首先将系统划分为不同的<span style="background-color: #c0c0c0;">关注点，或者叫业务模块</span></li>
<li>当把系统分隔为不同部分后，往往会发现一些<span style="background-color: #c0c0c0;">通用的部分</span>，这部分往往对应了非功能需求。不同的非功能需求，例如可扩展性和性能，本身就存在冲突，这里就牵涉到权衡</li>
</ol>
<p>架构定义的引入，有几种定义方式：</p>
<ol>
<li>架构组件划分，以及组件之间的关系、交互，这是当前很多团队的认识</li>
<li>架构是决策集，以前对决策集的描述都是文字性的，一个严重的问题是<span style="background-color: #c0c0c0;">这些决策很难被下端业务模块开发人员全部遵循</span>，因此这些决策现在都转换为Framework<span style="text-decoration: underline; color: #c0c0c0;">（例如使用Spring，其IoC、MVC注解体系就限制了开发人员必须遵循架构决策）</span>，通过Framework将非功能需求实现出来，结合上层的业务组件，形成架构的组成（Framework+上层组件）。Framework包括其核心设计思想、类图、代码，在大规模开发之前，应该将底层Framework的代码全部做好，然后开发其上层组件。使用Framework后，就形成了业务组件、Framework的分层，两者具有调用-被调用的关系，Framework的代码很少，可能小于5%，但是却大大的降低了开发难度</li>
</ol>
<p>软件架构要做的第一件事：分解，将复杂的业务拆分到不同的模块，这也是静态架构设计的工作。拆分存在一个粒度的问题，要细致到什么粒度？</p>
<p>随着软件规模的不断扩大，渐渐引入新的封装技术：代码行 ➪ 面向过程（函数）➪ 面向对象（类）➪ 模块（很多类组成，一种虚拟的划分方式，例如Java的Package方式、C++的namespace方式）➪ 组件（在模块基础上提出，具有物理边界，组件是模块的体现形式，比模块细，可以表现为jar包、DLL、Linux的so）➪ 子系统。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">个人觉得模块、组件的关系比较含糊 ，有时是包含关系，有时是等同关系。</span></span></p>
<p><span style="background-color: #c0c0c0;"><span style="color: #ffffff;"><span style="background-color: #ff0000;">架构的粒度</span></span>一般精确到子系统、组件级别</span>。</p>
<p>除了组件划分以外，还有去分析通用的、解决非功能需求的框架。这些框架往往在系统间复用。一旦定义了框架，上面上百个业务组件都要使用该框架的API，因而牢牢的绑定这些业务组件的实现方式、落地架构决策。使用框架时，绝大部分复杂性封装在框架中，因此普通开发人员的工作难度大大降低、且代码相似度很高。</p>
<p>软件系统的架构对软件开发有什么好处：</p>
<ol>
<li>在系统开发之初，就对系统的结构就有了非常<span style="background-color: #c0c0c0;">清晰的定义</span>——能够从需求实现到系统的静态结构映射。可以映射到什么程度——功能划分到什么组件中。架构上接目标、下接技术决策</li>
<li>有了架构后，可以<span style="background-color: #c0c0c0;">控制系统的复杂度</span>。首先，底层框架是复杂度最高的部分，但是代码量仅占5%，但是解决了95%的复杂性（解耦的例子）；同时，架构划分了组件，不同功能划分到不同组件中，那么不同模块的复杂性都在各自的组件中（不影响组件间交互）</li>
<li>架构可以<span style="background-color: #c0c0c0;">组织开发，有利于项目管理</span>。架构划分了组件、接口，可以分配给不同的开发人员完成</li>
<li><span style="background-color: #c0c0c0;">可扩展性好</span>的架构<span style="background-color: #c0c0c0;">有利于迭代式开发</span>，迭代是敏捷开发的重要思想。这当然要求功能解耦、组件化开发，敏捷开发对软件架构的要求很高</li>
<li>架构<span style="background-color: #c0c0c0;">有利于基于产品线的开发</span>：<span style="background-color: #c0c0c0;">产品线开发的一个重要思想是平台</span>。所谓<span style="color: #ffffff;"><span style="background-color: #ff0000;">平台是Framework+广义功能</span></span><span style="color: #c0c0c0;">（即通用功能，我以前的叫法是基础服务）</span>。产品线基于平台构建，产品会有一些自定义的功能实现。老师的产品线建设方式是：
<ol>
<li>平台有独立的团队，平台的大版本自己演变。通过<span style="background-color: #c0c0c0;">可配置来实现具体产品的功能差异化</span></li>
<li>需要开发一个产品时，从平台中拿出一个稳定的版本，比如v3.0，在此基础上定制开发</li>
<li>定制开发的内容形成独立的工程，只在此工程中进行开发，不修改上述迁入的v3.0的代码。此工程的编译结果插入到平台中，形成产品</li>
</ol>
</li>
</ol>
<p><span style="background-color: #c0c0c0;">平台一般都要组件化开发，基本步骤：首先需要提取通用非功能需求，依此定义框架；然后需要提取所有产品的通用功能需求</span>。</p>
<p>架构师到底要做什么：</p>
<ol>
<li>整体对系统负责：系统的任何部分出现问题，架构师都可以清晰的指出，问题可能出现在什么地方。这个很重要，对于千万行级代码的系统，对于一个普通开发人员，他往往根本不知道自己所做的工作对系统有什么影响（Swimming in codes），这时需要一个全局上掌控的人</li>
<li>在设计架构时，要满足客户需求，包括功能、非功能需求</li>
<li>能够在整个研发过程，能够控制架构，对整个团队提供架构上的指导，协助开发人员的分工协同</li>
</ol>
<p>软件开发的流程：需求分析 ➪ 系统架构设计 ➪ 得到<span style="background-color: #c0c0c0;">系统整体的结构定义</span> ➪  依据该定义进行分工 ➪  由不同开发人员完成每个组件的详细设计和编码实现 ➪ 测试。因此在编码之前，架构为系统提供了整体定义，编码人员可以知道组件之间的关系。非功能需求基本都是在架构阶段去解决的。</p>
<p>架构设计的最终达到的目标：通过<span style="background-color: #c0c0c0;">底层框架</span>与<span style="background-color: #c0c0c0;">上端业务模块</span>的<span style="background-color: #c0c0c0;">分割配合</span>，最终实现利用底层框架来<span style="background-color: #c0c0c0;">支撑</span>整个业务组件的开发。框架性的代码API<span style="background-color: #c0c0c0;">强制上层代码</span>依照架构要求进行实现，<span style="background-color: #c0c0c0;">不仅仅是文字性的指导原则</span>。</p>
<p>好的架构，有一个重要的特点，去除一个组件，不会影响其它组件的正常运行。这样的架构例如Eclipse平台。</p>
<p>这里举了ERP的例子，原料管理与凭证管理。传统的基于interface的解耦有作用，但是是很局限的，主要是接口规格很难稳定。如果使用<span style="background-color: #c0c0c0;">组件化开发+消息架构</span>，则可以很好的解耦。在原料创建完毕后，需要创建凭证，这时它传递一个简单的消息（可能仅仅包括原料的ID），这个消息被凭证管理接收，凭证管理通过集中<span style="background-color: #c0c0c0;">数据资源管理（内存体系）</span>获得原料信息，生成凭证后再次通过消息通知原料管理。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">我觉得这种实现方式其实是观察者的变种，另外即使这样，也不可能完全解耦，在这个例子中，接口规格变化，可能意味着底层数据格式的变化。即使使用消息，原料管理模块也往往必须要修改</span></span>。当然消息架构适合于模块间/组件间通信，对于模块内则不提倡，这是<span style="background-color: #c0c0c0;">高内聚（模块内）、低耦合（模块间）</span>原则，所谓高内聚低耦合实质上说的是一件事情，就是对象之间<span style="background-color: #c0c0c0;">依赖关系的强度</span>。函数调用是属于强依赖，调用次数越多，则内聚越高。模块内的类之间本身从业务上就是互相之间强依赖的，因此适合高内聚；模块间的类则从业务上说，关联相对较少，模块A的修改应该尽可能避免牵涉到模块B。因此适合低耦合。</p>
<p><span style="background-color: #c0c0c0;">观察者模式是一种间接调用的思想</span>。老师比较反对通过MOM传递数据（消息），认为MOM传递消息的成本较大，建议只传递对象的标识符。</p>
<div class="blog_h2"><span class="graybg">业务架构简介</span></div>
<p>业务架构的实现过程。<span style="background-color: #c0c0c0;">业务架构来源于需求</span>，很多团队做需求存在问题：需求没有层次，是一块大饼。其实需求分为几个层面（老师称为CFSU）：</p>
<ol>
<li>Concept：高层概念。定义了大方向，系统干什么？不干什么。简单的说，<span style="background-color: #c0c0c0;">高层概念往往对应了一级菜单</span></li>
<li>Features：特性，功能。具体的<span style="background-color: #c0c0c0;">一个个业务功能</span>，往往对应二、三甚至四级菜单</li>
<li>Scenario：每个特性下的场景，每个功能至少有一个业务场景</li>
<li>Use cases：每个场景下的用例，依据业务场景分析出的<span style="background-color: #c0c0c0;">软件操作步骤、界面、业务逻辑、数据</span></li>
</ol>
<p>这四个层面是自上而下、逐层分解。我们通过这种类似于剥洋葱的方式来一层层的分析需求，思路会很清晰，可以避免一片混乱的、跳来跳去的大饼式分析导致功能点遗失。</p>
<p>业务架构往往只需要涉及到1、2层，下面两层属于细节的业务需求。今天国内很多架构团队能做到的，也就仅仅是业务架构这一部分，静态架构、动态架构没有涉及。业务架构本质上应该属于系统分析（Systems Analysis）。</p>
<p>业务架构阶段，首先需要把需求分析清楚，然后使用UML将其建模出来，形成业务架构文档。业务架构需要做以下几个分析：</p>
<ol>
<li>系统间的数据关系分析，这个我们称之为上下文（Context），使用UML包图表达</li>
<li>业务模块的调用关系分析，这个我们称为业务组织图（Business Organization Map，BOM），使用UML包图表达</li>
<li>功能的流程、前后关系分析，这个我们称为业务路线图（Business Road Map，BRM）使用UML活动图表达</li>
<li>此外，还应该进行领域模型分析（DMA），可以使用类图表达，描述核心实体类的关系</li>
</ol>
<p>出这三类图之前，需要把Concept、Feature都列出来。那么如何能不遗漏的把Concept、Feature都列出来？</p>
<ol>
<li>首先，我们要做<span style="background-color: #c0c0c0;">项目的背景分析</span>，分为两个部分：<span style="background-color: #c0c0c0;">业务域分析、价值链分析</span>，这个做完后应该<span style="background-color: #c0c0c0;">得到系统的所有Concept</span></li>
<li>有了Concept之后，就可以<span style="background-color: #c0c0c0;">基于Concept进行Context、BOM分析</span></li>
<li>之后，需要分析每个Context下的Feature</li>
<li>Feature分析完后，可以进行BRM分析</li>
</ol>
<div class="blog_h2"><span class="graybg">案例：第三方支付平台</span></div>
<p>甲方期望建设一个第三方支付平台（xpay），该平台共互联网上的商户、消费者使用，此外甲方还期望基于此支付平台建设一个电子商务平台（xeb）。</p>
<div class="blog_h3"><span class="graybg">需求访谈</span></div>
<p>需求访谈的一些技巧：<span style="text-decoration: underline; color: #c0c0c0;">（我自己总结的）</span></p>
<ol>
<li>尽量使用客户的行业语言，或者简单的语言。不要出现太多技术性名词</li>
<li>尊敬客户，不要无意识的让客户承认其自己的错误，让客户难堪。记住客户是付你钱的，是上帝</li>
<li>为客户着想，让客户看到系统能给他带来的价值、利益</li>
<li>复述、需求确认，特别是使用易懂的流程图复述</li>
<li>引导，对于客户不合理的需求，应当适当引导、变通，而不是生硬的拒绝</li>
<li>整理记录，如果允许，最好录音</li>
</ol>
<p>通过需求访谈来了解项目背景，需求获取是比较依赖于行业经验的工作，最好由行业专家主导<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（讲课过程中需求访谈是交叉在业务分析过程中完成的，这里做了整理和部分修改）</span></span>：</p>
<p><strong>客户：</strong>我们需要建设的平台类似于支付宝，但是主要是面对商业客户（2B），主要业务包括<span style="background-color: #c0c0c0;">充值、支付、退款、提现、转账、调账</span>等。<span style="text-decoration: underline; background-color: #ffffff; color: #c0c0c0;">（了解到了高层Cencept，要做哪些最核心的事情）</span>此外我们还想基于次<span style="background-color: #c0c0c0;">支付平台</span>建设一个电商平台，<span style="background-color: #c0c0c0;">电商平台</span>可以直接使用支付平台的用户/账户<span style="text-decoration: underline;"><span style="background-color: #ffffff; color: #c0c0c0; text-decoration: underline;">（了解到了Context，后面还会继续了解）</span></span></p>
<p><strong>需求：</strong>即两个平台使用一套账户体系，类似于天猫可以直接使用支付宝？</p>
<p><strong>客户：</strong>是的，一方面电商平台可以方便的使用支付功能，另外<span style="background-color: #c0c0c0;">支付平台可以对第三方系统提供服务</span>。另外我们希望电商平台<span style="background-color: #c0c0c0;">不需要管理用户</span>，<span style="background-color: #c0c0c0;">直接通过支付平台</span>的用户名密码就可以登陆</p>
<p><strong>需求：</strong>这可以通过单点登录、或者支付平台OAuth解决，电商平台根据OAuth验证结果，可以在数据库中自动生成电商用户</p>
<p><strong>客户：</strong>我们不希望电商平台管理用户或者账户</p>
<p><strong>需求：</strong>一个用户可以有多个账户么？<span style="text-decoration: underline; color: #c0c0c0;">（关键领域对象的关系）（口吻：不要出现什么one-to-many之类的名词）</span></p>
<p><strong>客户：</strong>目前是有一个就可以了，我们不是银行，不需要管理一个人的多个账户 </p>
<p><strong>需求</strong>：充值的流程是怎么样的？</p>
<p><strong>客户：</strong>用户将银行卡里面的钱充到支付平台的虚拟帐户中<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（Context）</span></span></p>
<p><strong>需求</strong>：这需要银行的配合，需要使用<span style="background-color: #c0c0c0;">银行支付网关</span>，银行会收取费用</p>
<p><strong>客户：</strong>支付网关的商务问题已经谈好</p>
<p><strong>需求：</strong><span style="background-color: #c0c0c0;">支付业务</span>在什么场景下使用？</p>
<p><strong>客户：</strong>第三方<span style="background-color: #c0c0c0;">商户平台</span>向支付平台发起订单，用户可以通过我们支付平台支付订单，把钱划给商户<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（Context）</span></span></p>
<p><strong>需求：</strong>这个业务与充值类似，只是银行的角色改为支付平台，我们可以在收到商户平台的订单后，引导用户去支付。这个支付的钱是来自之前充值的钱么？</p>
<p><strong>客户：</strong>可以是<span style="background-color: #c0c0c0;">虚拟帐户</span>中的钱，也可以<span style="background-color: #c0c0c0;">直接选银行</span>支付</p>
<p><strong>需求：</strong>是否需要类似于支付宝的快捷支付功能 <span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（引导，行业专家的经验）</span></span></p>
<p><strong>客户：</strong>这个功能很好，我们需要</p>
<p><strong>需求：</strong>商户也使用支付平台的虚拟帐户？</p>
<p><strong>客户：</strong>是的，<span style="background-color: #c0c0c0;">任何接入的商户都要在我们的支付平台有一个虚拟帐户</span>，用于接受款项</p>
<p><strong>需求：</strong>好的，这样可以做一个类似于支付宝的收银台。退款业务又是怎么样的呢？</p>
<p><strong>客户：</strong>消费者在电商平台购买的物品不满意，需要退货退款。电商平台同意后，我们就退款，把商户的钱划回去。用户支付的时候钱怎么来的，还<span style="background-color: #c0c0c0;">原路返回</span></p>
<p><strong>需求：</strong>如果商户的钱已经被提走了怎么办？支付平台是否需要<span style="background-color: #c0c0c0;">审批</span>退款？</p>
<p><strong>客户：</strong>商户需要缴纳保证金，如果他的虚拟账户钱不够，就用保证金支付。审批退款是电商平台的事情，只要他们同意我们就退款</p>
<p><strong>需求：</strong>保证金支付是否需要在平台中自动完成</p>
<p><strong>客户：</strong>暂时不需要，人工处理吧</p>
<p><strong>需求：</strong>提现就是将虚拟帐户中的钱提走，<span style="background-color: #c0c0c0;">转移到银行卡</span>中？提现是否需要审核？</p>
<p><strong>客户：</strong>提现都<span style="background-color: #c0c0c0;">人工审核</span></p>
<p><strong>需求：</strong>提现到哪个银行卡，需要<span style="background-color: #c0c0c0;">预先登记</span>吧？</p>
<p><strong>客户：</strong>是的，用户必须首先登记真实有效、实名验证的<span style="background-color: #c0c0c0;">提现银行卡</span></p>
<p><strong>需求：</strong>用户可以分为商户、消费者两类。消费者可以充值，用来消费商户的商品 ；消费者如果退货，则把商户虚拟账户中的钱原路返回；商户可以对销售收入进行提现。是这样吧？<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（访谈中对有疑问的地方及时复述，得到用户确认）</span></span></p>
<p><strong>客户：</strong>是的。消费者也可以提现</p>
<p><strong>需求：</strong>我们的支付平台需要和各大银行进行现金交易，因此需要在这些所有银行开户吧？</p>
<p><strong>客户：</strong>是的，根据国内法规，支付平台在银行的账户是受到人行监管的特殊账户</p>
<p><strong>需求：</strong>商户、消费者在支付平台中的虚拟账户中的虚拟余额，实际是存放在支付平台的银行账户中，当出现提现、充值、退款等业务时，会发生用户<span style="background-color: #c0c0c0;">银行账户</span>与支付平台银行账户之间的<span style="background-color: #c0c0c0;">资金转移</span>？<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（潜在的银行结算模块）</span></span></p>
<p><strong>客户：</strong>没错</p>
<p><strong>需求：</strong>转账业务什么商户做？</p>
<p><strong>客户：</strong>用户可以输入转账金额、对方的XPay账号，就可以转账</p>
<p><strong>需求：</strong>调账是什么样的功能？</p>
<p><strong>客户：</strong>调账很简单，就是增加或者减少虚拟账户的余额</p>
<p><strong>需求：</strong>不需要牵涉到银行那边的处理</p>
<p><strong>客户：</strong>不需要，牵涉到的其它事务人工处理</p>
<p><strong>需求：</strong>支付平台还有什么重要的业务吗？<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（不要丢失业务域）</span></span></p>
<p><strong>客户：</strong> 还有风控、费率。所谓费率，是指：一，我们向商家收费；二，银行向我们收费。所谓风控：可以用于指定超过一个什么额度后，就需要记录下来</p>
<p><strong>需求：</strong>向所有商户收费的费率一致么？所有银行的收费费率都一样么？</p>
<p><strong>客户：</strong>都不一样</p>
<p><strong>需求：</strong>好的，我们初步了解了系统的功能，回去后我们整理一个功能表出来，再和您确认</p>
<p>第一次需求访谈到此结束，需求人员回去后进行初步整理和分析（具体参考下面的业务域分析），并得到功能特性清单，与客户进行二次确认。</p>
<p><strong>需求：</strong>昨天回去我们把需求整理一下，这是功能表，您看一下：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 模块<span style="text-decoration: underline; color: #c0c0c0;">（Concept）</span></td>
<td style="text-align: center;">功能点<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（Features）</span></span></td>
</tr>
</thead>
<tbody>
<tr>
<td>用户管理</td>
<td>用户注册、用户修改、用户查询、创建用户、开通用户、关闭用户</td>
</tr>
<tr>
<td>账户管理 </td>
<td>账户添加、账户查询、银行卡管理、快捷支付设置、提现设置、账户开通、账户关闭、调账申请、调账审批</td>
</tr>
<tr>
<td>交易管理</td>
<td>充值、银行卡支付、账户余额支付、组合支付、退款、提现申请、提现审核、订单管理</td>
</tr>
<tr>
<td>银行结算</td>
<td>支撑性的与银行之间的后台结算功能</td>
</tr>
<tr>
<td>风控管理</td>
<td>风控规则设置、风险交易查询</td>
</tr>
<tr>
<td>费率管理</td>
<td>创建商户费率方案、设置商户费率方案、设置银行费率</td>
</tr>
</tbody>
</table>
<p><strong>客户：</strong>我们希望充值、支付、提现这些功能作为单独的菜单。另外调账是直接在账户查询中的链接上做的</p>
<p><strong>需求：</strong>没关系，后续我们做界面设计时再详细讨论<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（UI组织与业务模块划分无关，甚至很多开发人员都没有认清这一点）</span></span></p>
<div class="blog_h2"><span class="graybg">业务架构过程</span></div>
<p>上面提到，业务架构的第一步是通过业务域分析、价值链分析，来得到系统的所有Concept，尽量避免遗漏。在案例中，老师主要使用了业务域分析技术。</p>
<div class="blog_h3"><span class="graybg">业务域分析</span></div>
<p>什么叫<span style="background-color: #c0c0c0;">业务域，就是业务中要干哪些事情</span>。比如对于ERP，我们要做生产管理、订单管理、业务管理……</p>
<p>我们现在做xpay的业务域分析，xpay要干充值、支付、提现等等事情（这里要注意Concept的粒度，不要深入细节）。业务域的分析很重要，任何一个<span style="background-color: #c0c0c0;">业务域的丢失</span>对系统来说都是很大的<span style="background-color: #c0c0c0;">风险</span>。</p>
<p><strong>用户管理分析：</strong>用户可以自助注册，支付平台的管理人员也可以在后台进行增删改<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（MIS系统需求分析时要自然而然想到CRUD、启禁）</span></span>。商户的信息由平台管理人员手工录入，由于商户的信息和用户没有本质的区别，因此引入一个用户录入的功能</p>
<p><strong>账户管理分析：</strong>账户对象是随着用户注册自动添加的。与账户相关的功能点还包括提现银行卡管理、快捷银行卡设置。此外处于风险控制的目的，账户还可以被关闭，账户关闭的时候用户仍然可以登录</p>
<p><strong>充值业务分析：</strong>一般的流程：用户在xpay中输入充值金额、选择银行后，<span style="background-color: #c0c0c0;">跳转</span>到银行支付网关页面，与此同时支付平台在后端将银行发起<span style="background-color: #c0c0c0;">消费请求（订单）</span>，这个订单信息会在用户浏览器上呈现。用户在银行支付网关输入卡号密码等信息并<span style="background-color: #c0c0c0;">支付成功后</span>，银行会将<span style="background-color: #c0c0c0;">消费请求的处理结果</span>发回给支付平台，同时浏览器跳转会到支付平台。</p>
<p><strong>支付业务分析：</strong>和充值业务类似，流程：用户在商户网站上购买商品后，商户调用<span style="background-color: #c0c0c0;">xpay提供的接口</span>，将订单号、商品信息、金额等信息传递给xpay，同时商户<span style="background-color: #c0c0c0;">跳转</span>浏览器到xpay的收银台页面，用户可以直接通过<span style="background-color: #c0c0c0;">帐户余额</span>支付，亦可选择<span style="background-color: #c0c0c0;">银行支付</span>，如果选择银行支付，则再次执行<span style="background-color: #c0c0c0;">类似于充值业务的流程</span>。操作完毕后，跳转到商户的<span style="background-color: #c0c0c0;">回调URL</span>。对于快捷支付，不涉及页面跳转，是通过银行后台接口完成的</p>
<p><strong>退款业务分析：</strong>商户平台中发生的退货申请，与xpay平台没有关系，只要商户平台同意退款，那么商户就调用<span style="background-color: #c0c0c0;">xpay提供的接口</span>，将订单号信息传递给xpay，xpay要么将商户账户的钱<span style="background-color: #c0c0c0;">划给用户的虚拟帐户</span>，要么将商户<span style="background-color: #c0c0c0;">虚拟帐户的钱直接划走给银行</span>，即原路退回。</p>
<p><strong>提现业务分析：</strong>用户申请提现，审核通过后，xpay<span style="background-color: #c0c0c0;">调用银行支付接口</span>，将钱从xpay的银行账户划走到用户银行卡中，同时<span style="background-color: #c0c0c0;">修改用户虚拟帐户的余额</span>。</p>
<p><strong>功能合并考虑：</strong>考虑到充值、支付、退款、提现业务上、技术上的相似性，合并为一个模块：交易管理；这些模块都牵涉到的与银行的接口，抽取为银行解散模块。</p>
<p><strong>异常情况考虑：</strong>支付平台很多业务都牵涉到金钱，需要相当小心。如果充值时，银行处理成功，但是结果xpay却没收到，那么用户虚拟帐户的金额就偏少；如果提现业务银行处理成功，但是xpay没有收到处理结果，那么用户虚拟账户金额就偏多。虽然这些事情发生几率很小，但都可能导致严重的纠纷。按照行业一般性做法，我们可以进行<span style="background-color: #c0c0c0;">每日对账</span>，如果银行处理成功，xpay没有收到结果的，执行<span style="background-color: #c0c0c0;">补单处理</span>。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（这个是分析出来的需求，不是用户原始需求）</span></span></p>
<p>需要注意的是，<span style="background-color: #c0c0c0;">在业务分析阶段，不管是用户可见的模块，还是纯后台的功能，都要分析。只从业务角度分析，<span style="background-color: #ff0000; color: #ffffff;">不管UI的组织方式</span>：即使在单个UI页面上显示多个业务功能、或者没有任何UI，也与业务分析、模块划分无关</span>。有时候用户会期望从UI的角度思考模块的划分（大菜单，二级菜单等），但是我们需求分析、架构人员则不能这么思考，起码在内部文档中，要保证前面提到的模块划分原则。</p>
<p>如果某个UI模块（展现层）依赖于我们这里分析的多个业务模块，那么可以在静态架构分析阶段，在此展现层模块使用多条线连接到关联的业务逻辑层模块。</p>
<p><span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">在业务域分析的过程中，老师从来没有提到使用系统的角色，老师认为业务用例的分析不属于架构阶段，但是我认为既然业务架构本质上属于析分，那么就应该考虑到业务用例的角色。本平台的角色可以分为：平台管理者、消费者、商户、商户系统、银行支付网关等。</span></span></p>
<div class="blog_h3"><span class="graybg">价值链分析</span></div>
<p>在业务域分析的基础上分析业务域的流程，考虑其依赖、前后关系：用户管理 ➪ 账户管理 ➪ 费率管理 ➪ 交易管理 ➪ 风控管理 ➪ 银行结算。</p>
<p>如果出现某个Concept不在价值链上是正常的。价值链可以方便你从高级别理解业务过程的顺序。</p>
<div class="blog_h3"><span class="graybg">上下文（Context）分析</span></div>
<p>借用UML包图（老师推荐使用EA），来描述中当前系统与周围系统之间的<span style="background-color: #c0c0c0;">数据流动关系</span>，<span style="background-color: #c0c0c0;">箭头从数据的来源指向数据的目的地</span>。一般自己系统的颜色标记为浅色，外部系统的颜色标记为深色。这张图叫做：System Context。</p>
<p><img class="aligncenter size-full wp-image-7301" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_XPayContextAnalysis.png" alt="xpay_XPayContextAnalysis" width="599" height="437" /></p>
<p><span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">老师的图只画了箭头，没有标记文字，我觉得使用文字标注数据的简短描述更好。</span></span></p>
<div class="blog_h3"><span class="graybg">业务组织图（BOM）分析</span></div>
<p>使用UML包图，来描述系统内部各模块（Concept）之间的调用关系。<span style="background-color: #c0c0c0;">箭头的方向从调用方指向被调用方</span>。分析过程一般是：找到一个为<span style="background-color: #c0c0c0;">起点，挨个分析</span>它与周围模块的关系，第一个模块分析完后，再对第二个模块进行上述分析过程，直到分析完所有模块。牵涉到的外部系统也要参与分析。</p>
<p>下图以支付平台为例：</p>
<p><img class="aligncenter size-full wp-image-7307" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_XPayBOM_1.png" alt="xpay_XPayBOM_1" width="100%" /></p>
<p><span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">老师的图只画箭头，没有标记文字，我觉得使用文字标注数据的简短描述更好。如果调用关系过于复杂，则可以只画箭头，辅以文字描述</span></span></p>
<p>大家可以看到，这么简单的系统，模块间的关系就如此复杂，这个<span style="background-color: #c0c0c0;">复杂性是没法避免</span>的，因为业务就是这样，除非Concept划分不合理。这种业务上的复杂性可以在设计阶段，利用接口、消息等技术进行解耦，避免模块之间的强关联。</p>
<p><span style="background-color: #c0c0c0;">这张图在静态架构设计阶段，将构成业务逻辑层的主体</span>。</p>
<div class="blog_h3"><span class="graybg">功能特性（Feature）分析</span></div>
<p>在上面需求访谈、业务域分析的过程中已经完成。</p>
<div class="blog_h3"><span class="graybg">业务路线图（BRM）分析</span></div>
<p>借用UML活动图来表示某个模块（Concept）<span style="background-color: #c0c0c0;">内部的功能流程</span>的关系，这个图中会画出模块中的Feature，并且画出<span style="background-color: #c0c0c0;">Feature对外部模块的调用</span>。</p>
<p>下图以支付平台的账户管理模块为例：</p>
<p><img class="aligncenter size-full wp-image-7319" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_ActivityDiagram_1.png" alt="xpay_ActivityDiagram_1" width="637" height="541" /></p>
<div class="blog_h3"><span class="graybg">领域模型分析</span></div>
<p>列出所有领域类，并识别它们之间的关系，列出所有属性。在业务分析阶段可以使用全中文。</p>
<p>支付平台中的领域类包括：用户、账户、银行卡、交易订单、风险规则、费率规则、风控日志等等。</p>
<div class="blog_h2"><span class="graybg">静态架构设计</span></div>
<p>上面我们完成了业务架构，业务架构之后是软件架构，软件架构包括静态、动态架构两部分，分别关心软件的功能、非功能部分。<span style="background-color: #c0c0c0;">静态架构设计是直接依据业务架构</span>导出的，有时候也称为逻辑（应用）架构设计，该阶段需要做以下事情：</p>
<ol>
<li>做系统的整体结构的设计。即系统的高层分割，系统分为哪些模块。需要注意这些模块分为两类：<span style="background-color: #ff0000; color: #ffffff;">系统性模块、功能性（业务）模块</span>。所谓功能性模块来源于业务分析阶段识别出来的Concept、Feature，与业务紧密相关。而系统性模块则是因为技术需要而设计的模块，例如TCP/IP通信模块</li>
<li>进行组件的设计。把模块拆分为不同的组件，如果模块比较小，则可以只对应一个组件</li>
<li>完成接口的设计。接口分为系统内接口、系统间设计</li>
<li>完成通讯设计。老师所指通讯设计，主要是指模块间、系统间通信报文、传输方式的设计。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">我觉得属于接口设计</span></span></li>
<li>进行分层设计</li>
<li>进行进线程设计</li>
<li>进行部署设计。部署设计往往和网络有关，例如路由、交换机、服务器和软件组件之间的关系</li>
<li>把上面几步分析的内容形成4+1视图</li>
<li>进行架构机制（Pattern）的选型，这些选型会称为未来动态架构中Framework设计的基础</li>
</ol>
<p>我们首先讲述架构机制，因为这一部分很重要。</p>
<div class="blog_h3"><span class="graybg">架构机制选型的重要性</span></div>
<ol>
<li>框架可以提供API，业务模块使用这些API，降低了开发难度</li>
<li>保证架构的执行，保证代码风格一致</li>
<li>解决重复性问题</li>
</ol>
<div class="blog_h3"><span class="graybg">非功能需求总结和量化</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">非功能需求 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>可扩展性<br />Extensibility</td>
<td>添加新功能，不用修改老功能。 量化标准：添加新功能，老代码修改的比例不超过N%</td>
</tr>
<tr>
<td>可伸缩性<br />Scalability</td>
<td>添加新硬件，不用修改软件。量化标准：添加新硬件，软件修改的比例不超过N%</td>
</tr>
<tr>
<td>可配置性<br />Configurability</td>
<td>系统可以通过修改配置文件，来定制系统的功能。量化标准：修改配置文件改变功能时，不需要修改任何代码</td>
</tr>
<tr>
<td>集中资源管理<br />Knowledgeability</td>
<td>最常见的集中资源管理是指数据资源的管理。系统整体使用数据，而不是让数据在模块间传递。量化标准：系统把数据集中存放在一个地方，供模块去获取、修改</td>
</tr>
<tr>
<td>可维护性<br />Maintainability</td>
<td>达到可扩展性、可伸缩性、可配置性、集中资源管理的要求，则认为软件是可维护的 </td>
</tr>
<tr>
<td>性能<br />Performance</td>
<td>
<p>包括若干指标：</p>
<ol>
<li>响应时间，即速率，从事务发起到处理结束消耗的时间</li>
<li>并发度，即指定给定时间内，发生的事务数，如果不指定时间，则意味着瞬时并发，即每秒并发数。并发度表征了系统的接入能力</li>
<li>吞吐量，即单位时间内，成功处理事务的最大数量。吞吐量表征了系统的处理能力</li>
</ol>
<p>并发度与吞吐量应该是结合考虑：</p>
<ol>
<li>如果并发很低、吞吐很高，说明存在硬件浪费</li>
<li>如果并发很高、吞吐很低，说明系统性能低下，持续维持这种状态系统将不稳定。突然性的高并发可能导致事务排队，典型的例子是小米的抢购、12306的春运</li>
</ol>
</td>
</tr>
<tr>
<td>可靠性<br />Reliability</td>
<td>
<p>系统持续稳定运行的能力。一般有两种量化标准：</p>
<ol>
<li>按照时间来量化：比如要求7 × 24 × 99.99，意味着全年宕机时间不得超过8小时。可以通过稳定性测试来验证</li>
<li>按成功执行事务占比来量化：比如要求每10万次事务失败不得超过3次</li>
</ol>
<p>一般维护时间会算在宕机时间中的，除非合同中明确规定。现在很多互联网系统是要求不宕机、热升级的，通常使用分区升级方式</p>
</td>
</tr>
<tr>
<td>健壮性<br />Robustness</td>
<td>
<p><span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">老师的翻译是Stability，但是该词的含义是稳定性，稳定性应该和可靠性是一回事</span></span></p>
<p>一般认为健壮性和抗误操作、容错能力有关。量化标准：宕机（出错）后恢复需要的时间。今天很多系统要求5秒恢复，要保证这样的健壮性，基本上需要双机热备或者集群技术</p>
</td>
</tr>
<tr>
<td>安全性<br />Security</td>
<td>
<p>包括多个方面：</p>
<ol>
<li>加密：保证数据不被非授权人员访问或者窃取，技术上包括VPN、HTTPS、对称密钥</li>
<li>身份认证：确认操作人员的身份，技术上包括：口令验证、数字证书验证，包括加密狗等</li>
<li>数字证书：确认文件的来源，并防止文件作者抵赖</li>
<li>授权：只在操作人员有权限的情况下允许操作</li>
<li>防攻击：例如防拒绝服务攻击、防跨站脚本攻击、防SQL注入</li>
</ol>
</td>
</tr>
<tr>
<td>可移植性<br />Portability</td>
<td>是否支持：数据库的迁移、多操作系统、多语言（人类语言）、可迁移性（系统是否能够向下兼容）</td>
</tr>
<tr>
<td>易用性 <br />Usability</td>
<td>架构阶段不考虑，在需求阶段考虑 </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">xpay平台的非功能需求识别</span></div>
<p>根据业务特性来识别非功能需求：</p>
<ol>
<li>可扩展性：期望未来能够方便的添加新功能，互联网应用需要迅速的对市场做出反应</li>
<li>可伸缩性：面向互联网，在某些商户促销活动时，支付平台的压力会陡增，期望能够方便的添加新服务器以增强系统性能</li>
<li>性能：要求能够支持2000Trans/s的吞吐能力，每次支付能够在3秒内完成全部处理</li>
<li>安全性：牵涉资金，需要可靠的身份验证</li>
<li>可靠性：零宕机，每秒的宕机会导致大量的经济损失</li>
</ol>
<p>识别出来的每一个非功能需求，都是我们的设计目标。有了设计目标后，架构师需要找出对应的技术实现框架机制<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（即架构模式）</span></span>。常见的架构机制包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 架构模式</td>
<td style="width: 120px; text-align: center;">分类</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>MVC</td>
<td style="text-align: center;" rowspan="3">用户界面架构</td>
<td>
<p>模型-视图-控制器模式，该模式可以很好的将表现层代码和业务逻辑分离，从而提高可扩展性，并且为系统提高可配置性，因此也就能够提升可维护性</p>
<p>MVC模式在现代GUI应用中几乎是必选</p>
</td>
</tr>
<tr>
<td>AUI</td>
<td>
<p>抽象用户视图模式，即通过配置的方式自动生成用户界面。这种技术从另外一个角度提高可维护性——少写代码。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">以前公司的MIS平台、我设计的两步视图技术、Breeze框架的实现，均使用这种模式</span></span></p>
</td>
</tr>
<tr>
<td>RIA</td>
<td>
<p>在浏览器中实现类似于C/S效果的复杂UI，提高易用性。ExtJS、Flex、SilverLight、JQuery、ActiveX等框架可以实现此模式</p>
</td>
</tr>
<tr>
<td>IoC</td>
<td style="text-align: center;" rowspan="6">基本架构</td>
<td>控制反转模式，该模式在业务逻辑层解决组件实现之间的耦合问题，从而提高可扩展性</td>
</tr>
<tr>
<td>AOP</td>
<td>面向切面编程，该模式用于将被多个业务模块使用的系统模块独立出来，与业务模块完全解耦，从而提高可扩展性。典型框架是AspectJ</td>
</tr>
<tr>
<td>ORM</td>
<td>对象-关系映射，该模式用于将具体数据库隔离，对数据资源进行集中管理，可以提高可扩展性</td>
</tr>
<tr>
<td>CMF</td>
<td>组件化管理框架，典型的代表是OSGI，可以非常好的解决可扩展性问题，更好的进行解耦</td>
</tr>
<tr>
<td>MF</td>
<td>面向消息框架，也用于解耦，往往与CMF一起使用。典型实现包括MQ、ActiveMQ、Kafka等</td>
</tr>
<tr>
<td>InterfaceBus</td>
<td>接口总线，解决接口的集中资源管理问题，可以提高可扩展性。典型的应用是ESB</td>
</tr>
<tr>
<td>Cluster</td>
<td style="text-align: center;" rowspan="9">
<p>分布式网络架构</p>
<p>（DNA）</p>
</td>
<td>集群模式，这是一种分布式架构，可以提高可靠性、健壮性、可伸缩性。今天绝大部分的集群是通过<span style="background-color: #c0c0c0;">负载均衡NLB+Linux PC集群来实现<span style="background-color: #ffffff;">。ZooKeeper也可以用于集群管理</span></span></td>
</tr>
<tr>
<td>NLB</td>
<td>网络负载均衡（Network Load Balancing）模式。可以提高可靠性、健壮性、可伸缩性。经常使用Nginx来实现，商业产品有F5</td>
</tr>
<tr>
<td>DMF</td>
<td>双机热备。用于防止单点故障，可以提高可靠性、健壮性。可以使用Nginx来实现</td>
</tr>
<tr>
<td>VMF</td>
<td>虚拟机框架。开源界有XEN，未来的趋势是Docker。VMF可以用于实现集群、NLB</td>
</tr>
<tr>
<td>DSF</td>
<td>分布式会话框架。可以配合Cluster、NLB、DMF等分布式架构，实现集群成员的无差别化</td>
</tr>
<tr>
<td>DFS</td>
<td>分布式文件系统。可以提高性能、安全性、可伸缩性。典型代表是HDFS</td>
</tr>
<tr>
<td>DC</td>
<td>分布式计算。配合分布式文件系统实现大数据处理。如果数据达到TB级，应该考虑使用分布式计算。典型代表是Hadoop、Storm，分别用于处理存储上的静态数据、来自网络的流式数据，其它还有Spark</td>
</tr>
<tr>
<td>DCF</td>
<td>分布式缓存框架。可以提高性能。用的比较多的是Redis</td>
</tr>
<tr>
<td>Cloud</td>
<td>
<p>云架构，综合虚拟化技术、面向服务的设计思想，将基础设施、平台、软件等都作为服务对外提供。相关技术有OpenStack、CloudStack</p>
<p>云架构可以很好的解决突发的系统压力，同时不需要太多的运维、硬件成本</p>
</td>
</tr>
<tr>
<td>SimpleDB</td>
<td style="text-align: center;" rowspan="4">数据库架构</td>
<td>简单的、不完全保证ACID的数据，用于存放一些不重要的数据，可以提高性能。典型实现是文档数据库MongoDB</td>
</tr>
<tr>
<td>ClusterDB</td>
<td>集群关系型数据库，可以提高可伸缩性，例如Oracle的RAC</td>
</tr>
<tr>
<td>ShardingDB</td>
<td>分区数据库，可以提高可伸缩性，例如MySQL的Proxy，开源实现有Amoeba。主要做SQL的转发和结果的收集</td>
</tr>
<tr>
<td>RWS</td>
<td>读写分离（Read/Write Splitting），通过主从（Master-Slave）复制手段，使用不同的数据库实例完成读、写操作</td>
</tr>
<tr>
<td>SPC</td>
<td style="text-align: center;" rowspan="2">页面缓存架构</td>
<td>静态页面缓存架构，通过对不需要改变的页面进行缓存、静态化，提高性能。电信的详单查询、门户网站新闻、统计报表适合使用该模式实现</td>
</tr>
<tr>
<td>DPC</td>
<td>动态页面缓存架构，通过动态生成的、隔一段时间会发生变化的页面进行缓存，提高性能。12306的车次信息适合使用该模式实现</td>
</tr>
<tr>
<td>CQRS</td>
<td style="text-align: center;">其它</td>
<td>
<p>命令与查询责任分离（Command Query Responsibility Segregation）：查询操作不会改变数据，属于幂等性操作，可以反复发起，可以基于缓存来提供性能，此外查询操作常常远多于命令操作；命令操作则会影响系统的状态，具有事务性要求，可能不需要返回值，因而可以异步处理。</p>
<p>由于查询与命令操作的差异性，将其分离处理——使用不同的模型来处理读、写——可以很好的提高系统的可扩容性、性能。axon framework是CQRS的实现</p>
<p>RESTful API适合暴露CQRS系统的功能</p>
</td>
</tr>
</tbody>
</table>
<p>架构机制包括三个层次的内容：机制的核心思想、机制的详细设计、机制的代码和API。其中机制的核心思想是通用的，设计以及代码则依据具体实现各不相同。</p>
<p>确定系统需要使用哪些架构机制，可以通过两种分析方式：</p>
<ol>
<li>自顶而下：架构师非常有经验，直接依据系统特点、个人经验判断使用哪些机制。缺点是可能引入不必要的机制，造成不必要的工作量</li>
<li>自底而上：开始时只使用简单的、必要的机制，根据业务需要，进行架构重构，引入新的机制。缺点是为未来重构的代价较大</li>
</ol>
<p>根据上一节识别出来的xpay系统的非功能设计目标，可以考虑使用：MVC、IoC、CMF、MF、Cluster、DSF这几个框架机制。</p>
<div class="blog_h3"><span class="graybg">高层分割</span></div>
<p>高层分割，即整体结构设计，是系统的<span style="background-color: #c0c0c0;">横向结构划</span>分，需要践行以下原则：</p>
<ol>
<li>软件模块中的<span style="background-color: #c0c0c0;">主要模块，来自于业务模块</span>，即：业务模块会称为软件模块的一部分。任何一个Concept都会成为软件模块，这些Concept会落在展现层、业务逻辑层等层次</li>
<li><span style="background-color: #c0c0c0;">所有的机制</span>，都会成为软件模块，并形成Framework层的主要内容</li>
<li>模块的暴露方式应该是接口</li>
<li>模块间要避免循环依赖，如果存在循环依赖，应当使用消息/事件等方式解开</li>
</ol>
<div class="blog_h3"><span class="graybg">分层设计（逻辑视图）</span></div>
<p>我们知道，在软件工程中，引入新的抽象层是屏蔽复杂性的通用方法。在确定软件模块后，应当进行分层设计，分层是系统的<span style="background-color: #c0c0c0;">纵向结构划分</span>。分层设计应当践行以下原则：</p>
<ol>
<li>避免不必要的分层，过多的分层不但降低性能，还增加系统的复杂性</li>
<li>自上而下，逐层依赖，避免跨层调用。跨层调用破坏层的价值</li>
<li>严禁反向调用。在分层设计中，越底下的层越稳定，让稳定的层调用不稳定的层，将破坏其本身的稳定性。<span style="text-decoration: underline; color: #c0c0c0;">以前我们的平台就出现了高层代码入侵的情况</span></li>
<li>层间应该通过抽象接口来隔离。<span style="text-decoration: underline;"><span style="background-color: #ffffff; color: #c0c0c0; text-decoration: underline;">例如，我们的sshe平台通过自定义的接口隔离Hibernate</span></span></li>
</ol>
<p>注意，架构师并不能完全保证不出现跨层调用，例如使用旧式的MVC框架时，控制层会依赖于Framework层的组件。出现跨层调用时，架构师应当给予充分的理由。</p>
<p>经典的分层模型为六层，足够适用于大部分的系统：</p>
<ol>
<li>展现层：包含了UI组件</li>
<li>控制层：包含了MVC的控制器</li>
<li>业务逻辑层：包含了常规的业务模块</li>
<li>框架层：包含了所有架构机制，典型的会在框架层之上提供一个抽象接口层API。同时需要进行开源选型</li>
<li>中间件层：包含非平台性质的第三方产品，例如消息中间件、JavaEE容器<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">，一些JDK标准化组件也被老师分在这一层</span></span></li>
<li>基础设施层： 包括硬件、操作系统、通信机制</li>
</ol>
<p>分析后，将上面的分层绘制成图，具体的技术选型使用版型表示，越下面的层次使用越冷的颜色表示。层次间的调用使用箭头表示，如果上层大部分模块都需要调用下层模块，则不必画调用箭头。xpay的分层图如下：</p>
<p><img class="aligncenter size-full wp-image-7416" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_HierarchicalDesign.png" alt="xpay_HierarchicalDesign" width="95%" /></p>
<div class="blog_h3"><span class="graybg">接口设计（逻辑视图）</span></div>
<p>接口设计应当践行以下原则：</p>
<ol>
<li>接口主要的目的是用于<span style="background-color: #c0c0c0;">模块间、系统间</span>的通信，分别称为内部接口、外部接口</li>
<li>接口由业务决定，应当逐一分析功能（Feature），来分析哪些功能需要对外暴露，并设计为接口</li>
<li>接口不应当由调用方决定，防止调用方的特殊需求导致调用方的逻辑混入接口提供方，长期下去会导致接口非常多<span style="text-decoration: underline; color: #c0c0c0;"><span style="background-color: #ffffff;">。比如在xpay平台中，交易管理模块不应该直接响应某个模块要求的“查询交易额大于5000的交易记录”</span></span></li>
<li>在设计接口时，一个需要注意的是参数的类型，应当尽量使用领域模型而不是基本类型，特别是避免使用冗长的基本类型参数列表，以增强接口的稳定性</li>
<li>外部接口因为牵涉到远程调用，接口的粒度应当尽量粗，避免影响性能</li>
</ol>
<p>在分析功能以识别接口的时候，应当从业务分析的BOM开始，BOM中已经标明Concept之间、Concept与外部系统之间的调用关系。然后进一步通过BRM来查看各Feature在业务活动中的位置、与其它Feature的前后关系，并从中识别出接口。</p>
<p> 以xpay交易管理模块为例分析：</p>
<ol>
<li>充值，会被本模块的页面调用，不需要暴露接口</li>
<li>交易查询，会被本系统的其它模块调用，暴露为内部接口：findTransaction</li>
<li>账户支付，会被第三方系统调用，需要暴露为外部接口：doPayment，需要进一步进行系统间通信方式的设计，例如基于HTTP的报文交换</li>
<li>……</li>
</ol>
<p>接口设计应当完成接口规格的详细规定。</p>
<div class="blog_h3"><span class="graybg">组件设计（组件视图/实现视图）</span></div>
<p>组件是模块的<span style="background-color: #c0c0c0;">物理实体（往往在操作系统中表现为文件）</span>，组件一般是在<span style="background-color: #c0c0c0;">模块下的细化</span>。组件设计完成的任务类似于温老师的开发架构视图。</p>
<p>如果模块的功能较为复杂，可以根据业务的相关性分割组件；如果模块功能较为简单，可以直接作为单个组件<span style="text-decoration: underline;"><span style="background-color: #ffffff; color: #c0c0c0; text-decoration: underline;">。我倾向于把若干简单的模块划分到一个组件中，避免工程管理的复杂度</span></span></p>
<p>组件设计最终使用UML组件图来表示、使用版型说明组件的类型（例如jar、dll、so），以xpay交易管理模块为例：</p>
<p><img class="aligncenter size-full wp-image-7428" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_TransactionMgrCompDiagram.png" alt="xpay_TransactionMgrCompDiagram" width="577" height="191" /></p>
<p>组件内部可以标出关键的包、类。</p>
<div class="blog_h3"><span class="graybg">进线程设计（并发视图）</span></div>
<p>并发视图用来说明组件与进线程的对应关系。</p>
<p>实现并发的两种方式是：多进程和多线程，两者的本质区别是后者共享地址空间，性能较高，但是没有多进程安全（单个线程可能导致应用崩溃）、编程难度大。并发本身并不会提高性能，但是由于系统中大量I/O的存在，单线程可能因为I/O阻塞而浪费CPU资源，并发设计特别适用于I/O密集型系统，对CPU密集型系统的性能提高意义不大。</p>
<p><span style="background-color: #c0c0c0;">多CPU、分布式、计算的强度、事件驱动、并行计算等</span>会导致并发设计的考虑。 </p>
<p>进行并发设计后，<span style="background-color: #c0c0c0;">线程、进程一般都会在组件上提体现</span>。我们在<span style="background-color: #c0c0c0;">组件视图</span>上使用<span style="background-color: #c0c0c0;">红线框来标注进程</span>、<span style="background-color: #c0c0c0;">使用红线条标注多线程，</span>并标注并发进线程数量，例如xpay进线程设计的片段如下：</p>
<p><img class="aligncenter size-full wp-image-7441" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_TransactionMgrCompDiagram1.png" alt="xpay_TransactionMgrCompDiagram" width="589" height="442" /></p>
<p>个人觉得，没必要在并发视图上体现出模块与组件的关系，我倾向于使用部署图的Artifact来包围组件，形成并发视图：</p>
<p><img class="aligncenter size-full wp-image-7447" src="https://blog.gmem.cc/wp-content/uploads/2015/07/concurrent-xpay-view.jpg" alt="concurrent-xpay-view" width="545" height="334" /></p>
<div class="blog_h3"><span class="graybg">部署设计（部署视图）</span></div>
<p>部署视图说明<span style="background-color: #c0c0c0;">组件与服务器、网络的对应关系</span>。</p>
<p>最早的应用程序，后台只部署一个数据库，所有业务逻辑都放在客户端<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（杀毒软件）</span></span>；后来发展到后台包含一台应用服务器，客户端处理一些简单的应用逻辑、图像渲染<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（网络游戏）</span></span>；再后来，发展到B/S，后端往往包含Web服务器和应用服务器，后者存放业务逻辑层<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（电子商务网站）</span></span>。这些都是属于客户端/服务器架构。</p>
<p>除了<span style="background-color: #c0c0c0;">客户端/服务器部署架构</span>外，还有一种<span style="background-color: #c0c0c0;">对等网络架构（Peer-to-Peer）</span>，它利用NAT穿透技术（UDP打洞），允许任意两个处于NAT设备背后的局域网机器建立通信。这种网络架构一方面可以减轻服务器的压力，另外一方面具有顽强的生存能力。</p>
<p>出于减轻单服务器负载、可伸缩性、更优性价比<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（二版的反例）</span></span>等方面的考虑，大型复杂的系统都需要使用分布式架构。</p>
<p>通常网络环境由网络管理部门提供，我们需要在<span style="background-color: #c0c0c0;">网络拓扑图上标记出组件与网络服务器的对应关系</span>。<span style="text-decoration: underline;"><span style="color: #c0c0c0; text-decoration: underline;">（我觉得UML构件更合适，组件并不是运行时实体，而且组件可能被部署在多个不同用途的构件中）</span></span>。UML中的部署图用来绘制部署视图，但是视觉效果较差，可以使用VISIO、亿图等工具绘制。</p>
<p><img class="aligncenter size-full wp-image-7449" src="https://blog.gmem.cc/wp-content/uploads/2015/07/xpay_XpayDeployment.png" alt="xpay_XpayDeployment" width="95%" /></p>
<div class="blog_h2"><span class="graybg">动态架构设计</span></div>
<p>动态架构设计阶段需要：</p>
<ol>
<li>根据静态架构阶段的架构机制选型，确定需要使用哪些开源框架，确定需要自主开发哪些框架</li>
<li>对于自主开发的框架，需要完成详细设计和代码编写</li>
<li>补充架构文档</li>
</ol>
<p>在这个议题中，老师主要讲了一个案例。</p>
<div class="blog_h3"><span class="graybg">案例：下一代能源平台（NGEP）</span></div>
<p>NGEP希望能监控全球油田的油井的实时数据（压力、温度、粘度、频谱等），由于这些油井都在荒无人烟的地方，因此数据采集后需要上传到卫星，由卫星转发到位于数据中心的NGEP以便进行处理。NGEP有数千种应用，其中之一：由于地质条件的影响，油井的压力可能突然增高，导致井喷，进而破坏上端设备，为了防止井喷的发生，我们通过监控油管的压力，在到达一定的临界值后关闭油井出口钢板。</p>
<p>NGEP初期准备支持8万口油井的实时监控。数据采集的频率很高、每口油井每天大概产生几百M的数据，数据一般需要保留三年，8万口油井的累计数据最多可到达8000TB，初期准备了200台服务器。经过几年发展，发展到2000万口油井，因此NGEP的系统架构经历了多次重构：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">阶段 </td>
<td style="text-align: center;">架构说明与重构情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>平台建设初期：从有数百口油井，数台服务器发展到数万口油井，数百台服务器</td>
<td>
<p>在以前，很多团队根据组件对计算资源要求的不同，将其部署到不同的服务器上，比如对于实时计算的组件，将其多部署几台服务器，其它对计算资源要求较少的组件，例如数据采集，则少部署几台服务器。</p>
<p>这种部署方式随着系统规模的不断扩大，会带来很多运维负担，因为你需要不断的重新调整部署规划。按DNA架构的思路，每台服务器应该<span style="background-color: #ff0000; color: #ffffff;">部署一样的组件集</span>，并通过NLB机制进行请求转发。NGEP在初期开发了一个很简单<span style="background-color: #c0c0c0;">NLB</span>，就是通过random()函数进行随机的转发。应用了DNA架构后，就能够均匀的将负载分布在多台服务器上。为了防止服务器宕机导致请求处理失败，NGEP进一步开发了<span style="background-color: #c0c0c0;">心跳监听</span>，对每台服务器进行心跳监听，一旦服务器宕机就通知NLB。</p>
<p>NLB大大提供了系统的可靠性，NGEP逐渐的不再依赖于小机，而是转用PC服务器，此时PC + NLB的Cluster已经具备雏形。为了防止NLB分发节点的单点故障，NGEP引入了<span style="background-color: #c0c0c0;">分发节点的双机热备</span></p>
<p>DNA也让应用程序部署、升级变得简单，只需要一个通用的部署脚本即可</p>
<p>由于硬件成本的降低，NGEP可以更低的价格提供服务，因而吸引了更多油井接入</p>
</td>
</tr>
<tr>
<td>运维的压力：60多万口油井，2000台服务器</td>
<td>
<p>后期引入的服务器，性能比老的服务器高，此时基于random()的NLB就不适用了，NGEP引入了<span style="background-color: #c0c0c0;">monitor组件</span>，该组件采集服务器的CPU、网络、I/O等参数，形成一个<span style="background-color: #c0c0c0;">因子</span>，影响NLB转发请求的策略</p>
<p>系统到达这个规模了，运营的压力非常大，NGEP开始寻找第三方的数据中心，最后找到位于奥斯丁的数据中心ODC，该数据中心连更换硬盘等工作都是机械手自动完成的</p>
<p>2000台服务器的脚本部署也是非常大的工作量，因此NGEP考虑引入<span style="background-color: #c0c0c0;">虚拟机管理框架（VMF）</span>，他们选择XEN，并在最近考虑切换到Docker</p>
<p>有了VMF后，只需要将image复制到对应服务器上就可以完成部署，NGEP甚至根据服务器的负载，来决定部署虚拟机的数量</p>
</td>
</tr>
<tr>
<td>高资源消耗请求导致的负载不均衡</td>
<td>
<p>在之前的建设中，NGEP使用动态列表的方式进行Session相关性的请求转发，这导致了一个严重的问题：后续请求被关联到固定的服务器上。当出现大量高资源消耗的分析请求时，无法进行负载重新均衡。NGEP在运营过程中出现了1/3服务器空载，1/3服务器满载并且性能低下的情况。</p>
<p>为了解决该问题，NGEP<span style="background-color: #c0c0c0;">引入DSF</span>，配置一台大内存（数百G）服务器，并且自行开发Session接口，所有组件中涉及到Session操作的代码均改调此接口，为了避免此分布式会话服务器的单点故障，引入了双机热备</p>
</td>
</tr>
<tr>
<td>周边应用的引入</td>
<td>
<p>随着数据量的不断增加和NGEP平台的发展，大量周边应用（例如三维分析）被开发，最初这些周边应用是出售给客户，在它们自己的机房中部署，通过远程获取能源平台的数据进行处理分析的</p>
<p>后来客户提出要求，你们能不能把应用的运维也管起来？于是NGEP把这些周边应用也打包部署在数据中心、利用VMF自动化管理，客户只需要在NGEP的portal中注册、选取需要的服务、服务使用的时间，即可生成账单、使用服务</p>
<p>系统发展到这一步，实际上就是一个<span style="background-color: #c0c0c0;">私有云</span>了</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">架构文档包含的内容</span></div>
<ol>
<li>项目简介、预期读者等描述性内容</li>
<li>逻辑视图，并对主要内容进行说明：首先做层的技术实现、职责的说明；然后做主要模块的描述</li>
<li>组件视图，把图中的组件拿出来，形成一张全是组件的图，并对每一个组件进行说明</li>
<li>并发视图，与组件视图是一张图，需要说明组件与进线程的关系</li>
<li>部署视图，并说明组件与物理计算机的关系</li>
<li>通信协议的说明</li>
<li>接口设计，包括内部、外部接口的详细说明</li>
<li>底层框架设计，对使用的所有框架挨个说明，并给出必要的使用规范</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/201507-architect-training-note">2015年7月架构师培训笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/201507-architect-training-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>反应器模式</title>
		<link>https://blog.gmem.cc/reactor-pattern</link>
		<comments>https://blog.gmem.cc/reactor-pattern#comments</comments>
		<pubDate>Sat, 21 Mar 2015 08:48:28 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[架构模式]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=25349</guid>
		<description><![CDATA[<p>模式定义 反应器模式（Reactor pattern）是一种事件处理模式，用于处理从一个或多个输入并发的发送给单个服务处理器（service handler）的请求。服务处理器对请求进行多路分用（demultiplexes） —— 解析并分发（dispatcher）给请求处理器（service handler）。 反应器模式通常是单线程的，但是它可以在多线程系统中运行。 模式结构和说明 角色 资源 能够为系统提供输入，或者消费系统的输出。 同步事件多路分离器 即Synchronous Event Demultiplexer，负责运行事件循环， 在所有资源上阻塞。当可以在资源上执行异步操作时，此分离器将资源转给分发器（dispatcher）。 分发器 能够注册、解除注册请求处理器，分发来自Demultiplexer的资源给相应的请求处理器。 请求处理器 处理特定类型的资源。 应用举例 <a class="read-more" href="https://blog.gmem.cc/reactor-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/reactor-pattern">反应器模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">模式定义</span></div>
<p>反应器模式（Reactor pattern）是一种<span style="background-color: #c0c0c0;">事件处理模式</span>，用于处理从一个或多个输入<span style="background-color: #c0c0c0;">并发的发送</span>给单个服务处理器（service handler）的请求。服务处理器对请求进行多路分用（demultiplexes） —— 解析并分发（dispatcher）给请求处理器（service handler）。</p>
<p>反应器模式通常是单线程的，但是它可以在多线程系统中运行。</p>
<div class="blog_h1"><span class="graybg">模式结构和说明</span></div>
<div class="blog_h2"><span class="graybg">角色</span></div>
<div class="blog_h3"><span class="graybg">资源</span></div>
<p>能够为系统提供输入，或者消费系统的输出。</p>
<div class="blog_h3"><span class="graybg">同步事件多路分离器</span></div>
<p>即Synchronous Event Demultiplexer，<span style="background-color: rgb(192, 192, 192);">负责运行事件循环</span>， 在所有资源上阻塞。当<span style="background-color: rgb(192, 192, 192);">可以在资源上执行异步操作时</span>，此分离器将资源转给分发器（dispatcher）。</p>
<div class="blog_h3"><span class="graybg">分发器</span></div>
<p>能够注册、解除注册请求处理器，分发来自Demultiplexer的资源给相应的请求处理器。</p>
<div class="blog_h3"><span class="graybg">请求处理器</span></div>
<p>处理特定类型的资源。</p>
<div class="blog_h1"><span class="graybg">应用举例</span></div>
<div class="blog_h2"><span class="graybg">libevent</span></div>
<p>libevent是一个事件驱动编程库，可以在文件描述符上发生特定事件、超时后，执行相应的回调函数。回调函数还可以由信号、定时器触发。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/reactor-pattern">反应器模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/reactor-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>软件架构设计基础</title>
		<link>https://blog.gmem.cc/introduction-to-software-architecture-design</link>
		<comments>https://blog.gmem.cc/introduction-to-software-architecture-design#comments</comments>
		<pubDate>Mon, 24 Oct 2011 07:24:50 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=16773</guid>
		<description><![CDATA[<p>架构简介 什么是架构 微软的观点 架构是一个过程，它定义了满足软件所有操作性、技术性需求的结构化的解决方案。同时对各种质量属性进行优化。 Kruchten的观点 Philippe Kruchten（RUP主管）等人认为，架构是关于软件系统如何组织的一系列重大决定，包括： 结构元素的识别选择以及它们的对外接口 行为元素的识别，即上述结构元素之间的协作行为 上述结构元素、行为元素如何有机结合成为子系统 Philippe Kruchten也认可架构牵涉到易用性、健壮性、性能、可重用性等非功能因素。同时需要兼顾技术性约束和权衡以及美学方面的考虑。 Fowler的观点 企业应用架构模式的作者Martin Fowler总结了当解释何为软件架构时，会反复出现的论点： 架构将系统进行高层分解，形成多个协作的子系统或者模块 这种分解决策一旦落地实施，就难以再改变 一个大型系统中会有多个架构 在系统的生命周期中，架构可能随着时间而改变，甚至发生重构 架构可以归结为：软件设计中任何重要的东西 我的观点 尽管有不同的描述方式，软件架构一定牵涉到以下内容： <a class="read-more" href="https://blog.gmem.cc/introduction-to-software-architecture-design">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/introduction-to-software-architecture-design">软件架构设计基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">架构简介</span></div>
<div class="blog_h2"><span class="graybg">什么是架构</span></div>
<div class="blog_h3"><span class="graybg">微软的观点</span></div>
<p>架构是一个过程，它定义了满足软件所有操作性、技术性需求的结构化的解决方案。同时对各种质量属性进行优化。</p>
<div class="blog_h3"><span class="graybg">Kruchten的观点</span></div>
<p>Philippe Kruchten（RUP主管）等人认为，架构是关于软件系统如何组织的一系列重大决定，包括：</p>
<ol>
<li>结构元素的识别选择以及它们的对外接口</li>
<li>行为元素的识别，即上述结构元素之间的协作行为</li>
<li>上述结构元素、行为元素如何有机结合成为子系统</li>
</ol>
<p>Philippe Kruchten也认可架构牵涉到易用性、健壮性、性能、可重用性等非功能因素。同时需要兼顾技术性约束和权衡以及美学方面的考虑。</p>
<div class="blog_h3"><span class="graybg">Fowler的观点</span></div>
<p>企业应用架构模式的作者Martin Fowler总结了当解释何为软件架构时，会反复出现的论点：</p>
<ol>
<li>架构将系统进行高层分解，形成多个协作的子系统或者模块</li>
<li>这种分解决策一旦落地实施，就难以再改变</li>
<li>一个大型系统中会有多个架构</li>
<li>在系统的生命周期中，架构可能随着时间而改变，甚至发生重构</li>
<li>架构可以归结为：软件设计中任何重要的东西</li>
</ol>
<div class="blog_h3"><span class="graybg">我的观点</span></div>
<p>尽管有不同的描述方式，软件架构一定牵涉到以下内容：</p>
<ol>
<li>系统的结构或者叫组织——将个系统分解为多个组件，分解的粒度不会过细。根据系统的规模不同，可能分解到子系统或者模块级别</li>
<li>明确分解后系统组件的交互机制</li>
<li>明确对各种质量需求如何支持，这通常需要引入中间件、框架</li>
</ol>
<p>用户接口风格设计（例如交互风格、主题和配色）通常不属于系统架构师的职责，但我认为用户接口风格应当在架构阶段即落地。</p>
<div class="blog_h2"><span class="graybg">架构的目标</span></div>
<p>在决定软件架构时，需要考虑：</p>
<ol>
<li>用户如何与软件交互</li>
<li>软件如何在生产环境下部署和管理</li>
<li>软件的运行期质量属性，例如性能、并发性、安全性、健壮性、可扩容性、高可用性、易用性</li>
<li>在软件的生命周期内，开发期质量属性，例如可互操作性、可扩展性、可重用性、可维护性如何保证</li>
<li>架构的最根本的部分是什么，或者说，哪些决定如果出错后果最严重</li>
<li>架构的最易变的部分是什么，或者说，哪些决定可以延迟决定</li>
<li>你做了哪些关键的假设，如何验证这些假设。不要过度设计</li>
<li>什么情况下，可能导致架构重构</li>
</ol>
<p>架构的目标：</p>
<ol>
<li>暴露系统结构的同时，隐藏实现细节</li>
<li>实现所有需求用例和应用场景</li>
<li>满足不同干系人的需求</li>
<li>同时满足功能需求、质量需求</li>
</ol>
<div class="blog_h2"><span class="graybg">架构的重要性</span></div>
<p>为什么要做架构？其根本原因是为了降低复杂性，否则用机器语言可以编写出任何软件。</p>
<p>和其它任何工程一样，软件工程必须具有坚实的基础。对关键场景的错误考虑，对公共模块的错误设计，缺乏对关键决策的长远后果的考虑，都可能对软件工程带来严重的后果。例如：</p>
<ol>
<li>软件不能稳定运行</li>
<li>不能支持现有或者未来的需求</li>
<li>难以在生产环境下部署和管理</li>
</ol>
<p>引入中间件、框架，可以减轻构建软件的工作量，但是不能用来代替针对特定应用场景和需求的架构。架构不是一堆框架的糅合。</p>
<div class="blog_h2"><span class="graybg">架构和框架</span></div>
<p>框架是一种可重用的半成品软件，既然是软件，框架当然可以有自己的架构设计。</p>
<p>框架提供一般性的软件功能，这些功能可以被选择性的覆盖或者扩展，以产生针对性的软件。</p>
<p>框架用于辅助架构的落地，主要用于实现质量需求。在软件实现阶段，框架成为软件的一部分。</p>
<div class="blog_h2"><span class="graybg">架构和设计</span></div>
<p>架构关注软件中的主要元素和组件如何交互（如何被其它组件或者用户使用）。每个组件的数据结构、算法或者实现细节是详细设计阶段需要考虑的。架构和详设常常重叠。某些情况下，详设具有天然的架构性特征，另外一些情况下，某些重大决策是细致到详设的。</p>
<div class="blog_h1"><span class="graybg">架构原则</span></div>
<p>前面我们提到过，软件架构（作为名词时）通常被描述为系统的结构或者组织。所谓系统，是完成一个或者多个功能的组件的集合。也就是说，架构需要决定如何来组织组件以满足功能。组件常常基于关注域（areas of concern）进行分组，例如下面的架构图，组件被划分到多个关注域：</p>
<p><img class="aligncenter size-full wp-image-16790" src="https://blog.gmem.cc/wp-content/uploads/2011/10/common-architecture.png" alt="common-architecture" width="509" height="527" /></p>
<p>分层是解决软件复杂性问题的重要法宝，在上图的例子中，多个关注域构成一个逻辑层，多个逻辑层堆叠形成系统的整体逻辑架构，跨层之间通常不会有交互。</p>
<div class="blog_h2"><span class="graybg">关键设计原则</span></div>
<p>很多设计原则从实际软件工程的经验教训中总结得到，这些原则不仅仅适用于架构，也适用于详设：</p>
<ol>
<li>关注点分离原则：尽可能的把软件划分为多个相互尽可能没有重叠的特性。重要的估量因子是，最小化交互点以实现高内聚低耦合</li>
<li>单一职责原则：每个组件/模块应当仅仅负责一个特性/功能，或者一个聚合性的功能</li>
<li>最少知识原则：也叫迪米特法则。一个组件应该尽可能少的知道其它组件的内部细节</li>
<li>不要重复自己（DRY）：一个特定的特性/功能应该仅仅在一个组件中实现，不应在其它组件中重复</li>
<li>尽可能使用组合而非继承</li>
<li>不要过度设计：仅仅设计需要的功能。进行前瞻性设计时一定要考虑成本和失败的后果</li>
</ol>
<div class="blog_h3"><span class="graybg">整体设计</span></div>
<ol>
<li>在每个层内，保持设计模式的一致性</li>
<li>不要在应用程序里面重复实现一个功能</li>
<li>创建编码风格和命名规范</li>
</ol>
<div class="blog_h3"><span class="graybg">分层设计</span></div>
<ol>
<li> 将关注域分离到层，应用关注点分离原则。主要好处有：
<ol>
<li>一个特性/功能可以独立的被优化</li>
<li>如果一个特性开发失败，可以被替换而不影响其它特性</li>
<li>让代码更加容易理解</li>
</ol>
</li>
<li>明确不同层之间如何交互。通常下层不会依赖上层，不得出现跨层依赖</li>
<li>基于抽象达到层间松耦合的目标。抽象类、接口、Facade模式都是可用的工具</li>
<li>不要把不同类型的组件混到一个层中</li>
<li>在层或组件内保持数据格式的一致性</li>
</ol>
<div class="blog_h3"><span class="graybg">组件/模块/函数设计</span></div>
<ol>
<li>组件或对象不应依赖其它组件或对象的内部细节，应用最少知识原则</li>
<li>不要让组件的功能过载，应用单一职责原则。例如UI组件不应该包含数据库访问代码</li>
<li>理解组件之间的交互方式。要结合部署方式确定，两个组件在进程内、跨进程还是跨机器部署</li>
<li>尽可能的把横切性的功能从商业逻辑中抽象出去，越远越好。横切性的功能包括身份验证和授权、通信、缓存、日志等</li>
<li>为组件定义一份清晰的接口规约</li>
</ol>
<div class="blog_h2"><span class="graybg">关键设计决策</span></div>
<div class="blog_h3"><span class="graybg">决定应用类型</span></div>
<p>基本的应用程序类型包括：</p>
<ol>
<li>为移动设备设计的应用</li>
<li>富客户端应用，安装并在客户端PC运行</li>
<li>富因特网应用，具有丰富的UI组件</li>
<li>服务型应用，用于支持松耦合的组件的交互</li>
</ol>
<div class="blog_h3"><span class="graybg">决定部署策略</span></div>
<p>决定你的应用程序如何部署。各组件是否需要支持跨进程部署，是否需要支持部署在不同的服务器上，这些服务器是否具有网络协议限制、防火墙、路由限制。</p>
<p>部署策略对架构的影响很大，因为需要进程内组件交互可以直接进行函数调用，进程外组件交互必须使用IPC或者Socket等机制。</p>
<div class="blog_h3"><span class="graybg">决定技术框架</span></div>
<p>最近十多年各领域开源框架大放异彩，因此首先考虑选型开源框架，无法满足需求的情况下才考虑自研。</p>
<p>引入框架后，你的应用程序往往和它产生某种程度上的耦合，因此框架的选择需要慎重。使用框架时，应当尽量避免产生耦合，特别是明确的API调用。</p>
<div class="blog_h3"><span class="graybg">决定质量属性</span></div>
<p>质量属性聚焦于架构设计需要解决的一系列非功能性的关键问题。根据需求的不同，你需要关注的质量属性也不同。通常每个应用程序都需要关注安全性和性能，但是很多应用程序不需要关注互操作性和可扩容性。</p>
<div class="blog_h3"><span class="graybg">决定横切关注域</span></div>
<p>横切关注域是不仅仅和某个特定分层有关的关注域。关键的横切关注域包括：</p>
<ol>
<li>系统日志：记录系统中各种事件，但不引入敏感信息</li>
<li>身份验证：决定如何对用户、外部系统的身份进行验证。如何在各层之间传递其身份信息</li>
<li>访问授权：决定每个层的每个组件能否被指定的实体访问</li>
<li>异常管理：避免异常信息暴露到最终用户</li>
<li>通信机制：选择适当的通信协议，最小化网络调用，保证数据安全</li>
<li>缓存管理：识别哪些数据应该被缓存，在哪里缓存</li>
</ol>
<div class="blog_h1"><span class="graybg">架构风格</span></div>
<p>所谓架构风格（Architectural Style，也叫架构模式），是一系列可重用的设计原则， 这些原则以组粒度Pattern的形式定义了一大类系统的抽象框架。你可以认为架构风格是一系列确定了应用系统“形状”的设计原则。</p>
<p>架构风格不会牵涉到具体的技术，因此可以用在技术不可知的技术讨论中。</p>
<p>架构风格可以根据它们的关注域的不同进行分类：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">分类</td>
<td style="text-align: center;">架构风格</td>
</tr>
</thead>
<tbody>
<tr>
<td>通信</td>
<td>SOA、消息总线</td>
</tr>
<tr>
<td>部署</td>
<td>
<p>客户端/服务器、N-Tier（多层式）、3-Tier、级联、负载均衡、HA</p>
</td>
</tr>
<tr>
<td>领域</td>
<td>领域驱动设计（DDD）</td>
</tr>
<tr>
<td>结构</td>
<td>基于组件的架构、面向对象的架构、分层架构、微服务架构</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">架构风格摘要</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">架构风格</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>客户端/服务器架构</td>
<td>将系统分割为两个独立的应用程序：服务器、客户端。客户端向服务器发起请求</td>
</tr>
<tr>
<td>基于组件架构</td>
<td>将应用程序分解为多个可重用的、具有良好接口定义的组件</td>
</tr>
<tr>
<td>领域驱动设计</td>
<td>一种面向对象的架构风格，关注如何对商业领域进行建模，并以实体（Entity）的形式在商业领域内对商业对象进行定义</td>
</tr>
<tr>
<td>分层架构</td>
<td>将应用程序的关注点分区为堆叠的层 </td>
</tr>
<tr>
<td>消息总线 </td>
<td>规定中间件系统可以基于一个或者多个通信通道接收、发送消息。这样，通过此中间件交互的组件不需要知道彼此的细节</td>
</tr>
<tr>
<td>N-Tier / 3-Tier</td>
<td>类似于分层架构，但是各层部署在不同的物理机器上</td>
</tr>
<tr>
<td>面向对象</td>
<td>将应用程序或者系统分解为多个可重用、自包含的对象中。每个对象包含数据和行为</td>
</tr>
<tr>
<td>SOA</td>
<td>
<p>面向服务架构，也被称为服务化。每个组件基于规约、消息暴露接口，并彼此消费</p>
<p>SOA将系统划分为多个独立运作的服务，相互之间基于中立的接口协议进行交互</p>
<p>SOA的典型实现方式：</p>
<ol>
<li>WebService，也就是SOAP over HTTP。SOAP是一种基于XML的格式，因此存在不必要的数据冗余</li>
<li>RESTful WebService，常常使用JSON over HTTP</li>
</ol>
</td>
</tr>
<tr>
<td>微服务</td>
<td>
<p>可以看作是SOA架构的延伸。微服务将系统划分为较细粒度的服务，每个服务都是自治的，通常都有独立的数据存储</p>
<p>微服务允许各系统组件独立的Scale up or down</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">联用架构风格</span></div>
<p>一个软件系统几乎不可能仅仅使用一种架构风格。例如，你可能使用SOA架构风格构建服务，同时使用分层架构和面向对象架构。</p>
<p>在开发面向Web的应用系统时，联用架构风格很有意义。使用分层架构风格可以帮助你实现关注点分离，将展现层逻辑和商业逻辑、数据访问逻辑分开。由于组织机构的安全性需求，你可能需要基于3-Tier或者N-Tier架构风格，将展现层部署在周界网络（内外网之间）中。在展现层内部，你可能进一步的应用分层架构风格，典型的是MVC模式。你也可能应用SOA架构风格，让展现层和应用服务器基于消息进行通信。</p>
<p>当构建一个桌面应用时，你可能需要让客户端向某个服务器程序发送请求，这需要C/S架构风格。在服务器端，你可能采用基于组件的架构，将服务器端应用分解为相互独立的组件，让这些组件暴露适当的通信接口。在实现组件时，使用面向对象架构风格可以改善可重用性、易测试性、灵活性。</p>
<p>很多因素会影响架构风格的选择，这些因素包括你的团队开发、设计能力，以及基础设施、组织方面的约束。</p>
<div class="blog_h2"><span class="graybg">客户端/服务器架构风格</span></div>
<p>这种架构风格描述一种分布式系统，该系统由独立运行的客户端应用和服务器应用组成，服务器和客户端通过网络连接。最简单的C/S架构中服务器和客户端直接通信，也被称为2-Tier架构。</p>
<p>典型的C/S架构客户端是流行在PC上的Native应用，当今浏览器（B/S）、移动设备都可能称为客户端的载体。</p>
<div class="blog_h3"><span class="graybg">C/S架构的变体</span></div>
<p>C/S架构风格的变体包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">C/S架构变体</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>客户端-队列-客户端</td>
<td>允许两个客户端通过服务器端对象进行通信。这种架构允许客户端进行文件和消息的分发。也叫被动队列架构</td>
</tr>
<tr>
<td>点对点架构</td>
<td>客户端-队列-客户端架构的升级，允许客户端和服务器互换角色</td>
</tr>
<tr>
<td>应用服务器集</td>
<td>服务器作为程序的宿主，瘦客户端发起命令，程序在服务器上执行。典型的例子是SSH这样的Terminal服务</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">C/S架构的优势</span></div>
<ol>
<li>高安全性：数据存储在服务器上，服务器的安全条件往往远好于客户端</li>
<li>中心化数据访问：由于数据集中在服务器上，因此管理起来方便</li>
<li>易于维护：可以实现客户端对服务器的信息的无感知，这样服务器移动、维修、升级会很方便</li>
</ol>
<div class="blog_h3"><span class="graybg">C/S架构的劣势</span></div>
<ol>
<li>服务器端组件倾向于耦合，导致难以扩展、扩容</li>
<li>集中性的服务器可能存在单点故障，降低可靠性</li>
</ol>
<p>为了克服典型C/S架构的缺点，可以引入N-Tier部署架构、负载均衡、HA等</p>
<div class="blog_h3"><span class="graybg">适用场景</span></div>
<p>当满足以下条件时，可以考虑C/S架构风格：</p>
<ol>
<li>你的应用是基于服务器的，且服务器需要支持多个客户端</li>
<li>你需要创建通过浏览器访问的应用</li>
<li>你需要开发服务供其它应用程序消费</li>
<li>你希望集中化数据存储、备份、管理 </li>
</ol>
<div class="blog_h2"><span class="graybg">基于组件的架构风格</span></div>
<p>这种架构风格描述了一种系统设计和开发的手段，它关注如何把设计分解为独立的功能、逻辑组件，并且让这些组件暴露出良好定义的通信接口。通信接口的形式可以包含方法、事件、属性。基于组件的架构是<span style="background-color: #c0c0c0;">面向对象设计原则的一种高层抽象</span>。面向组件架构不关心组件之间的通信协议、共享状态等问题。</p>
<p>组件可能依赖于某种平台提供的环境才能运行，典型的平台包括组件对象模型（COM）、分布式组件模型（DCOM）、通用对象请求代理架构（CORBA）、企业JavaBean（EJB）等等。</p>
<p>依赖注入（DI）、服务定位（Service Locator）之类的设计模式可以用来管理组件之间的依赖，同时降低耦合度、提升可重用度。这类设计模式常常用于构建组合性（Composite）应用程序，以及用于实现跨应用的组件重用。</p>
<div class="blog_h3"><span class="graybg">组件设计原则</span></div>
<p>基于组件架构的组件设计原则包括：</p>
<ol>
<li>可重用性，组件应该可以在不同的场景、不同应用中重用。但是某些组件的确会针对特定项目的特定任务</li>
<li>可替换性，组件应该很容易被其它类似的组件替换</li>
<li>上下文无关，组件应该被设计以在不同的上下文中工作。注入状态数据之类的特殊信息应该被传入，而不是保存在组件内</li>
<li>可扩展性，组件应当允许扩展，以产生新的行为</li>
<li>封装性，组件暴露接口，其调用者基于接口使用其功能，不应该对外暴露任何内部状态和处理逻辑</li>
<li>独立性，组件应该尽可能少的依赖其它组件，这样它才能部署到适当的运行环境中，同时不影响其它组件或系统</li>
</ol>
<div class="blog_h3"><span class="graybg">组件的类型</span></div>
<ol>
<li>用户接口类组件：按钮、表格，这些组件通常被称为控件（Controls）</li>
<li>助手类组件/实用工具：暴露一些通用的，在其它组件中反复需要的功能</li>
<li>资源密集型组件：占用资源大，应当以按需（JIT）方式创建。很多远程、分布式组件属于此类型</li>
<li>排队组件（ Queued Components）：这些组件的接口可以通过消息队列机制，异步的调用</li>
</ol>
<div class="blog_h3"><span class="graybg">基于组件架构的优势</span></div>
<ol>
<li>易于部署：当一个新版本的兼容性组件开发完成后，你可以安全的替换老版本的组件，而不会对系统产生影响</li>
<li>开发成本：使用成熟通用的第三方组件可以降低开发的时间成本</li>
<li>易于开发：组件暴露良好定义的接口，协作开发时很容易通过Mock实现并行开发</li>
<li>可重用性：组件往往能够跨越多个系统、项目重用</li>
<li>降低技术复杂度：通过使用组件容器以及组件容器提供的服务，可以降低使用复杂度。组件容器的服务可以包括组件激活、生命周期管理、方法排队、事件机制、事务性</li>
</ol>
<div class="blog_h3"><span class="graybg">适用场景</span></div>
<p>当满足以下条件时，可以考虑基于组件的架构风格：</p>
<ol>
<li>你已经拥有合适的组件，或者可以从第三方提供商获得这样的组件</li>
<li>你的应用程序大量的执行过程式的功能，可能没有或者具有较少的数据输入</li>
<li>你希望联用基于不同编程语言实现的组件</li>
<li>你希望实现一个可拔插或者组合性的架构，希望随时能够替换某些组件</li>
</ol>
<div class="blog_h2"><span class="graybg">领域驱动架构风格</span></div>
<p>领域驱动设计（DDD）是一种<span style="background-color: #c0c0c0;">面向对象</span>的软件架构风格。它以<span style="background-color: #c0c0c0;">业务领域</span>（Business Domain）、业务领域<span style="background-color: #c0c0c0;">的元素和行为</span>以及这些<span style="background-color: #c0c0c0;">元素行为之间的关系</span>为基础和依据。</p>
<p>DDD以行业专家（而不是技术专家）的视角来识别、定义（领域）模型，并基于这些模型来组织软件系统，以实现领域的业务逻辑。</p>
<p>要应用DDD，你必须对需要建模的业务领域具有深刻的理解，或者善于获得这些领域的知识。开发团队通常需要和行业专家协同工作。架构师、开发人员、行业专家通常具有不同的知识背景，在很多上下文中他们使用不同的语言描述自己的目标、设计、需求。使用DDD时，所有这些人员使用一种语言来交互，这种语言面向业务领域，不使用技术行话。</p>
<p>基于DDD设计的软件的核心是领域模型，这些模型是上述公共领域语言的直接映射。通过分析公共语言，团队能够快速的发现软件中的Gap。创建公共领域语言不是简单的接受并应用行业专家的知识。开发团队内部的沟通问题不仅仅由于对领域语言的错误理解，也常常由于<span style="background-color: #c0c0c0;">领域语言本身具有歧义性</span>。DDD过程不仅仅需要创建公共领域语言，更要<span style="background-color: #c0c0c0;">改善、优化领域语言</span>。这种优化转而能够软件开发收益，因为模型即领域语言的直接映射。</p>
<div class="blog_h3"><span class="graybg">DDD的优势</span></div>
<ol>
<li>沟通：开发团队的所有成员使用领域模型及其定义的实体来针对业务知识进行交流。使用的语言是公共的业务领域语言，而不是技术行话</li>
<li>可扩展性：领域模型通常是模块化、灵活的。当需求变化后，扩展它们将比较容易</li>
<li>可测试性：领域模型的对象松耦合、高内聚，这意味着它们便于被测试</li>
</ol>
<div class="blog_h3"><span class="graybg">适用场景</span></div>
<p>当满足以下条件时，可以考虑DDD：</p>
<ol>
<li>业务领域很复杂，需要有效改善团队的沟通</li>
<li>你需要基于某种公共语言来向干系人解释你的设计</li>
<li>你拥有大量复杂的企业级数据场景，难以通过其它技术手段管理它们</li>
</ol>
<div class="blog_h2"><span class="graybg">分层架构风格</span></div>
<p>最简单的屏蔽复杂性的途径是引入新的层。</p>
<p>分层架构风格将软件系统中具有相关性的功能分组到一个独立的层中。这些层逐个堆叠，形成类似于栈的结构。每个层内部的功能具有相近的角色或职责。层与层之间的交互是明确定义的、松散耦合的。对应用程序进行分层可以实现关注点的强分离，从而实现灵活性、可维护性。</p>
<p>分层架构风格被描述为倒金字塔式重用。原因是每个层都对它的直接下层的职责和抽象<span style="background-color: #c0c0c0;">进行了某种聚合</span>。在严格的分层架构中，每个层<span style="background-color: #c0c0c0;">仅仅能和本层内、直接下层内的组件进行交互</span>。比较自由的分层架构则允许和非直接下层中的组件进行交互。</p>
<p>应用程序的各分层，可能部署在单个服务器中（相同Tier），也可能部署在多台服务器中（N-Tier）。</p>
<div class="blog_h3"><span class="graybg">设计原则</span></div>
<ol>
<li>抽象：分层架构抽象出系统的视图，同时提供足够的细节，让团队能够理解每个层的职责和角色，以及层与层之间的关系</li>
<li>封装：由于数据类型、方法、属性、实现细节不会在层边界暴露出去，因此在设计时不需要对它们做任何假设</li>
<li>清晰的层：每个层的功能应当很明确，上层可能向下层发起调用，也可能响应下层发布的事件，这允许上下层之间的数据流动</li>
<li>高内聚：应当明确每个层的职责边界，确保每个层仅仅包含和它的任务直接相关的功能</li>
<li>可重用：下层不能对上层产生依赖，这有利于下层的重用</li>
<li>低耦合：层与层之间的交互应当基于抽象和事件，以降低耦合</li>
</ol>
<div class="blog_h2"><span class="graybg">消息总线架构风格</span></div>
<p>消息总线架构风格描述了使用一类中间件系统的原则。这类中间件能够基于多种信道接收、发送消息。这样，以这类中间件作为交互中介的应用程序不需要知道彼此的细节，例如部署位置、使用的通信协议。</p>
<p>最常见的消息总线架构的实现通常基于消息路由器或者订阅/发布系统，通常需要使用到某种消息队列软件。消息总线提供的功能包括：</p>
<ol>
<li>面向消息的通信，所有应用以模式（Schema，结构）已知的消息进行通信</li>
<li>复杂处理逻辑，复杂的操作可以通过一组较小的操作联合而成，每个操作负责特定的任务</li>
<li>集成不同的环境，基于消息和通用的标准，你可以集成基于不同开发语言的应用，例如Java和.NET</li>
</ol>
<p>消息总线架构在很多年依赖都用于支持复杂的逻辑处理，它提供了一种可拔插的架构，你可以随时将应用接入到总线以提供新的功能，或者接入统一应用的多个实例以扩容。</p>
<p>消息总线架构的变体包括：</p>
<ol>
<li>企业服务总线（ESB）：总线和应用以服务的方式进行通信。ESB常常提供消息格式转换的功能，这样使用不兼容消息格式的应用可以通过总线进行互操作</li>
<li>因特网服务总线（ISB）：类似于ESB，但是应用程序在云端而非企业内部网络部署。ISB的核心理念是基于URI的资源定位和消息路由控制策略</li>
</ol>
<div class="blog_h3"><span class="graybg">消息总线架构的优势</span></div>
<ol>
<li>可扩展性：应用程序可以被添加、移除，现有接入的应用程序不受影响</li>
<li>低复杂性：每个应用程序仅仅需要和总线直接交互</li>
<li>灵活性：实现复杂流程的应用程序集、应用程序之间的交互方式，可以根据业务需求轻松的调整。通常只需要调整配置或路由参数</li>
<li>低耦合：应用程序仅仅暴露了和总线交互的接口，应用程序本身不会相互依赖</li>
<li>可扩容性：应用程序的多个实例可以连接到总线，以提供更大的处理能力</li>
<li>简单性：尽管总线本身引入了复杂度，但是对于每个应用程序而言，它仅仅需要和一个总线交互，而不是很多应用程序</li>
</ol>
<div class="blog_h3"><span class="graybg">适用场景</span></div>
<p>当满足以下条件时，考虑使用基于总线的架构：</p>
<ol>
<li>既有应用程序需要交互以完成特定任务</li>
<li>需要将多个任务合并为单个操作</li>
</ol>
<div class="blog_h2"><span class="graybg">N-Tier架构风格</span></div>
<p>N-Tier是一种部署架构风格，它类似于分层架构，描述了功能的分离方式。被分离的功能可能部署在不同的物理服务器上。这些功能通常使用面向组件的风格设计，使用平台特定的通信手段而非基于消息通信。</p>
<p>典型的N-Tier架构至少具有三个层。例如一个注重安全的Web财务系统，它的业务逻辑层必须部署在防火墙之后，而其展现层则必须部署在周界网络中暴露HTTP端口，客户端则可能运行在因特网上。</p>
<div class="blog_h3"><span class="graybg">N-Tier架构的优势</span></div>
<ol>
<li>可维护性：由于每个Tier相互独立，系统升级不会影响到其它Tier</li>
<li>可扩容性：可以为某个Tier添加更多的实例实现扩容</li>
<li>灵活性：每个层可以独立的管理和扩容，因而提高了灵活性</li>
<li>高可用性：某个Tier可以部署Master/Slave方式的实例，实现HA</li>
</ol>
<div class="blog_h2"><span class="graybg">面向对象架构风格</span></div>
<p>这种设计风格将应用程序或者系统划分为单独的可重用、自包含的对象，每个对象包含相关的数据和行为。面向对象的设计将系统看做一系列相互协作的对象，而非一系列例程、过程指令。对象是离散的、独立的、松耦合的，它们通过接口通信。</p>
<div class="blog_h3"><span class="graybg">OO设计原则</span></div>
<ol>
<li>抽象：简化复杂操作为一般化形式，暴露其基本特征</li>
<li>组合：对象可以相互组装，形成更大的对象。大对象可以隐藏其包含的对象或者为包含对象提供接口</li>
<li>继承：对象可以从其它对象集成，从而直接获得它的特性</li>
<li>封装：对象仅仅包含必要的方法、属性、事件，而隐藏大部分细节</li>
<li>多态：允许在子类中覆盖某些行为</li>
<li>解耦：通过定义抽象类、接口，使用者可以和对象解耦</li>
</ol>
<div class="blog_h2"><span class="graybg">面向服务架构风格</span></div>
<p>SOA风格基于一系列服务对外暴露应用程序的功能，其它的应用程序可以调用这些服务。服务是松散耦合的，因为它们使用基于标准的接口，这些<span style="background-color: #c0c0c0;">接口可以被调用、发布、发现</span>。SOA中的服务必须以基于消息的、格式（Schema）明确的暴露接口，这些接口是面<span style="background-color: #c0c0c0;">向应用级别而非面向组件的</span>，具有更粗的粒度。</p>
<div class="blog_h3"><span class="graybg">设计原则</span></div>
<ol>
<li>服务应该是自治的：所有服务应该独立的开发、维护、部署、版本化</li>
<li>服务应该是分布式的：只要目标网络支持通信协议，服务可以部署在任何网络中，不管是本地还是远程</li>
<li>服务应该是松耦合的：每个服务不对其它服务产生依赖，只要接口保持兼容，服务可以被替换</li>
<li>服务共享Schema和规约，而不是类</li>
</ol>
<div class="blog_h3"><span class="graybg">SOA的优势</span></div>
<ol>
<li> Domain alignment：重用基于标准接口的服务，可以增加商业和技术机会，降低成本</li>
<li>抽象：服务基于规范化的合约暴露接口，因此了实现细节，因此具有松耦合、抽象性</li>
<li>可发现：服务可以暴露描述性信息，让其它应用程序能够定位它并自动确定其接口</li>
<li>互操作性：由于协议、数据格式基于开放工业标准，因此服务提供者、消费者可以基于不同平台开发和部署</li>
</ol>
<div class="blog_h3"><span class="graybg">适用场景</span></div>
<p>在以下情况下，可以考虑使用SOA：</p>
<ol>
<li>需要重用某个服务</li>
<li>准备从第三方提供商购买服务</li>
<li>构建使用多个服务的单一UI应用</li>
<li>准备构建SaaS平台或者其它基于云的应用</li>
<li>必须使用平台无关的、基于消息的通信方式</li>
</ol>
<div class="blog_h2"><span class="graybg">微服务架构风格</span></div>
<p>使用一系列的微小服务来实现整体的业务流。每个微小服务通常具有专用的数据库、缓存。</p>
<p>每个微服务工作在独立的进程中，可以单独的扩容/缩容，其扩容的自由度比传统粗粒度的SOA更高。微服务常常基于容器技术部署。</p>
<p>每个微服务常常对应了一个全栈的团队，因此跨团队的沟通成本较低。</p>
<div class="blog_h1"><span class="graybg">架构方法</span></div>
<div class="blog_h2"><span class="graybg">4+1架构视图模型</span></div>
<p>4+1架构视图模型由Philippe Kruchten提出，基于多种、并发的视角来描述软件密集型系统的架构。这些视角是站在不同干系人角度的，包括最终用户、开发人员、项目管理员。</p>
<p>该模型的4各视图是逻辑架构、开发架构、进程架构、物理架构。此外，典型用例或场景作为+1视图，主要用于架构。</p>
<div class="blog_h3"><span class="graybg">开发架构视图</span></div>
<p>也叫实现视图（Implementation View），它从程序员的视角来描述系统，项目管理需要关注此视图。可以考虑使用UML的组件图、包图来描述系统的组件及其关系。</p>
<div class="blog_h3"><span class="graybg">逻辑架构视图</span></div>
<p>从最终用户的角度来描述系统，着重阐述系统包含的功能。UML的类图、状态图可以用于描述逻辑架构。</p>
<div class="blog_h3"><span class="graybg">物理架构视图</span></div>
<p>也叫部署视图（Deployment View）。从系统工程师的角度来描述系统，关注软件组件的部署拓扑，以及组件之间的物理连接。UML的部署图可以用于描述物理架构。</p>
<div class="blog_h3"><span class="graybg">进程架构视图</span></div>
<p>关注系统的运行时动态行为，描述系统的进程如何交互。该视图处理并发性、分布性、性能、可扩容性等质量属性。UML的活动图可以用于描述进程架构。</p>
<div class="blog_h3"><span class="graybg">场景视图</span></div>
<p>通过一小部分关键的业务用例来阐述系统架构。场景视图描述对象、进程之间如何交互。</p>
<div class="blog_h1"><span class="graybg">用户体验</span></div>
<p>尽管用户体验设计通常不是系统架构设计师的职责范围，但用户体验设计的部分工作的确是“架构”级别的，用户体验设计对易用性、可查找性（Findability）、可访问性等质量属性有重要影响。</p>
<p>架构设计牵涉到四个目标：<span style="background-color: #c0c0c0;">用户目标、业务目标、系统目标、开发目标的权衡</span>。用户体验设计主要关注如何满足用户目标。</p>
<p>用户体验设计可能影响架构和设计决策，例如：</p>
<ol>
<li>为了更实时的UI信息更新，可能导致使用WebSocket作为通信方式</li>
<li>为了获得更好的用户交互体验，可能需要设计特殊的控件</li>
<li>为了提升可访问性，引入国际化支持，这需要架构考虑引入新的横切面</li>
</ol>
<div class="blog_h2"><span class="graybg">什么是UX设计</span></div>
<p>所谓用户体验，就是指你操作应用程序的UI时的内心感觉。</p>
<p>用户体验设计是通过改善产品的易用性、可访问性，以提高用户满意度的过程。</p>
<div class="blog_h2"><span class="graybg">UX的重要性</span></div>
<p>最终用户了解软件的唯一途径是和软件的用户接口进行交互，用户体验设计影响用户的第一印象。从心理学角度来说，第一印象非常重要，差劲的用户体验容易让用户对软件甚至是开发商产生刻板印象。<br />对于产品来说，试用时的用户体验很大程度上影响其采购意愿。</p>
<div class="blog_h2"><span class="graybg">UX设计的内容</span></div>
<div class="blog_h3"><span class="graybg">视觉设计</span></div>
<p>也叫图形设计，视觉设计通过色彩、图片、符号等视觉元素向用户传递信息。<br />视觉设计确定用户接口的美学特性 —— 外观和感觉（Look &amp; Feel）。外观和感觉也即系统的主题（Theme），应当在架构阶段结合用户特点进行确定。</p>
<div class="blog_h3"><span class="graybg">信息架构</span></div>
<p>所谓信息架构，是指在产品或服务中正确的对信息进行结构化和组织，以提升易用性和可检索性。其工作内容包括：</p>
<ol>
<li>导航设计，选择用户接口元素的排列方式。其目标是让用户在信息架构浏览时更加轻松</li>
<li>结构/组织/标签：
<ol>
<li>结构化：化简信息量到它的基本单元，并将这些基本单元关联到一起</li>
<li>组织：以有意义的方式来分组上述基本单元。例如表单、Grid的行</li>
<li>标签：使用适当的词汇来支持导航和查找</li>
</ol>
</li>
<li>查询设计：查询是信息架构中最关键的因素，需要让用户通过检索、询问、浏览等方式获取期望的信息</li>
</ol>
<div class="blog_h3"><span class="graybg">交互设计</span></div>
<p>交互设计是用户体验设计的关键。交互设计需要关注的内容是：</p>
<ol>
<li>定义在某个上下文中最合适的交互模式</li>
<li>在研究用户知识特点、使用习惯的过程中确定用户的交互需求</li>
<li>理解哪些信息和特性对于用户最为重要</li>
<li>确定用户接口的行为，例如拖拽、选取、鼠标悬停时的行为</li>
<li>让用户接口尽可能的直观</li>
<li>在整个系统中保持交互风格的一致性</li>
</ol>
<p>在过去数年里，交互设计师的职责发生了很大的变化。曾经他们仅仅是确定UI控件及其布局，然后与开发工程师进行确认，现在，交互设计师具有更大的自由度，他们进行面向用户需求的概念性接口设计，而不是首先关心此用户接口能否被实现。</p>
<div class="blog_h2"><span class="graybg">用户体验和质量属性</span></div>
<div class="blog_h3"><span class="graybg">性能</span></div>
<p>很明显，性能对用户体验影响很大。性能的三大指标：响应时间、吞吐量、持续高速性，其中响应时间的高低直接影响用户的等待时间。</p>
<div class="blog_h3"><span class="graybg">健壮性</span></div>
<p>提升系统的健壮性有利于增强用户体验。识别用户的某些输入错误并正确处理，甚至是更正，可以很好的提升用户满意度。反之，用户可能感到困惑，例如某些用户不理解全角、半角数字的区别，在输入全角数字时引发失败会让他不解。</p>
<p>增强此质量属性对传统架构设计的反馈：设计合理的用户输入处理横切面。</p>
<div class="blog_h3"><span class="graybg">易用性</span></div>
<p>提升易用性很显然会增强用户体验。其方法包括：</p>
<ol>
<li>简单的用户接口，界面要尽量的简单，不要让人望而生畏</li>
<li>缩短响应时间，不要让用户不耐烦的等待。如果等待不可避免，一定要给予合理的提示</li>
<li>避免用户反感，过多的弹窗、移动应用中过多的推送、角标可能让用户厌烦</li>
<li>简单清晰的导航，不管导航放在哪里，都要容易找到，易于理解</li>
<li>理想的配色，符合视觉心理需求的主题和配色。适当的对比度，不要让用户看不清</li>
</ol>
<p>增强此质量属性对传统架构设计的反馈：</p>
<ol>
<li>缩短响应时间，即要求提升性能这一质量属性</li>
</ol>
<div class="blog_h3"><span class="graybg">可访问性</span></div>
<p>提升可访问性很显然会提升用户体验，没有此质量属性直接导致部分人群无法使用系统。</p>
<p>可访问性的提升主要牵涉两方面内容：</p>
<ol>
<li>国际化，用户支持不同语言背景的用户</li>
<li>障碍人群支持，包括盲人、色盲、肢体残疾等用户</li>
</ol>
<p>增强此属性对传统架构设计的反馈：</p>
<ol>
<li>要实现国际化，需要通盘考虑，使用消息占位符代替消息文本，可能需要在展现层引入新的子层</li>
<li>要支持障碍人群，可能需要开发大量功能模块，例如TTS、语音识别 </li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/introduction-to-software-architecture-design">软件架构设计基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/introduction-to-software-architecture-design/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>服务定位器模式</title>
		<link>https://blog.gmem.cc/service-locator-pattern</link>
		<comments>https://blog.gmem.cc/service-locator-pattern#comments</comments>
		<pubDate>Wed, 25 May 2011 07:40:53 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=16824</guid>
		<description><![CDATA[<p>模式定义 这种设计模式引入一个强大的抽象层，此层对获得服务的过程进行封装。该模式使用一个名为服务定位器的注册中心来进行服务的定位发现，对服务器进行请求，可以获得使用某种服务的所有必要信息。 模式结构和说明 此模式中包含三个角色：客户端、服务定位器、服务。 此模式的优势： 服务定位器可以作为简单的“运行时链接器”使用。组件可以在运行时加入到应用中，整个应用不需要重新编译，某些情况下甚至不需要重启应用 服务定位器可以在运行时动态添加、替换、移除服务，例如替换掉一个XML解析器的实现 应用和库可以完全隔离，它们仅仅通过服务定位器交互 此模式的缺点： 服务定位器对于系统的其它部分是黑盒。服务定位器发生错误将难以检测和恢复，从而导致整个系统的可靠性下降 服务定位器通常是单例的，这可能导致性能瓶颈 服务定位器可能是一个安全风险点，因为它允许外部代码的注入 服务定位器隐藏了它的类依赖，某些本应该在编译器暴露的错误以运行时错误的形式出现 增加了测试的难度，因为所有测试用例都需要和服务定位器交互，才能获得一个服务的Mock 应用举例 JNDI Java命名和目录服务可以认为是服务定位器模式的实现。此服务就相当于一个定位器，它提供了发布、获取服务的接口。 ZooKeeper ZooKeeper可以作为服务定位器使用，但这是一种变体： 定位器不是单例的，ZooKeeper通常是集群部属，避免了单点故障 ZooKeeper都用于分布式架构中，所谓服务也需要远程调用，而不是进程内基于内存地址的直接调用</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/service-locator-pattern">服务定位器模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>这种设计模式引入一个强大的抽象层，此层对获得服务的过程进行封装。该模式使用一个名为服务定位器的注册中心来进行服务的定位发现，对服务器进行请求，可以获得使用某种服务的所有必要信息。</p>
<div class="blog_h2"><span class="graybg">模式结构和说明</span></div>
<p>此模式中包含三个角色：客户端、服务定位器、服务。</p>
<p>此模式的优势：</p>
<ol>
<li>服务定位器可以作为简单的“运行时链接器”使用。组件可以在运行时加入到应用中，整个应用不需要重新编译，某些情况下甚至不需要重启应用</li>
<li>服务定位器可以在运行时动态添加、替换、移除服务，例如替换掉一个XML解析器的实现</li>
<li>应用和库可以完全隔离，它们仅仅通过服务定位器交互</li>
</ol>
<p>此模式的缺点：</p>
<ol>
<li>服务定位器对于系统的其它部分是黑盒。服务定位器发生错误将难以检测和恢复，从而导致整个系统的可靠性下降</li>
<li>服务定位器通常是单例的，这可能导致性能瓶颈</li>
<li>服务定位器可能是一个安全风险点，因为它允许外部代码的注入</li>
<li>服务定位器隐藏了它的类依赖，某些本应该在编译器暴露的错误以运行时错误的形式出现</li>
<li>增加了测试的难度，因为所有测试用例都需要和服务定位器交互，才能获得一个服务的Mock</li>
</ol>
<div class="blog_h2"><span class="graybg">应用举例</span></div>
<div class="blog_h3"><span class="graybg">JNDI</span></div>
<p>Java命名和目录服务可以认为是服务定位器模式的实现。此服务就相当于一个定位器，它提供了发布、获取服务的接口。</p>
<div class="blog_h3"><span class="graybg">ZooKeeper</span></div>
<p>ZooKeeper可以作为服务定位器使用，但这是一种变体：</p>
<ol>
<li>定位器不是单例的，ZooKeeper通常是集群部属，避免了单点故障</li>
<li>ZooKeeper都用于分布式架构中，所谓服务也需要远程调用，而不是进程内基于内存地址的直接调用</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/service-locator-pattern">服务定位器模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/service-locator-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AspectJ编程学习笔记</title>
		<link>https://blog.gmem.cc/aspectj-study-note</link>
		<comments>https://blog.gmem.cc/aspectj-study-note#comments</comments>
		<pubDate>Sun, 10 Apr 2011 02:20:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[AspectJ]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1697</guid>
		<description><![CDATA[<p>AOP基本概念 名词  含义 切面（方面，Aspect） 一个关注点的模块化，这个关注点实现可能横切（crosscutting）多个对象切面的例子包括：事务控制、日志记录、权限控制等在AspectJ中，切面表现为Java类，其源码具有AspectJ的特殊语法增强，使用ajc编译器编译 连接点（Joinpoint） 程序执行过程中明确的点，例如方法的调用开始、结束，或者特定的异常被抛出，横切（crosscutting）在连接点发生 通知（Advice） 在特定的连接点，AOP框架执行的操作 切入点（Pointcut） 切入点是这样的一种程序构造：包含一个连接点的集合、以及收集的连接点的上下文信息（例如方法参数、被执行方法和对象）。通知在这些切入点被触发。AOP框架允许开发者以多种方式定义切入点 引入（Introduction） 添加方法、字段、接口、注解等到被通知的类 目标对象（Target Object） 包含连接点的对象，也被称作被通知或被代理对象 AOP代理（AOP Proxy） AOP框架创建的对象，以目标对象为基础，织入了通知逻辑代理主要包括动态代理、字节码增强两种类型 AspectJ基础知识 AspectJ编译器能识别任何普通的Java代码，可以使用ajc编译.java文件 织入方式： <a class="read-more" href="https://blog.gmem.cc/aspectj-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/aspectj-study-note">AspectJ编程学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">AOP基本概念</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">名词</td>
<td style="text-align: center;"> 含义</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 200px;">切面（方面，Aspect）</td>
<td>一个关注点的模块化，这个关注点实现可能横切（crosscutting）多个对象切面的例子包括：事务控制、日志记录、权限控制等在AspectJ中，切面表现为Java类，其源码具有AspectJ的特殊语法增强，使用ajc编译器编译</td>
</tr>
<tr>
<td>连接点（Joinpoint）</td>
<td>程序执行过程中明确的点，例如方法的调用开始、结束，或者特定的异常被抛出，横切（crosscutting）在连接点发生</td>
</tr>
<tr>
<td>通知（Advice）</td>
<td>在特定的连接点，AOP框架执行的操作</td>
</tr>
<tr>
<td>切入点（Pointcut）</td>
<td>切入点是这样的一种程序构造：包含<strong>一个连接点的集合、以及收集的连接点的上下文信息</strong>（例如方法参数、被执行方法和对象）。通知在这些切入点被触发。AOP框架允许开发者以多种方式定义切入点</td>
</tr>
<tr>
<td>引入（Introduction）</td>
<td>添加方法、字段、接口、注解等到被通知的类</td>
</tr>
<tr>
<td>目标对象（Target Object）</td>
<td>包含连接点的对象，也被称作被通知或被代理对象</td>
</tr>
<tr>
<td>AOP代理（AOP Proxy）</td>
<td>AOP框架创建的对象，以目标对象为基础，织入了通知逻辑代理主要包括动态代理、字节码增强两种类型</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ基础知识</span></div>
<ol>
<li>AspectJ编译器能识别任何普通的Java代码，可以使用ajc编译.java文件</li>
<li>织入方式：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 100px;">源代码织入</td>
<td>织入器作为编译器的一部分，处理源代码，支持经典语法和注解语法。生成的字节码符合JVM规范，需要使用ajc代替javac</td>
</tr>
<tr>
<td>字节码织入</td>
<td>传递给织入器的是字节码。使用这种方式时，包含编译普通Java类、编译切面，织入3个步骤。</td>
</tr>
<tr>
<td>加载时织入</td>
<td>传递给织入器的是Java类字节码、切面类，以及aop.xml配置文件。</td>
</tr>
</tbody>
</table>
</li>
<li id="joinpoints-exposed-by-aspject">AspectJ暴露的连接点。<strong>只能在这些连接点上应用通知</strong>：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> </td>
<td style="width: 120px; text-align: center;">暴露的连接点</td>
<td style="text-align: center;">代码表现</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">方法</td>
<td>执行<br />Execution</td>
<td>方法体：该连接点环绕整个方法体的执行过程。这也是Spring AOP唯一支持的接入点类型。通知被织入方法体<br /> <strong>切入点语法：execution(方法签名)</strong></td>
</tr>
<tr>
<td>调用<br />Call</td>
<td>方法调用：该接入点发生在方法被调用时。通知被织入调用者代码处<br /> <strong>切入点语法：call(方法签名)</strong></td>
</tr>
<tr>
<td rowspan="2">构造器</td>
<td>执行<br />Execution</td>
<td>对象构造逻辑：该连接点环绕整个构造器的执行过程<br /> <strong>切入点语法：execution(构造器签名)</strong></td>
</tr>
<tr>
<td>调用<br />Call</td>
<td>对象构造调用<br /> <strong>切入点语法：call(构造器签名)</strong></td>
</tr>
<tr>
<td rowspan="2">字段访问</td>
<td>读访问<br />Read access</td>
<td>读取类或对象的字段<br /> <strong>切入点语法：get(字段签名)</strong></td>
</tr>
<tr>
<td>写访问<br />Read access</td>
<td>写入类或对象的字段<br /> <strong>切入点语法：set(字段签名)</strong></td>
</tr>
<tr>
<td>异常处理</td>
<td>处理器<br />Handler</td>
<td>处理异常的catch块<br /> <strong>切入点语法：handler(类型签名)</strong></td>
</tr>
<tr>
<td rowspan="3">初始化</td>
<td>类初始化<br />Class initialization</td>
<td>类加载，包括静态成员初始化部分<br /> <strong>切入点语法：staticinitialization(类型签名)</strong></td>
</tr>
<tr>
<td>对象初始化<br />Object initialization</td>
<td>在构造器中的对象初始化。包含：从一个父构造器返回，到当前构造器执行完成的部分（不包括调用父构造器的部分）<br /> <strong>切入点语法：initialization(构造器签名)</strong><br /> Spring的@Configurable即为此例 。下面的例子解释了连接点的范围：<br />
<pre class="crayon-plain-tag">public class SavingsAccount extends Account
{
    private boolean isOverdraft;
    private int     minimumBalance;
    //构造器1
    public SavingsAccount( int accountNumber, 
                                 boolean isOverdraft )
    {
        super( accountNumber );
        //对象初始化连接点：构造器1，2
        this.isOverdraft = isOverdraft;
    }
    //构造器2
    public SavingsAccount( int accountNumber )
    {
        //对象初始化连接点：构造器2
        this( accountNumber, false );
        //对象初始化连接点：构造器2
        this.minimumBalance = 25;
    }
}</pre>
</td>
</tr>
<tr>
<td>对象预初始化<br />pre-initialization</td>
<td>在构造器中的对象初始化之前：用的很少，从当前构造器调用，到父构造器调用结束为止<br /> <strong>切入点语法：preinitialization(构造器签名)</strong></td>
</tr>
<tr>
<td>通知</td>
<td>执行<br />Execution</td>
<td>通知的执行：环绕某个通知的整个执行。可以对通知进行通知<br /> <strong>切入点语法：adviceexecution()</strong></td>
</tr>
</tbody>
</table>
</li>
<li>AspectJ切入点声明语法<br />
<pre class="crayon-plain-tag">/**
 * 切入点声明语法，包含5个部分：
 * 访问标识符：例如 public
 * 关键字：pointcut
 * 切入点名称()
 * 切入点类型：例如execution、call，参考：第3点
 * 切入点签名：根据切入点类型不同，可能是类型签名、方法签名、字段签名
 */
public pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));</pre></p>
<p>AspectJ支持仅有切入点名称，而没有类型和签名的切入点，通常使用在抽象切面中。<br /> AspectJ支持匿名切入点，作为通知签名的一部分存在。<br /> AspectJ支持使用 !、 &amp;&amp; 、|| 来作为切入点的运算符</p>
</li>
<li>AspectJ通知定义语法：由<strong>通知声明、切入点定义、通知体三部分</strong>组成<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">组成</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2">整体语法形式：<br />
<pre class="crayon-plain-tag">[@AdviceName("通知的名称")]
[返回值] 通知类型([上下文信息]) [returning|throwing] [throws 通知抛出的异常] : 切入点定义([上下文信息]) {
    通知体
}</pre>
</td>
</tr>
<tr>
<td>通知声明</td>
<td>通知类型可以是：before, after 或 around，其中around需要声明返回值通知声明可以指定通知体可用的上下文信息，包括：执行对象通知声明可以指定通知体可能抛出的异常</td>
</tr>
<tr>
<td>切入点定义</td>
<td>位于冒号之后，任何匹配切入点定义的连接点上都会执行本通知</td>
</tr>
<tr>
<td style="width: 100px;">通知体</td>
<td>可以访问一系列特殊的变量或关键字，例如：proceed、thisJoinPoint、thisJoinPointStaticPart、 thisEnclosingJoinPointStaticPart</td>
</tr>
</tbody>
</table>
</li>
<li>AspectJ支持的通知类型<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">通知类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Before advice</td>
<td>在连接点执行之前执行，如果抛出异常，那么连接点不被执行</td>
</tr>
<tr>
<td>After advice</td>
<td>在连接点执行之后执行，根据连接点的执行结果，具有3个子类型：After (finally)：在连接点执行之后执行，不论其结果，通知声明语法：<strong>after()</strong>After returning：在连接点执行成功后执行，通知声明语法：<strong>after() returning(&lt;ReturnType returnObject&gt;)</strong>After throwing：在连接点执行失败后执行，通知声明语法：<strong>after() throwing(&lt;ExceptionType exceptionObject&gt;)</strong></td>
</tr>
<tr>
<td>Around advice</td>
<td>环绕连接点的执行过程，具有修改连接点执行上下文的能力，可以用来：
<ol>
<li>在连接点之前/之后添加额外的逻辑，例如性能分析</li>
<li>跳过原先逻辑还执行备选的逻辑，例如缓存。只要不调用proceed()，即跳过</li>
<li>使用try-catch包裹原先逻辑，提供异常处理策略，例如事务管理</li>
</ol>
</td>
</tr>
</tbody>
</table>
</li>
</ol>
<div class="blog_h2"><span class="graybg">AspectJ切入点签名语法详解</span></div>
<div class="blog_h3"><span class="graybg">通配符</span></div>
<p>通配符用于匹配一系列的连接点。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> *</td>
<td>匹配点号.范围内的任意数目的字符在类型签名中：表示部分包名或者部分类名在其他地方：表示部分的方法或者字段名</td>
</tr>
<tr>
<td> ..</td>
<td>匹配任意数目的字符，包括任意数量的点号在类型签名中：表示任意直接、间接的子包在方法签名中：表示任意数量的方法参数</td>
</tr>
<tr>
<td> +</td>
<td>作为类型签名的后缀，表示包含任意的子类型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">类型签名语法</span></div>
<p>这里的类型泛指：类、接口、注解、切面、基本类型<br /> <strong>基本类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Account</td>
<td>仅匹配Account类型</td>
</tr>
<tr>
<td>*Account</td>
<td>匹配任何类名以Account结尾的类型</td>
</tr>
<tr>
<td>java.*.Date</td>
<td>匹配java的<strong>直接子包</strong>中的Date</td>
</tr>
<tr>
<td>java..*</td>
<td>匹配java包及其任意子包中的任意类型</td>
</tr>
<tr>
<td>javax..*Model+</td>
<td>匹配java包及其任意子包中的任意以Model结尾的类型，及其子类型</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的类型签名示例：</strong><br /> 由于Java不支持注解的继承，所有注解名后不会出现加号。注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Secured Account</td>
<td>标注了@Secured注解的Account类型</td>
</tr>
<tr>
<td>@Sensitive *</td>
<td>标注了@Sensitive注解的任何类型</td>
</tr>
<tr>
<td>@Business* Customer+</td>
<td>Customer及其子类型，且标注了Business开头的注解</td>
</tr>
</tbody>
</table>
<p><strong>基于泛型的类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Map&lt;Long,Account&gt;</td>
<td>仅仅匹配Map&lt;Long,Account&gt;类型</td>
</tr>
<tr>
<td>*&lt;Account&gt;</td>
<td>任何把Account作为其泛型参数的类型</td>
</tr>
<tr>
<td>Collection&lt;@Sensitive *&gt;</td>
<td>Collection类型，其泛型参数类必须被标注了@Sensitive</td>
</tr>
<tr>
<td>Collection&lt;? extends Account&gt;</td>
<td>Collection类型，其泛型参数类必须是Account或者其子类</td>
</tr>
<tr>
<td>Collection&lt;? super Account&gt;</td>
<td>Collection类型，其泛型参数类必须是Account的父类</td>
</tr>
</tbody>
</table>
<p><strong>联合类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>!Collection</td>
<td>除了Collection的任何类型</td>
</tr>
<tr>
<td>Collection || Map</td>
<td>Collection或者Map类型</td>
</tr>
<tr>
<td>java.util.RandomAccess+ &amp;&amp; java.util.List+</td>
<td>任何同时实现了这两个接口的子类，例如ArrayList</td>
</tr>
<tr>
<td>!@Secured *</td>
<td>任何没有标注@Secured 的类型</td>
</tr>
<tr>
<td>@Secured @Sensitive *</td>
<td>任何同时标注了@Secured @Sensitive的类型</td>
</tr>
<tr>
<td>@(Secured || Sensitive) *</td>
<td>任何标注了@Secured @Sensitive之一的类型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">方法和构造器签名语法</span></div>
<p>构造器签名与方法类似，但是：</p>
<ol>
<li>由于构造器没有返回值，因此不得在签名中指定返回值</li>
<li>由于构造器不能为静态，故不得添加static关键字</li>
<li>由于构造器没有名称，必须使用new作为其名称部分</li>
</ol>
<p><a href="/wp-content/uploads/2011/04/method-pattern-format.jpg"><img src="https://blog.gmem.cc/wp-content/uploads/2011/04/method-pattern-format.jpg" alt="" /></a><br /> <strong>基本方法签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>public void Account.set*(*)</td>
<td>Account类中，包含单个参数、返回类型为void、以set开头的公共方法</td>
</tr>
<tr>
<td>public void Account.*()</td>
<td>Account类中，任何返回类型为void、没有参数的公共方法</td>
</tr>
<tr>
<td>public * Account.*()</td>
<td>Account类中，任何没有参数的公共方法</td>
</tr>
<tr>
<td>public * Account.*(..)</td>
<td>Account类中，包含任意参数（包括0参）的公共方法</td>
</tr>
<tr>
<td>* Account.*(..)</td>
<td><span style="color: #000000;">Account类中，包含任意参数（包括0参）的方法</span></td>
</tr>
<tr>
<td>* *.*(..)* *(..)</td>
<td>任意方法</td>
</tr>
<tr>
<td>!public * Account.*(..)</td>
<td>任何Account类的非公共方法</td>
</tr>
<tr>
<td>* *(..) throws SQLException</td>
<td>任何声明抛出SQLException的方法</td>
</tr>
<tr>
<td>* Account+.*(..)</td>
<td>Account及其子类的任何方法</td>
</tr>
<tr>
<td>* java.io.Reader.read(char[],..)</td>
<td>java.io.Reader类的read方法，第一个参数必须为char[]</td>
</tr>
<tr>
<td>* javax..*.add*Listener(EventListener+)</td>
<td>javax的任意子包中的类的方法，必须以add开头、Listener结尾，且包含唯一的参数EventListener类型及其子类型</td>
</tr>
<tr>
<td>* java.io.PrintStream.printf(String,Object...)</td>
<td>java.io.PrintStream的printf方法，第一个参数为字符串，第二个参数为Object...</td>
</tr>
<tr>
<td>Account AccountService.*(..)</td>
<td>AccountService类中任何返回AccountService的方法</td>
</tr>
<tr>
<td>public Account.new()</td>
<td>匹配Account类的0参公共构造器</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的方法签名示例：</strong></p>
<p>注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Secured * *(..)</td>
<td>任何标注了@Secured的方法</td>
</tr>
<tr>
<td>@Secured @Transactional * *(..)</td>
<td><span style="color: #000000;">任何同时标注了@</span><span style="color: #000000;">Secured、@Transactional 的方法</span></td>
</tr>
<tr>
<td>@(Secured || Transactional) * *(..)</td>
<td><span style="color: #000000;">任何标注了@</span><span style="color: #000000;">Secured或者@Transactional 的方法</span></td>
</tr>
<tr>
<td>(@Sensitive *) *(..)</td>
<td>任何返回值类型上标注了@Sensitive的方法</td>
</tr>
<tr>
<td>* (@BusinessEntity *).*(..)</td>
<td>任何标注了@BusinessEntity的类的任何方法</td>
</tr>
<tr>
<td>* *(@RequestParam (*))</td>
<td>任何包含了单个参数，且参数上标注了@RequestParam 的方法，例如：void show(@RequestParam Long id)</td>
</tr>
<tr>
<td>* *(@Sensitive *)* *((@Sensitive *))</td>
<td>任何包含了单个参数，且参数的类型上标注了@RequestParam 的方法，例如：void create(ClassAnnotatedWithSensitive obj)</td>
</tr>
<tr>
<td>* *(@RequestParam(@Sensitive *))</td>
<td><span style="color: #000000;">void create(@RequestParam </span><span style="color: #000000;">ClassAnnotatedWithSensitive </span><span style="color: #000000;">obj</span><span style="color: #000000;">)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">字段签名语法</span></div>
<p>字段签名中，使用get表示读字段，set表示写字段：<br /> <img src="https://blog.gmem.cc/wp-content/uploads/2011/04/field-pattern-format.jpg" alt="" /><br /> <strong>字段签名示例：</strong></p>
<p>注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>private double Account.balance</td>
<td>Account类的私有double类型的balance字段</td>
</tr>
<tr>
<td>* Account.*</td>
<td>Account类的任何字段</td>
</tr>
<tr>
<td>* Account+.*</td>
<td>Account类及其子类的任何字段</td>
</tr>
<tr>
<td>@Sensitive * *</td>
<td>字段上标注了@Sensitive的任何字段</td>
</tr>
<tr>
<td>(@Sensitive *) *.*</td>
<td>字段的类型上标注了@Sensitive的任何字段</td>
</tr>
<tr>
<td>* (@Sensitive *).*</td>
<td>标注了@Sensitive的类的任何字段</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ切入点的实现</span></div>
<p>AspectJ提供几种切入点指示器（designators），配合上面所述的切入点签名，即组成切入点。<br /> 切入点通过两种方式与连接点进行匹配：</p>
<ol>
<li>类型限定的切入点：直接映射到某个类型的连接点，参考<a href="#joinpoints-exposed-by-aspject">AspectJ暴露的连接点</a>一节中的“切入点语法”部分</li>
<li>无类型的切入点：根据指定的信息，来选择特定的连接点。这些信息可以是：连接点上下文的运行时类型、控制流，或者此法作用域（lexical scope）</li>
</ol>
<div class="blog_h3"><span class="graybg">类型限定的切入点</span></div>
<p>匹配：指定类型和签名的连接点<br /> Spring：仅支持execution类型<br /> 静态确定：是</p>
<div class="blog_h3"><span class="graybg">无类型的切入点</span></div>
<p>不去限定连接点的类型，还是通过若干种规则去匹配多种连接点。</p>
<p>Spring虽然仅仅支持execution一种类型的连接点，但是却支持多种方式来选择它们</p>
<p><strong>基于控制流的切入点</strong><br /> 这一类的切入点匹配 <strong>处于其他切入点的控制流之中</strong> 的连接点。这句话比较难以理解，以贷款业务为例，假设Account.debit()方法调用Account.getBalance()进行余额查询，那么，可以说：getBalance在debit的控制流之中。类似的，字段访问、异常处理，也可以处于某个切入点的控制流之中。<br /> Spring不支持这一类切入点，不能静态确定（需要运行时判断）<br /> 包含两种切入点类型：cflow、cflowbelow，后者不包括启动控制流的连接点本身</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cflow(execution(* Account.debit(..)))</td>
<td>匹配所有这样的连接点：位于Account的任何debit方法的控制流之中，包括debit方法本身</td>
</tr>
<tr>
<td>cflowbelow(execution(* Account.debit(..)))</td>
<td>匹配所有这样的连接点：位于Account的任何debit方法的控制流之中，不包括debit方法本身</td>
</tr>
<tr>
<td>cflow(execution(@Transactional * *(..)))</td>
<td>匹配所有位于配置@Transactional方法的控制流之中的任何连接点</td>
</tr>
<tr>
<td>cflow(transacted())</td>
<td>匹配切入点transacted所选择的任何连接点的控制流之中的连接点</td>
</tr>
</tbody>
</table>
<p>下面以事务管理的场景为例，来说明控制流切入点的用法：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj.cflow;
public class Account
{
    //两个方法均具有事务注解，但是，我们需要在“最外层”来启动事务
    //需要忽略内层的事务注解
    @Transactional
    public void debit()
    {
        getBalance();
    }
    @Transactional
    public void getBalance()
    {
    }
}</pre><br />
<pre class="crayon-plain-tag">public aspect TransactionAspect
{
    //匹配所有标注了@Transactional的方法执行
    private pointcut transacted() : execution(@Transactional * *(..));
    //匹配这样的@Transactional方法：不在@Transactional方法的所确定的控制流的内部
    private pointcut topLevelTransacted() : transacted() &amp;&amp; !cflowbelow(transacted());

    Object around() : topLevelTransacted() {
        System.out.println( "启动事务" );
        Object ret = proceed();
        System.out.println( "结束事务" );
        return ret;
    }
}</pre>
<p><strong>基于词法结构的切入点</strong><br /> 匹配：在指定类型、方法、构造器内的连接点<br /> Spring：支持within()，不支持withincode()<br /> 可以静态确定<br /> 包含两种切入点类型：within(类型签名)、withincode(构造器签名|方法签名)</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>within(Account)</td>
<td>Account类，及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>within(Account+)</td>
<td>Account类（或者子类），及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>within(@javax.persistence.Entity *)</td>
<td>标注了的@Entity类，及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>withincode(* Account.debit(..))</td>
<td>Account的debit方法中的任何连接点</td>
</tr>
</tbody>
</table>
<p><strong>执行对象切入点</strong><br /> 根据<strong>运行时对象类型</strong>来选择连接点。并且，可以收集执行对象——<strong>双重功能</strong>。</p>
<p>包含两种类型：this(&lt;Type or ObjectIdentifier&gt;)、target(&lt;Type or ObjectIdentifier&gt;)，前者匹配this引用为指定类型的连接点；后者匹配方法调用target为指定类型的连接点。</p>
<p>该类切入点<strong>不支持类型通配，也不支持泛型</strong>。该类切入点<strong>不会匹配任何静态方法的执行连接点</strong>。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>this(Account)</td>
<td>任何连接点，只要表达式 this instnaceof Account为真，即匹配</td>
</tr>
<tr>
<td>target(Account)</td>
<td>任何（通常是方法调用类型）连接点，只要被调用的方法所属对象 instanceof Account，即匹配</td>
</tr>
</tbody>
</table>
<p>注意区别：<br /> execution(* Account.*(..)) 匹配Account类的任何静态、成员方法<br /> execution(* *.*(..))  &amp;&amp; this(Account)：匹配Account及其子类的任何成员方法<br /> <strong>参数切入点</strong><br /> 该类切入点根据<strong>连接点的参数对象的运行时类型</strong>来匹配。并且，可以收集匹配的参数对象。<br /> Spring支持此类切入点。无法静态确定。<br /> 所谓参数对象，根据连接点不同，含义也不同：</p>
<ol>
<li>对于方法、构造器连接点，参数对象即方法参数</li>
<li>对于异常处理连接点，参数对象即被处理的异常</li>
<li>对于字段写入连接点，参数对象即被写入的值</li>
</ol>
<p>该类切入点的形式为：args(Type or ObjectIdentifier, ..)</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>args(Account, .., int)</td>
<td>任何方法或者构造器，第一个参数的运行时类型为Account或其子类型，最后一个参数的运行时类型为int</td>
</tr>
<tr>
<td>args(RemoteException)</td>
<td>任何具有单个RemoteException类型参数的连接点。</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的切入点</strong></p>
<p>注意：注解本身被标注的注解影响AOP行为：</p>
<ol>
<li>@Retention(RetentionPolicy.RUNTIME)：只有设置为运行时保留，才能在运行时进行匹配性判断</li>
<li>@<span style="color: #000000;">Inherited：只有标注了此注解，子编程元素（类、方法）才能继承父元素上标注的注解</span></li>
</ol>
<p>AspectJ支持根据类型、方法、字段所标注的注解来匹配连接点</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 350px; text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@this(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要this的类上具有TypePattern指定的注解注意：如果注解是@Inherited的，父类上有指定注解即可</td>
</tr>
<tr>
<td>@target(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要target的类上具有TypePattern指定的注解</td>
</tr>
<tr>
<td>@args(TypePattern or ObjectIdentifier, ..)</td>
<td>任何连接点，只要参数的类上具有TypePattern指定的注解</td>
</tr>
<tr>
<td>@within(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要在具有TypePattern指定的注解的类型的词法作用域内</td>
</tr>
<tr>
<td>@withincode(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要在具有TypePattern指定的注解的构造器或方法的词法作用域内</td>
</tr>
<tr>
<td>@annotation(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要目标上具有指定的注解目标的含义：
<ol>
<li>对于方法、构造器、通知执行连接点：目标为对应的程序元素</li>
<li>对于字段访问、异常处理连接点：目标为被访问的字段或者异常</li>
<li>对于初始化、预初始化连接点：目标为匹配的起始调用(first-called)构造器</li>
<li>对于静态初始化连接点：目标为被初始化的类</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p><strong>条件检查切入点</strong><br /> 此类切入点在连接点上的一些条件检查。通常和其它切入点联合使用</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>if(debug)</td>
<td>任何debug静态字段（位于切面类）设置为true的连接点</td>
</tr>
<tr>
<td>if(System.currentTimeMillis() &gt; triggerTime)</td>
<td>任何发生在triggerTime以后的连接点</td>
</tr>
<tr>
<td>if(circle.getRadius() &lt; 5)</td>
<td>任何连接点，只要circle的radius小于5。circle必须是被收集的上下文对象，或者切面的静态字段。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ切面类基本结构</span></div>
<p>本段代码示意经典的AspectJ语法：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

//切面的声明
public aspect SecurityAspect
{

    private Authenticator auth = new Authenticator();

    //切入点的声明
    pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));
    //通知声明：针对secureAccess切入点的before通知
    before() : secureAccess() {
        System.out.println( "Checking and authenticating user" );
        auth.doAuthenticate();
    }
}</pre>
<p>本段代码示意基于注解的AspectJ语法：<br /> 基于注解的语法，可以作为普通Java类编译，并且使用LTW（加载时织入）机制，在类被加载到JVM中时进行织入。</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SecurityAspect
{

    private Authenticator auth = new Authenticator();

    @Pointcut ( "execution(* cc.gmem.aj.MessageCommunicator.deliver(..))" )
    public void secureAccess()
    {
    }

    @Before ( "secureAccess()" )
    public void secure()
    {
        System.out.println( "Checking and authenticating user" );
        auth .doAuthenticate();
    }
}</pre>
<div class="blog_h2"><span class="graybg">AspectJ切面示例</span></div>
<p>环绕通知的简单例子，注意proceed()的使用：</p>
<pre class="crayon-plain-tag">public aspect SecurityAspect
{
    private Authenticator auth = new Authenticator();

    pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));

    Object around() : secureAccess() {
        long start = System.nanoTime();
        //伪关键字proceed，用于继续执行被通知的目标方法
        Object ret = proceed();
        //thisJoinPointStaticPart是通知可用变量之一，可以获取连接点的静态信息，例如方法名
        System.out.println( thisJoinPointStaticPart.getSignature() + " took " + ( end - start ) + " nanoseconds" );
        return ret;
    }
}</pre>
<p>上下文收集的简单例子：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

import java.sql.Connection;
import java.sql.SQLException;

public aspect ConnectionAspect
{
    //匹配Connection的任何抛出SQLException方法的调用
    //被调用（target）的Connection对象收集为connection变量。
    pointcut connectionOperation( Connection connection )//独立定义的切入点，必须使用这种参数声明来传递上下文信息给通知
        : call(* Connection.*(..) throws SQLException) &amp;&amp; target(connection);

    Object around( Connection connection ) : connectionOperation(connection) {//传递了上下文
        long startTime = System.nanoTime();
        Object ret = proceed();
        //thisJoinPoint是通知体可用的变量之一
        System.out.println( "Operation " + thisJoinPoint + " on " + connection + " took " + ( System.nanoTime() - startTime ) );
        return ret;
    }
}</pre>
<p>上下文收集注解的例子：</p>
<pre class="crayon-plain-tag">before(Secured secured ) : 
    //匹配任何方法执行
    execution(* *(..)) 
    //匹配连接点目标————即方法本身————上具有@secured注解的连接点
    &amp;&amp; @annotation(secured)
{
    //注解可以像普通对象一样被使用
    checkPermission(secured.permission());
}</pre>
<p>&nbsp;</p>
<p>上下文收集的另外一个例子，注意传递给目标的参数被收集，可被通知体使用：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public aspect AccountAspect
{

    boolean processOverdraft( Account account, float amount )
    {
        return true;
    }

    void around( Account account, float amount ) throws InsufficientBalanceException
    //匹配任何对Account.debit(float) throws InsufficientBalanceException方法的调用
        : call(void Account.debit(float) throws InsufficientBalanceException)
        &amp;&amp; target(account) &amp;&amp; args(amount)//收集的上下文信息作为局部变量使用
    {
        try
        {
            proceed( account, amount );//传递给目标方法的参数，可以任意修改
        }
        catch ( InsufficientBalanceException ex )
        {
            //进行透支处理
            if ( !processOverdraft( account, amount ) )
            {
                throw ex;
            }
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">进行加载时织入（LTW）</span></div>
<p>在classpath的META-INF/aop.xml中，声明需要织入的切面：</p>
<pre class="crayon-plain-tag">&lt;aspectj&gt;
    &lt;aspects&gt;
        &lt;aspect name="cc.gmem.aj.SecurityAspect"/&gt;
    &lt;/aspects&gt;
&lt;/aspectj&gt;</pre>
<p>通过指定JVM启动参数-javaagent来启用LTW：</p>
<pre class="crayon-plain-tag">-javaagent:%ASPECTJ_HOME%\lib\aspectjweaver.jar</pre>
<p>如果使用Spring，可以通过配置，避免修改JVM启动参数</p>
<div class="blog_h2"><span class="graybg">AspectJ横切构造（crosscutting construct）</span></div>
<div class="blog_h3"><span class="graybg">通用横切构造</span></div>
<p>AspectJ支持连接点、切入点、切面等构造。</p>
<div class="blog_h3"><span class="graybg">动态横切构造：修改行为</span></div>
<p>AspectJ的动态横切构造依赖于“通知”实现。通知包括Before、After、Around几种。</p>
<p><b>收集连接点上下文</b></p>
<p>连接点上下文包括两种类型：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> 连接点牵涉的对象</td>
<td>切入点：this(), target(), args()可以用于收集相应的对象可以使用类型（Type）或者对象名称（ObjectIdentifier）两种方式来收集，对于后者，必须在通知声明处声明相应的对象</td>
</tr>
<tr>
<td>连接点关联的注解</td>
<td>切入点：@this(), @target(), @args(), @annotation(),@within(), @withincode()可用于收集相应的注解</td>
</tr>
</tbody>
</table>
<p><strong>通过反射访问连接点上下文</strong></p>
<p>作为备选方式，AspectJ提供了基于反射机制的访问静态（不会随着连接点执行而变化的）、动态连接点上下文信息的方法。这种方式可以完全替换上一条进行动态上下文收集，但是具有以下缺点：</p>
<ol>
<li>性能较为低下</li>
<li>缺少静态语法检查</li>
<li>比较笨重</li>
</ol>
<p>通过AspectJ提供的通知体可用的特殊变量来使用这种反射API：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>thisJoinPoint</td>
<td>变量类型为JoinPoint。可以访问目标对象、执行对象、参数的信息，包括相应的注解信息。可以通过getStaticPart()方法访问静态信息</td>
</tr>
<tr>
<td>thisJoinPointStaticPart</td>
<td>变量类型为JoinPoint.StaticPart。可以访问源码位置、连接点类型、连接点签名。</td>
</tr>
<tr>
<td>thisEnclosingJoinPointStaticPart</td>
<td>变量类型为JoinPoint.StaticPart。用于访问包围的连接点静态上下文，所谓“包围的连接点”依连接点类型而不同，例如：
<ol>
<li>对于方法调用，包围连接点是caller的执行</li>
<li>对于异常处理，包围连接点是try-catch块所在方法</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p><strong>两种访问连接点上下文的方式的对照表</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点语法</td>
<td style="text-align: center;"> 反射方式</td>
</tr>
</thead>
<tbody>
<tr>
<td>pointcut pc(Account acc) : this(acc)</td>
<td>Account acc = (Account)thisJoinPoint.getThis()</td>
</tr>
<tr>
<td>pointcut pc(Account acc): target(acc)</td>
<td>Account acc = (Account)thisJoinPoint.getTarget()</td>
</tr>
<tr>
<td>pointcut pc(Account acc,Customer cust): args(acc, cust)</td>
<td>Object[] arguments = thisJoinPoint.getArgs();<br /> Account acc = (Account)arguments[0];<br /> Customer cust = (Customer)arguments[1];</td>
</tr>
<tr>
<td>poincut pc(Secure sec) : @this(annot);</td>
<td>Secure sec = thisJoinPoint.getThis().getClass().<br /> getAnnotation(Secure.class)</td>
</tr>
<tr>
<td>poincut pc(Secure sec) : @target(sec);</td>
<td>Secure sec = thisJoinPoint.getTarget().getClass().<br /> getAnnotation(Secure.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec, Transactional tx) : @args(sec, tx);</td>
<td>Object[] arguments = thisJoinPoint.getArgs();<br /> Secure sec = arguments[0].getClass().<br /> getAnnotations(Secure.class);<br /> Transactional tx = arguments[1].getClass().<br /> getAnnotations(Transactional.class);</td>
</tr>
<tr>
<td>poincut pc(Sensitive sens) : @annotation(sec);</td>
<td>FieldSignature sig =<br /> (FieldSignature)thisJoinPointStaticPart.getSignature();<br /> Sensitive sens = sig.getField().getAnnotation(Sensitive.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec) : @within(sec);</td>
<td>thisJoinPointStaticPart.getSignature().<br /> getDeclaringType().getAnnotation(Secure.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec) : @withincode(sec);</td>
<td>MethodSignature sig =<br /> (MethodSignature)thisJoinPointStaticPart.getSignature();<br /> Secure sec = sig.getMethod().getAnnotation(Secure.class);</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">静态横切构造：修改结构</span></div>
<p><strong>静态横切总是在动态横切之前应用</strong>。</p>
<p>静态横切构造由：<strong>跨类型声明（inter-type declaration ，ITD，亦称“引入introduction)”）</strong>和<strong>织入时声明（weave-time declarations）</strong>组成<br /> <strong>跨类型声明（引入）</strong><br /> 可以修改类、接口、或者切面的静态结构，例如增加一个字段，添加一个接口。在一个切面中，声明其他类型的结构，故曰ITD。下面是一个例子：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public aspect TrackingAspect
{

    //声明MessageCommunicator实现接口AccessTracked，效果和Java标准语法一样
    declare      parents: MessageCommunicator implements AccessTracked;

    //声明AccessTracked类型的一个字段，所有它的子类型自动获取此字段
    private long AccessTracked.lastAccessedTime;

    //声明AccessTracked类型的两个方法，所有它的子类型自动继承这些方法
    public void AccessTracked.updateLastAccessedTime()
    {
        lastAccessedTime = System.currentTimeMillis();
    }

    public long AccessTracked.getLastAccessedTime()
    {
        return lastAccessedTime;
    }

    before( AccessTracked accessTracked ) : //声明了一个通知局部变量accessTracked
        execution(* AccessTracked+.*(..)) //AccessTracked子类型的任何方法
        &amp;&amp; !execution(* AccessTracked.*(..))//除了AccessTracked本身的方法
        &amp;&amp; this(accessTracked)//this对象收集为accessTracked变量
    {
        accessTracked.updateLastAccessedTime();
    }

    private static interface AccessTracked
    {
    }
}</pre>
<p><strong>成员引入的规则</strong></p>
<ol>
<li>只能引入private、public成员，前者只能被切面类访问，后者可以被系统中所有类访问</li>
<li>如果是private的，多个切面可以引入同名的成员</li>
<li>切面可以引入字段、函数、构造器到类或者接口。特别的，可以引入方法的实现到接口——缺省实现</li>
<li>如果切面引入一个和类中同名的方法，那么，类中的那个方法被保留</li>
<li>成员引入只能针对一个类型</li>
</ol>
<p><strong>接口缺省实现的例子</strong></p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public interface Nameable
{
    public void setName( String name );

    public String getName();
    //缺省实现
    static aspect Impl
    {
        private String Nameable.name;

        public void Nameable.setName( String name )
        {
            this.name = name;
        }

        public String Nameable.getName()
        {
            return this.name;
        }
    }
}</pre>
<p><strong>使用ITD来实现Java的Mixin机制</strong></p>
<p>Mixin是一种程序构造，允许在已有类中混入特定的逻辑，Java语言没有语言级别的机制，下面的例子示意了如何使用AspectJ进行混入</p>
<pre class="crayon-plain-tag">//一个标记性接口，用于混入功能
public interface BeanSupport {}
//一个简单的POJO，需要混入：属性变更监听的功能
public class Customer implements BeanSupport
{
    private String address;
    public String getAddress()
    {
        return address;
    }
    public void setAddress( String address )
    {
        this.address = address;
    }
}</pre>
<p>通过下面的代码，即可为Customer或者任何实现了BeanSupport的类型混入属性变更的功能，而没有任何的侵入性。</p>
<pre class="crayon-plain-tag">public aspect BeanMakerAspect
{

    private PropertyChangeSupport BeanSupport.propertyChangeSupport;
    public void BeanSupport.addPropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.addPropertyChangeListener( listener );
    }
    public void BeanSupport.removePropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.removePropertyChangeListener( listener );
    }
    pointcut beanCreation( BeanSupport bean )
    //匹配BeanSupport及其子类型的初始化连接点
    : initialization(BeanSupport+.new(..)) &amp;&amp; this(bean);

    pointcut beanPropertyChange( BeanSupport bean, Object newValue )
    //匹配BeanSupport及其子类型的任何Setter方法的执行
    : execution(void BeanSupport+.set*(*))
    &amp;&amp; args(newValue) &amp;&amp; this(bean);
    //BeanSupport初始化成功后，创建一个propertyChangeSupport字段的实例
    after( BeanSupport bean ) returning : beanCreation(bean) {
        bean.propertyChangeSupport = new PropertyChangeSupport( bean );
    }
    //Setter调用后，发布相应的事件
    void around( BeanSupport bean, Object newValue )  : beanPropertyChange(bean, newValue) 
    {
        String methodName = thisJoinPointStaticPart.getSignature().getName();
        String propertyName = Introspector.decapitalize( methodName.substring( 3 ) );
        Object oldValue = getPropertyValue( bean, propertyName );
        proceed( bean, newValue );
        bean.propertyChangeSupport.firePropertyChange( propertyName, oldValue, newValue );
    }
}</pre>
<p><strong>修改类层次结构</strong></p>
<p>AspectJ提供两种需要类层次的形式：</p>
<pre class="crayon-plain-tag">declare parents : [TypePattern] implements [InterfaceList];
declare parents : [TypePattern] extends [Class or InterfaceList];
//举例：所有实体类实现BeanSupport接口
public aspect EntityBeanParticipationAspect {
    declare parents: @Entity * implements BeanSupport;
}
//hasMethod、hasField，任何包含指定模式方法或者字段的类：
declare parents: hasmethod(@Observed * *(*)) implements BeanSupport;</pre>
<p><strong>引入注解</strong></p>
<p>AspectJ支持为字段、类、方法等添加注解</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 50%; text-align: center;">语法形式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>declare @method: &lt;Method signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给AccountService的任何方法，例如：declare @method: * AccountService.*(..):<br /> @Transactional(Propagation.Required);</td>
</tr>
<tr>
<td>declare @constructor: &lt;Constructor signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给构造器，例如：declare @constructor: AccountService+.new():<br /> @ConfigurationOnly;</td>
</tr>
<tr>
<td>declare @field: &lt;Field signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给字段，例如：declare @field: * MissileInformation.*:<br /> @Classified;</td>
</tr>
<tr>
<td>declare @type: &lt;Type signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给类型，例如：declare @type: banking..* :<br /> @PrivacyControlled;</td>
</tr>
</tbody>
</table>
<p><b>简化异常处理</b></p>
<p>允许不强制捕获受查异常，声明方式如下：</p>
<pre class="crayon-plain-tag">//这里声明不需要强制捕获的异常
declare soft : &lt;ExceptionTypePattern&gt; : &lt;pointcut&gt;;</pre>
<p><strong>织入时声明——警告或者错误信息</strong></p>
<p>可以在织入时，根据一定的模式来生成错误或者警告信息，举例：</p>
<pre class="crayon-plain-tag">//当doAuthenticate方法在SecurityAspect以外调用时，生成警告
declare      warning: 
    call(void Authenticator.doAuthenticate()) //切入点：方法被调用的地方，与execution不同
    &amp;&amp; !within(SecurityAspect)  : //切入点，不在SecurityAspect内
        
        "Authentication should be performed only by SecurityAspect";</pre>
<div class="blog_h2"><span class="graybg">在Spring中使用AspectJ</span></div>
<div id="spring-aop-with-aspectj-config" class="blog_h3"><span class="graybg">使用Spring AOP，结合AspectJ风格的切面声明</span></div>
<p>这种场景下，本质上还是Spring基于JDK动态代理的AOP机制，只是借用AspectJ的语法。</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
    "&gt;
    &lt;!-- 提示Spring自动为目标对象创建代理 --&gt;
    &lt;aop:aspectj-autoproxy /&gt;
    &lt;!-- 声明普通Bean --&gt;
    &lt;bean id="messageCommunicator" class="cc.gmem.aj.MessageCommunicator" /&gt;
    &lt;!-- 声明切面Bean，注意，必须使用注解语法定义切面 --&gt;
    &lt;bean id="securityAspect" class="cc.gmem.aj.SecurityAspect" /&gt;
&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">使用spring-aspects切面库</span></div>
<p>可以基于AspectJ，而不是Spring AOP来实现：事务管理、缓存、任务调度等常用AOP场景，以避免Spring AOP的固有缺陷。<br /> 特别是使用@Configurable注解来支持非Spring Bean的依赖注入，必须基于AspectJ完成。</p>
<pre class="crayon-plain-tag">&lt;!-- 支持非受管对象（Spring Bean）的依赖注入和生命周期注解 --&gt;
&lt;context:spring-configured /&gt;
&lt;!-- 支持基于AspectJ的事务管理 --&gt;
&lt;tx:annotation-driven transaction-manager="txManager" mode="aspectj" /&gt;
&lt;!-- 支持基于AspectJ的缓存机制--&gt;
&lt;cache:annotation-driven cache-manager="cacheManager" mode="aspectj" /&gt;
&lt;!-- 支持基于AspectJ的任务调度和执行 --&gt;
&lt;task:annotation-driven  scheduler="taskScheduler" executor="taskExecutor" mode="aspectj" /&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/aspectj-study-note">AspectJ编程学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/aspectj-study-note/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>访问者模式</title>
		<link>https://blog.gmem.cc/visitor-pattern</link>
		<comments>https://blog.gmem.cc/visitor-pattern#comments</comments>
		<pubDate>Wed, 09 Feb 2011 08:09:45 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7841</guid>
		<description><![CDATA[<p>模式定义 访问者模式表示一个作用于某对象结构中各元素的操作。 它允许在不改变各元素的类的前提下定义作用于这些元素的新操作。在GOF95中，该模式被分类为行为型模式。 模式结构与说明 Visitor：访问者接口，为所有元素声明一个visit方法，用来代表为此元素添加的功能 ConcreteVisitor：具体访问者实现，实现一组完整的应用到对象结构的功能 Element：抽象元素，代表对象结构中具体的元素，定义接收访问的操作 ConcreteElement：具体元素，亦即被访问对象，通常会回调访问者的真实功能，同时开放自身的数据供访问者使用 ObjectStructure：对象结构，通常包含多个可被访问的元素，它可以遍历多个被访问的元素，也可以让访问者直接访问元素。该对象可以持有若干Element的集合，或者具有多个Element类型的字段 访问者模式的价值在于可以透明的为对象结构添加新功能，避免对这一系列的对象进行修改。 访问者模式的优点： 更好的可扩展性：允许通过创建新的访问者来增加行为，而无需改变对象结构本身 行为的代码被放在一起，统一位于访问者内，易于维护 分离无关行为：相互之间无关的行为被封装到不同的访问者中 访问者模式的缺点： 允许访问者对这些内部元素进行访问，对象结构的内部封装被打破 由于访问者对结构进行遍历，对象结构的修改变得困难，因为牵涉到所有访问者的修改 访问模式的适用场景： 如果需要对一个对象结构实施一些依赖于具体元素的操作 如果需要对一个对象结构中的各元素实施很多不同、不相关的操作 如果对象结构很少变动，但是经常需要对元素添加新功能 经典应用 <a class="read-more" href="https://blog.gmem.cc/visitor-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/visitor-pattern">访问者模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>访问者模式表示一个作用于某对象结构中<span style="background-color: #c0c0c0;">各元素</span>的操作。 它允许在<span style="background-color: #c0c0c0;">不改变各元素的类</span>的前提下定义作用于这些元素的<span style="background-color: #c0c0c0;">新操作</span>。在GOF95中，该模式被分类为行为型模式。</p>
<div class="blog_h2"><span class="graybg">模式结构与说明</span></div>
<p><img class="aligncenter size-full wp-image-7847" src="https://blog.gmem.cc/wp-content/uploads/2011/02/patterns_VisitorPattern.png" alt="patterns_VisitorPattern" width="516" height="427" /></p>
<ol>
<li>Visitor：访问者接口，为所有元素声明一个visit方法，用来代表为此元素添加的功能</li>
<li>ConcreteVisitor：具体访问者实现，实现一组完整的应用到对象结构的功能</li>
<li>Element：抽象元素，代表对象结构中具体的元素，定义接收访问的操作</li>
<li>ConcreteElement：具体元素，亦即被访问对象，通常会<span style="background-color: #c0c0c0;">回调访问者的真实功能</span>，同时开放自身的数据供访问者使用</li>
<li>ObjectStructure：对象结构，通常包含多个可被访问的元素，它可以遍历多个被访问的元素，也可以让访问者直接访问元素。该对象可以持有若干Element的集合，或者具有多个Element类型的字段</li>
</ol>
<p>访问者模式的价值在于可以透明的为对象结构添加新功能，避免对这一系列的对象进行修改。</p>
<p>访问者模式的优点：</p>
<ol>
<li>更好的可扩展性：允许通过创建新的访问者来增加行为，而无需改变对象结构本身</li>
<li>行为的代码被放在一起，统一位于访问者内，易于维护</li>
<li>分离无关行为：相互之间无关的行为被封装到不同的访问者中</li>
</ol>
<p>访问者模式的缺点：</p>
<ol>
<li>允许访问者对这些内部元素进行访问，对象结构的内部封装被打破</li>
<li>由于访问者对结构进行遍历，对象结构的修改变得困难，因为牵涉到所有访问者的修改</li>
</ol>
<p>访问模式的适用场景：</p>
<ol>
<li>如果需要对一个对象结构实施一些依赖于具体元素的操作</li>
<li>如果需要对一个对象结构中的各元素实施很多不同、不相关的操作</li>
<li>如果对象结构很少变动，但是经常需要对元素添加新功能</li>
</ol>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">java.lang.model包中的访问者模式</span></div>
<p><img class="aligncenter size-full wp-image-7854" src="https://blog.gmem.cc/wp-content/uploads/2011/02/patterns_VisitorPattern_Java_Annotation.png" alt="patterns_VisitorPattern_Java_Annotation" width="672" height="626" /></p>
<div class="blog_h3"><span class="graybg">java.nio.file包中的访问者模式</span></div>
<p><img class="aligncenter size-full wp-image-7855" src="https://blog.gmem.cc/wp-content/uploads/2011/02/patterns_VisitorPattern_NIO.png" alt="patterns_VisitorPattern_NIO" width="608" height="268" /></p>
<p>这是一个变形的访问者模式，元素：文件、目录等没有统一的接口，也不能accept()访问者，实质上，对访问者的回调是由FileTreeWalker.walk()方法手动判断和完成的。FileTreeWalker相当于ObjectStructure角色。</p>
<div class="blog_h3"><span class="graybg">ASM框架</span></div>
<p>ASM是Java的一个字节码处理框架，它定义的访问者模式允许访问类中的各种元素：类本身、方法、字段、注解等：</p>
<p><img class="aligncenter size-full wp-image-7857" src="https://blog.gmem.cc/wp-content/uploads/2011/02/patterns_VisitorPattern_ASM.png" alt="patterns_VisitorPattern_ASM" width="95%" /></p>
<div class="blog_h3"><span class="graybg">Dom4j中的访问者模式</span></div>
<p>Dom4j是一个XML解析框架，它实现了非常典型的访问者模式：Visitor接口定义了访问各种XML节点的操作，包括文档、元素、属性、文本等。Node接口相当于Element角色，它为所有类型的XML节点定义了accept()方法。Document接口的实现AbstractDocument则充当了ObjectStructure角色：</p>
<pre class="crayon-plain-tag">public abstract class AbstractDocument extends AbstractBranch implements Document {
    public void accept(Visitor visitor) {
        visitor.visit(this); //先让访问者访问自己
        DocumentType docType = getDocType();
        if (docType != null) {
            visitor.visit(docType);  //访问文档类型
        }
        List content = content();
        //遍历，访问所有子节点
        if (content != null) {
            for (Iterator iter = content.iterator(); iter.hasNext();) {
                Object object = iter.next();
                if (object instanceof String) {
                    Text text = getDocumentFactory()
                            .createText((String) object);
                    visitor.visit(text);  //访问文本
                } else {
                    //如果是节点，那么调用相应的accept，递归的处理整个XML文档
                    Node node = (Node) object;
                    node.accept(visitor);
                }
            }
        }
    }
}</pre>
<p> Dom4j的访问者模式类图如下：</p>
<p><img class="aligncenter size-full wp-image-7859" src="https://blog.gmem.cc/wp-content/uploads/2011/02/patterns_VisitorPattern_Dom4j.png" alt="patterns_VisitorPattern_Dom4j" width="95%" /></p>
<div class="blog_h2"><span class="graybg">模式演变</span></div>
<ol>
<li>与缺省适配模式联用：如果元素种类特别多，则应当引入一个实现了缺省适配的访问者抽象类，提供缺省适配，这样可以减少具体访问者的代码量</li>
<li>与组合模式联用：将Component中定义的业务方法抽取出来，改用accept()方法，然后将那些业务方法的逻辑转移到访问者中</li>
<li>退化：取消Element接口。这种情况下，各元素没有一致的接口，不能accept()访问者。那么把哪些元素传递给访问者对应方法的职责就落到ObjectStructure头上了</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/visitor-pattern">访问者模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/visitor-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>不变模式</title>
		<link>https://blog.gmem.cc/immutable-pattern</link>
		<comments>https://blog.gmem.cc/immutable-pattern#comments</comments>
		<pubDate>Wed, 19 May 2010 07:39:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7834</guid>
		<description><![CDATA[<p>模式定义 对象创建后，即不允许改变其内部状态。  模式结构与说明 不变模式不需要类图来描述。 不变模式分为两者形式： 弱不变模式：当前类的实例的状态是不会变化的，但是其子类的实例状态则可能变化 强不变模式：不但当前类的实例状态不会变化，子类实例的状态也不会变化 不变模式的优点： 允许多个Client共享一个对象 允许安全性浅拷贝 允许避免并发访问时的同步开销  经典应用 Java基础类型中的不变模式 Java的字符串和所有数字类型，都实现了不变模式，不提供任何操作来修改对象的内部状态： 一些“貌似”修改对状态的方法，实质上是返回了一个新的对象，例如String.trim()、BigDecimal.add()： [crayon-69d7849eb7ad3503333585/] C++语言级别的不变模式支持 在C++中，可以使用const关键字强制施加不变性约束： [crayon-69d7849eb7ad7321489341/] 模式演变 与享元模式联用：由于不变模式的对象的内部状态不会改变，因而它可以被安全的享元</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/immutable-pattern">不变模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>对象创建后，即不允许改变其内部状态。 </p>
<div class="blog_h2"><span class="graybg">模式结构与说明</span></div>
<p>不变模式不需要类图来描述。</p>
<p>不变模式分为两者形式：</p>
<ol>
<li>弱不变模式：当前类的实例的状态是不会变化的，但是其子类的实例状态则可能变化</li>
<li>强不变模式：不但当前类的实例状态不会变化，子类实例的状态也不会变化</li>
</ol>
<p>不变模式的优点：</p>
<ol>
<li>允许多个Client共享一个对象</li>
<li>允许安全性浅拷贝</li>
<li>允许避免并发访问时的同步开销 </li>
</ol>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">Java基础类型中的不变模式</span></div>
<p>Java的字符串和所有数字类型，都实现了不变模式，不提供任何操作来修改对象的内部状态：</p>
<p><img class="aligncenter size-full wp-image-7838" src="https://blog.gmem.cc/wp-content/uploads/2010/05/patterns_ImmutablePattern_Java_1.png" alt="patterns_ImmutablePattern_Java_1" width="614" height="420" /></p>
<p>一些“貌似”修改对状态的方法，实质上是返回了一个新的对象，例如String.trim()、BigDecimal.add()：</p>
<pre class="crayon-plain-tag">public BigDecimal add(BigDecimal augend) {
    ...
    return (fst.signum == snd.signum) ? 
        new BigDecimal(sum, INFLATED, rscale, 0) :
        new BigDecimal(sum, compactValFor(sum), rscale, 0);
}</pre>
<div class="blog_h3"><span class="graybg">C++语言级别的不变模式支持</span></div>
<p>在C++中，可以使用const关键字强制施加不变性约束：</p>
<pre class="crayon-plain-tag">class Immutable
{
    private:
        int field;

    public:
        Immutable( int f ) :
                field( f )
        {
        }
        void op()
        {
        }
        //声明为常方法，即不会改变对象的任何内部状态
        void constOp() const
        {
            op(); //无法通过编译，常方法不得调用非常方法
            this-&gt;field = 1; //无法通过编译，常方法不得对字段进行写操作
            Immutable::staticField = 1; //静态字段的修改则不在限制
        }
};</pre>
<div class="blog_h2"><span class="graybg">模式演变</span></div>
<ol>
<li>与享元模式联用：由于不变模式的对象的内部状态不会改变，因而它可以被安全的享元</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/immutable-pattern">不变模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/immutable-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>原型模式</title>
		<link>https://blog.gmem.cc/prototype-pattern</link>
		<comments>https://blog.gmem.cc/prototype-pattern#comments</comments>
		<pubDate>Tue, 12 Jan 2010 06:40:03 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7825</guid>
		<description><![CDATA[<p>模式定义 使用原型实例指定创建对象的种类，并通过拷贝这些原型对象创建新的对象。 在GOF95中原型模式被分类为创建型模式。 模式结构与说明 Prototype：声明一个克隆自身的接口，用来约束想要克隆自己的类，让它们都要实现这里定义的克隆方法 ConcretePrototype：实现Prototype接口的类，真正实现了对自身的克隆功能 Client：客户端，首先获取原型实例对象，然后通过克隆原型来创建新的实例 原型模式优点： 向客户隐藏创建新实例的复杂性 在某些情况下，复制对象比创建新对象更有效  经典应用 Java的克隆机制 在Java语言中，所有类型的根类java.lang.Object支持克隆方法： [crayon-69d7849eb7cd3053053564/] 需要支持克隆功能的类型必须首先标记接口： [crayon-69d7849eb7cd7060199428/] 可以看到，在JDK中已经内置的原型模式的支持。  JavaScript的原型机制 JavaScript从语言层次上支持了原型模式，实际上，JavaScript的继承机制就是基于原型的继承，而不是基于类的继承。 设置一个构造函数的prototype属性为一个对象，这个对象就成为此构造函数创建出的所有对象的原型： [crayon-69d7849eb7cda088548162/] 模式演变 <a class="read-more" href="https://blog.gmem.cc/prototype-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/prototype-pattern">原型模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>使用原型实例指定创建对象的种类，并通过拷贝这些原型对象创建新的对象。 在GOF95中原型模式被分类为创建型模式。</p>
<div class="blog_h2"><span class="graybg">模式结构与说明</span></div>
<p><img class="aligncenter size-full wp-image-7828" src="https://blog.gmem.cc/wp-content/uploads/2010/01/patterns_PrototypePattern.png" alt="patterns_PrototypePattern" width="499" height="288" /></p>
<ol>
<li>Prototype：声明一个克隆自身的接口，用来约束想要克隆自己的类，让它们都要实现这里定义的克隆方法</li>
<li>ConcretePrototype：实现Prototype接口的类，真正实现了对自身的克隆功能</li>
<li>Client：客户端，首先获取原型实例对象，然后通过克隆原型来创建新的实例</li>
</ol>
<p>原型模式优点：</p>
<ol>
<li>向客户隐藏创建新实例的复杂性</li>
<li>在某些情况下，复制对象比创建新对象更有效 </li>
</ol>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">Java的克隆机制</span></div>
<p>在Java语言中，所有类型的根类java.lang.Object支持克隆方法：</p>
<pre class="crayon-plain-tag">public class Object
{
    /**
     * 返回当前对象的一个拷贝，拷贝的含义依赖于对象的真实类型，但是一般意味着：
     * x.clone() != x &amp;&amp; x.clone().getClass() == x.getClass() &amp;&amp; x.equals(x.clone())
     * 
     * 根据惯例，该方法返回值应当和当前对象是相互独立的，这意味着可能需要将super.clone()
     * 返回的对象进行一定的处理，这个修改往往意味着对可变（mutable）对象进行深拷贝，如果当前
     * 类型只包含基本类型，则不需要这一处理过程
     * 
     * 当前实现实际上实现了浅拷贝行为：
     * 1、如果当前对象没有实现Cloneable，抛出CloneNotSupportedException
     *    注意所有数组类型被认为实现了Cloneable
     * 2、否则，创建一个新实例，并把当前对象的所有字段赋值给这个实例
     *    注意任何字段本身都不会被clone()
     */
    protected native Object clone() throws CloneNotSupportedException;
}</pre>
<p>需要支持克隆功能的类型必须首先标记接口：</p>
<pre class="crayon-plain-tag">public interface Cloneable { }</pre>
<p>可以看到，在JDK中已经内置的原型模式的支持。 </p>
<div class="blog_h3"><span class="graybg">JavaScript的原型机制</span></div>
<p>JavaScript从语言层次上支持了原型模式，实际上，JavaScript的继承机制就是基于原型的继承，而不是基于类的继承。</p>
<p>设置一个构造函数的prototype属性为一个对象，这个对象就成为此构造函数创建出的所有对象的原型：</p>
<pre class="crayon-plain-tag">var protoObject = { x: 1, y: 2, z: [1, 2] };
var ConstructFunc = function(){};

var obj1 = new ConstructFunc();
console.log(obj1.z); // 从原型继承得到的属性 [1, 2]
obj1.z.push(3);

var obj2 = new ConstructFunc();
console.log(obj2.z); //[1, 2, 3] 原型对象的属性是浅复制的</pre>
<div class="blog_h2"><span class="graybg">模式演变</span></div>
<ol>
<li>深克隆与浅克隆：在使用原型模式的时候，涉及到两种克隆方式。深克隆会递归的克隆所有字段；浅克隆只是拷贝复杂类型的引用地址</li>
<li>与工厂模式的比较：两者都用来创建对象实例，但是原型模式强调使用克隆的方式来创建</li>
<li>与生成器模式联用：原型模式可以用来创建某些部件</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/prototype-pattern">原型模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/prototype-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>备忘录模式</title>
		<link>https://blog.gmem.cc/memonto-pattern</link>
		<comments>https://blog.gmem.cc/memonto-pattern#comments</comments>
		<pubDate>Fri, 19 Jun 2009 01:29:44 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7811</guid>
		<description><![CDATA[<p>模式定义 在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这一状态。以后可以将对象恢复到原先保存的状态。备忘录模式在GOF95中被分类为行为型模式。  模式结构与说明 Memento：备忘录接口，用来存放原发器对象某个瞬间的内部状态，该接口通常是空接口或者窄接口，不会声明任何获取原发器内部状态的方法 MementoImpl：备忘录实现类，为了不破坏封装性，备忘录的实现类往往作为原发器私有内部类实现，备忘录的实例应该由原发器自己生成 Originator：原发器，其状态需要保存和恢复，提供这两个接口 CareTaker：备忘录管理者，负责保存和取回备忘录 备忘录模式的优点： 将被存储的状态放在外面，不和关键对象混在一起，提高了后者内聚性 保持了关键对象的数据封装 提供了容易实现的恢复能力 备忘录的缺点： 存储和恢复状态的过程可能比较耗时 如果存储的备忘录数量很大，可能导致不必要的开销 备忘录的适用场景： 需要持久化对象状态，以便未来恢复时 经典应用 Java的串行化机制 串行化机制可以认为是一种备忘录模式，任何实现java.io.Serializable接口的类都相当于Originator角色： [crayon-69d7849eb7f0d754448077/] 而Memento角色则由使用二进制数据流承担，没有对应的Java对象。CareTaker角色由java.io包中一些类承担： [crayon-69d7849eb7f11330078152/] <a class="read-more" href="https://blog.gmem.cc/memonto-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/memonto-pattern">备忘录模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这一状态。以后可以将对象恢复到原先保存的状态。备忘录模式在GOF95中被分类为行为型模式。 </p>
<div class="blog_h2"><span class="graybg">模式结构与说明</span></div>
<p><img class="aligncenter size-full wp-image-7819" src="https://blog.gmem.cc/wp-content/uploads/2009/06/patterns_MementoPattern.png" alt="patterns_MementoPattern" width="564" height="330" /></p>
<ol>
<li>Memento：备忘录接口，用来存放原发器对象某个瞬间的内部状态，该接口通常是空接口或者窄接口，不会声明任何获取原发器内部状态的方法</li>
<li>MementoImpl：备忘录实现类，为了不破坏封装性，备忘录的实现类往往作为原发器私有内部类实现，备忘录的实例应该由原发器自己生成</li>
<li>Originator：原发器，其状态需要保存和恢复，提供这两个接口</li>
<li>CareTaker：备忘录管理者，负责保存和取回备忘录</li>
</ol>
<p>备忘录模式的优点：</p>
<ol>
<li>将被存储的状态放在外面，不和关键对象混在一起，提高了后者内聚性</li>
<li>保持了关键对象的数据封装</li>
<li>提供了容易实现的恢复能力</li>
</ol>
<p>备忘录的缺点：</p>
<ol>
<li>存储和恢复状态的过程可能比较耗时</li>
<li>如果存储的备忘录数量很大，可能导致不必要的开销</li>
</ol>
<p>备忘录的适用场景：</p>
<ol>
<li>需要持久化对象状态，以便未来恢复时</li>
</ol>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">Java的串行化机制</span></div>
<p>串行化机制可以认为是一种备忘录模式，任何实现java.io.Serializable接口的类都相当于Originator角色：</p>
<pre class="crayon-plain-tag">import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableObject implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;
    private int               field0;
    private String            field1;
    //transient关键字用来声明不需要保存的状态
    private transient int     field2;
    //特殊签名的方法用来定制备忘录生成的具体规则，承担了createMemento()、restoreMemento()的职责
    private void writeObject( ObjectOutputStream out ) throws IOException {
        out.writeInt( field0 );
        out.writeUTF( field1 );
    }
    private void readObject( ObjectInputStream in ) throws IOException {
        field0 = in.readInt();
        field1 = in.readUTF();
    }
}</pre>
<p>而Memento角色则由使用二进制数据流承担，没有对应的Java对象。CareTaker角色由java.io包中一些类承担：</p>
<pre class="crayon-plain-tag">SerializableObject so = new SerializableObject();
ObjectOutputStream output = new ObjectOutputStream( new FileOutputStream( "SerializableObject" ) );
output.writeObject( so );
output.close();

ObjectInputStream input = new ObjectInputStream( new FileInputStream( "SerializableObject" ) );
so = (SerializableObject) input.readObject();
input.close();</pre>
<div class="blog_h2"><span class="graybg">模式演变</span></div>
<ol>
<li>增量备忘录：如果原发器的状态很多，而修改的状态很少，可以使用增量方式存储备忘录</li>
<li>与原型模式联用：如果原发器的大部分字段都是需要保存的，那么原发器可以实现原型模式，保存备忘录的时候直接保存原发器的克隆</li>
<li>离线存储：标准的备忘录模式没有考虑到离线存储的问题，实际上CareTaker可以将备忘录保存为XML、JSON、Java串行化格式，甚至保存到数据库</li>
<li>与命令模式联用：命令模式支持撤销，这就涉及到对象状态恢复的问题。可以在执行命令时，将出现变化的状态原值保存到备忘录中，在撤销时，取回备忘录恢复</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/memonto-pattern">备忘录模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/memonto-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>中介者模式</title>
		<link>https://blog.gmem.cc/mediator-pattern</link>
		<comments>https://blog.gmem.cc/mediator-pattern#comments</comments>
		<pubDate>Tue, 05 Aug 2008 03:52:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7799</guid>
		<description><![CDATA[<p>模式定义 也译为“调停者模式”，该模式用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式的相互引用，从而使其松散耦合，而且可以独立的改变它们之间的交互。在GOF95中中介者模式被分类为行为型模式。  模式结构与说明 Mediator：中介者接口，在里面定义各同事之间交互需要的方法。可以是公共的通信方法，比如changed，大家都用；也可以是小范围的交互方法 ConcreteMediator：具体的中介者实现，持有所有同事的引用，并协调各同事之间的交互关系 Colleague：同事抽象类，主要负责约束同事的类型，不实现同事的公共功能，比如每个同事都应该知道中介者对象（getMediator） ConcreteColleague：具体同事类，实现自己的业务，它们直接绝不存在任何依赖关系。它们如果需要与其它同事通信，就持有中介者的引用 中介者模式很好的体现了迪米特法则，它可以彻底解耦网状的对象关系。 中介者模式的优点： 通过将对象彼此解耦，可以增加对象的复用度 通过将控制逻辑集中，可以简化系统维护 可以让对象之间传递的消息变得简单而且大幅减少 将多对多关系改变为一对多 中介者模式的缺点： 如果设计不当，中介者对象本身会变得过于复杂 中介者模式的适用场景： 如果一组对象之间的通信方式过于复杂，导致结构混乱的互相依赖 用来协调相关的GUI组件 经典应用 Java反射中的中介者模式 [crayon-69d7849eb80ff240677235/] Method类实现了一种变形的中介者模式，Method相当于Mediator角色，调用invoke()的Client代码相当于Colleague角色，invoke()的obj参数也相当于Colleague角色。虽然Client持有了需要交互的Colleague的引用，但是它不需要知道Colleague类型的任何信息。 <a class="read-more" href="https://blog.gmem.cc/mediator-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mediator-pattern">中介者模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式定义</span></div>
<p>也译为“调停者模式”，该模式用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式的相互引用，从而使其松散耦合，而且可以独立的改变它们之间的交互。在GOF95中中介者模式被分类为行为型模式。 </p>
<div class="blog_h2"><span class="graybg">模式结构与说明</span></div>
<p><img class="aligncenter size-full wp-image-7804" src="https://blog.gmem.cc/wp-content/uploads/2008/08/patterns_MediatorPattern.png" alt="patterns_MediatorPattern" width="551" height="445" /></p>
<ol>
<li>Mediator：中介者接口，在里面定义<span style="background-color: #c0c0c0;">各同事之间交互需要的方法</span>。可以是公共的通信方法，比如changed，大家都用；也可以是小范围的交互方法</li>
<li>ConcreteMediator：具体的中介者实现，持有所有同事的引用，并协调各同事之间的交互关系</li>
<li>Colleague：同事抽象类，主要负责约束同事的类型，不实现同事的公共功能，比如每个同事都应该知道中介者对象（getMediator）</li>
<li>ConcreteColleague：具体同事类，实现自己的业务，它们直接绝不存在任何依赖关系。它们如果需要与其它同事通信，就持有中介者的引用</li>
</ol>
<p>中介者模式很好的体现了迪米特法则，它可以彻底解耦网状的对象关系。</p>
<p>中介者模式的优点：</p>
<ol>
<li>通过将对象彼此解耦，可以增加对象的复用度</li>
<li>通过将控制逻辑集中，可以简化系统维护</li>
<li>可以让对象之间传递的消息变得简单而且大幅减少</li>
<li>将多对多关系改变为一对多</li>
</ol>
<p>中介者模式的缺点：</p>
<ol>
<li>如果设计不当，中介者对象本身会变得过于复杂</li>
</ol>
<p>中介者模式的适用场景：</p>
<ol>
<li>如果一组对象之间的通信方式过于复杂，导致结构混乱的互相依赖</li>
<li>用来协调相关的GUI组件</li>
</ol>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">Java反射中的中介者模式</span></div>
<pre class="crayon-plain-tag">public class Method{
    public Object invoke(Object obj, Object... args)
	throws IllegalAccessException, IllegalArgumentException;
}</pre>
<p>Method类实现了一种变形的中介者模式，Method相当于Mediator角色，调用invoke()的Client代码相当于Colleague角色，invoke()的obj参数也相当于Colleague角色。虽然Client持有了需要交互的Colleague的引用，但是它不需要知道Colleague类型的任何信息。</p>
<div class="blog_h2"><span class="graybg">模式演变</span></div>
<ol>
<li>退化：取消同事的公共抽象类。如果不同的同事类没有什么公共功能，仅仅一个getMediator()往往不值得让它们拥有公共父类</li>
<li>退化：具体中介者作为单例。如果只需要一个中介者对，那么可以用单例实现</li>
<li>退化：取消中介者接口。如果不可能有切换中介者的需求，可以取消此接口 </li>
<li>与外观模式的比较：两者都很好的提现迪米特法则。但是外观模式对系统内部进行封装，对Client提供单向的接口；而中介者模式则来协调系统内部的多个组件，并且通信是双向的</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mediator-pattern">中介者模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mediator-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
