存档

‘软件设计’ 分类的存档

转载:重构代码的7个阶段

2011年9月30日 admin 没有评论

原文地址: http://coolshell.cn/articles/5201.html 

你曾去想重构一个很老的模块,但是你只看了一眼你就恶心极了。文档,奇怪的函数和类的命名,等等,整个模块就像一个带着脚镣的衣衫褴褛的人,虽然能走,但是其已经让人感到很不舒服。面对这种情况,真正的程序员会是不会认输的,他们会接受挑战认真分析,那怕重写也在所不惜。最终那个模块会被他们重构,就像以前和大家介绍过的那些令人销魂的编程方式中的屠宰式编程一样。下面是重构代码的几个阶段,文章来自:The 7 stages of refactoring,下面的翻译只是意译。

第一阶段 – 绝望

在你开始去查看你想要重构的模块的,你会觉得好像很简单,这里需要改一个类,那里需要改两到三个函数,重写几个函数,看上去没什么大不了的,一两天就搞定了。于是你着手开始重构,然后当你调整重构了一些代码,比如改了一些命名,修理了一些逻辑,渐渐地,你会发现这个怪物原来体型这么大,你会看到与代码不符甚至含糊不清的注释,完全摸不着头脑的数据结构,还有一些看似不需要方法被调了几次,你还会发现无法搞清一个函数调用链上的逻辑。你感到这个事可能一周都搞不定,你开始绝望了。

第二阶段 – 找最简单的做

你承认你要重构的这个模块就是一个可怕的怪物,不是一两下就可以搞定的,于是你开始着干一些简单的事,比如重新命名一下几个函数,移除一些代码的阻碍,产生几个常量来消除magic number,等等,你知道这样做至少不会让代码变得更糟糕。

第三阶段 – 再次绝望

但是接下来的事会让你再次撞墙。你会发现那些代码的瑕疵是些不痛不痒的事,改正这些事完全于事无补,你应该要做的事就是重写所有的东西。但是你却没有时间这么干,而这些代码剪不乱理还乱,耦合得太多,让你再一次绝望。所以,你只能部分重写那些不会花太多时间的部分,这样至少可以让这些老的代码能被更多的重用。虽然不完美,但是至少可以试试。

第四阶段 – 开始乐观

在你试着部分重构这个模块几天之后,随着重构了几个单元后,虽然你发现改善代码的进度太慢了,但此时,你已知道代码应该要被改成什么样,你在痛苦之后也锁定了那些那修改的类。是的,虽然你的时间预算已经超支,虽然要干的事比较多,但你还是充满希望,觉得那是值得的。你胸中的那团火又被点燃了。

第五阶段  – 快速了结

在这个时候,你发现你已花了太多的时间,而情况越来越复杂,你感到你所面对的情况越来越让你越到不安,你明白你自己已经陷入了困境。你原本以为只需要一次简单的重构,然而现在你要面对的是重写所有的东西。你开始意识到原因是因为你是一个完美主义者,你想让代码变得完美。于是你开始在怠慢你文档,并想找到一个捷径来重写老的代码,你开始采用一些简单而粗暴,快速而有点肮脏的方法。虽然不是很完美,但你就是这样去做了。然后,你开始运行测试做UT,发现 UT报告上全是红色,几乎全都失败了,你恐慌了,于是快速地fix代码,然后让UT 能工作。此时,你拍拍自己胸口,说到,没问题 ,于是就把代码提交了。

第六阶段 – 修改大量的Bug

你的重写并不完美,虽然其过了测试,但是那些UT测试对于你的新的代码有点不太合适,虽然他们都没有报错,但是他们测试得范围太小了,没有覆盖到所有的情况和边界。所以,在这以后,你还需要几周或是更长的时间不得不来修正越来越多的bug,这使得你的设计和代码在每一次quick-fix后就变得越来越难看。此时,代码已经不像你所期望的那样完美了,但你依然觉得他还是比一开始要好一些。这个阶段可能历经几个月。

第七阶段  – 觉悟

经过了6个月,你重写的模块又出了一个比较严重的bug。这让你重构的那个模块变得更难堪。你发现出的这个问题是和当初的设计不一致,你还发现被你重构掉的那段老的代码并不是当初看上去的那么坏,那段老的代码确实考虑到了一些你未曾考虑到的事情。这个时候,你团队里有人站出来说这个模块应该被重构或是重写,而你却不动声色地一言不发,并希望那个站出来的人能在几个月后能觉悟起来。

——————

不知道这是不是你的经历,我经历过很多次这样的事。对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代码很不合口味。当然,那些从来没有写过代码的敏捷咨询师一定会说用TDD或是UT可以让你的重构更有效也更容易,因为这样会让他们显得更我价值,但我想告诉你,这种脱离实际的说法很不负责任,这就好比说—— 我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。

分类: 软件设计 标签:

离线并发模式

2011年6月5日 admin 没有评论

什么是离线并发

  1. 用户A将数据R(C1,C2)读取到A的浏览器中。
  2. 用户B将数据R(C1,C2)读取到B的浏览器中。
  3. 用户A在浏览器上将数据修改为R(C1’,C2),同时更新到数据库。
  4. 用户B在浏览器上将数据修改为R(C1,C2’),同时更新到数据库。
    上述过程存在两个问题,第一,第4步B在修改数据的时候数据库中的数据和B的浏览器中数据已经不一致了;第二,如果程序按照哪个字段变化在数据库中更新哪 个字段的方式处理的话,那么经过上述四步修改,数据库中R行的内容是(C1’,C2’),这和A或者B的想法都不同(A认为是(C1’,C2),B认为是 (C1,C2’))。上述过程中A对数据库的修改过程或者B对数据库的修改过程,都是无法根据数据库的最新内容做修改,所以称为为离线。A和B同时对记录R进行修改叫离线。以上的环境叫离线并发。

     

锁机制

锁机制,就是在需要修改的数据上加互斥锁,通过互斥锁避免数据被同时修改。锁机制更具其应用环境又分为乐观锁和悲观锁.

乐观离线锁

image

之所以叫离线锁是因为这种锁不是长时间的锁,而且一个业务事务中可能同时包含了几个系统事务。而乐观是相对悲观而言的,表示预计冲突不总是发生,以其得到最大的性能。可能对业务事务,系统事务的概念大家有些陌生。按我的理解,可以这样解释:首先,你要明白事务不仅仅是一个技术问题(系统事务),更是一个领域问题(业务事务),举例说明,我们编辑一篇文章,显示编辑页面的动作可能涉及一个系统事务,提交表单的时候又涉及了一个系统事务,而整体可以看成一个业务事务。

乐观锁,指认为冲突很少发生,所以只是在数据修改的时候比较修改的基础数据和数据库中的数据是否相同,相同则修改,否则提示用户重新装入数据库中已经变化的数据。

一个帐单系统中增加帐单并计算销售税。某个会话增加一个帐单,然后查找顾客的地址来计算税率。但在生成帐单的同时,一个进行顾客信息维护的会话编辑了顾客的地址信息。由于税率和住址有关,生成帐单是使用的税率就不正确了,但由于帐单生成会话不会修改地址信息,因而就不会检测到冲突。从这个问题中,我们能深刻体会到事务不仅仅是一个技术问题,更是一个领域问题。

乐观离线锁通过检查在会话读取一条数据以后没有其他的会话修改该数据来保证数据的一致性.可以在任何时候获取一个乐观离线锁,但它只在获取该锁地系统事务过程中有效.因此,业务事务为了不破坏记录数据,必须对它在每个系统事务中的变更集成员申请乐观离线锁.也就是说,只要系统事务中有对数据库的修改,就需要获取乐观离线锁.

最常见的实现方式是是为每条记录关联一个版本号,当某条记录被装载是,该版本号与其他会话状态一样,由会话本身来维护。获取乐观离线锁的本质就是将会话数据中的版本号与当前记录数据的版本号相比较。一旦验证成功,所有变化(包括版本号的增加)就可以提交。防止不一致的记录数据是通过版本号来完成的,因为一个拿着旧版本号的会话无法获得乐观离线锁。

在基于关系数据库的系统中,锁地检查是通过在所有更新或删除记录的SQL语句中加上对版本号的判别来完成的。用一条SQL就可以同时获取锁和更新数据。最后一步是检查业务事务执行的SQL所返回的行数。行数为1代表成功,0代表记录被更改过或者已经被删除。返回行数0是,要将系统事务回滚以防止不一致的数据进入数据库。这样一来,业务事务必须要么被取消,要么解决冲突并重试。

通常实现乐观离线锁是通过在UPDATE和DELETE语句中加上版本号检查来实现的,但这样不能防止不一致读。例如在一个账单系统中增加账单并计算消费税。某个会话增加一张账单,然后查找客户的地址来计算税率。但账单生成的同时,一个进行客户信息维护的会话编辑了顾客的地址信息。由于税率和住址有关,生成账单时使用的税率就不正确了,由于账单生成会话不会修改地址信息,因而就不会检测到冲突。

与其他锁模式一样,tongchang对于企业应用中某些棘手的并发和时序问题,乐观离线锁本身并不能提供充分的解决方案。必须再次强调,在企业应用中的并发管理更是一个领域问题,而不只是技术问题。上面所说的顾客信息计算税率也可能允许的,但究竟应该使用那个版本呢?这就是一个业务问题。或者考虑一个集合。当两个会话同时向集合中加入元素时会发生什么呢?典型的乐观离线锁模式无法防止这种情况的发生。

乐观离线锁仅仅在业务事物提交时最后的系统事物中报告冲突。但通常提前冲突会更有效。可以提供checkCurrent()方法随时检查是否有其他人改动了数据。虽然不能保证不冲突,但提前通知冲突来停止一个复杂的过程也是值得的。随时使用checkCurrent()检测冲突以提前终止事物是有用的,但要记住,它并不保证提交时不会失败。

 

悲观离线锁

就锁类型而言,第一个选择是独占写锁,只在业务事物获取锁是为了编辑会话数据是才需使用该锁。它通过避免两个业务事务同时编辑一份数据来消除冲突。这种锁模式忽略了对数据的读,因此如果对数据读出的要求不是很高是,应该使用这种方式。

通常在数据库中建立一张lock表,该表的字段包括,表明,唯一索引,时间,用户信息等。在用户读取数据准备修改的时候,首先判断lock表中是否存在自己将要读取的数据。如果不存在,则在lock表中添加一条记录,记录对那张表的哪行数据进行修改;如果存在,在判断时间字段是否超时。如果超时,则更新lock表中本条记录的时间字段。(防止死锁的必要手段)。如果存在,也不超时,说明本条记录正在被其他用户修改,则返回并发信息。

粗粒度锁

粗粒度锁是覆盖多个对象的单个锁,这样不但简化了加锁行为本身,而且让你不必为了给他们加锁而加载所有的对象。实现粗粒度锁的第一步是为一组对象建立一个控制点。这使得只需要一个锁就可以锁住一堆对象,然后尽量提供最直接的方法找到他们的锁,从而减少在获取锁时为了标识该组而读取的对象数目。

用乐观离线锁让组中的对象共享同一个版本号来建立一个控制点,这意味这它们共享同一个版本号,而不是说他们的版本相同。增加这额版本号是,就成为一个锁住组中的所有对象的共享锁。

参考

锁机制(离线并发,乐观锁,悲观锁)

乐观离线锁

分类: 软件设计 标签:

远程调用过程分析&Hessian简单分析

2011年5月7日 admin 2 条评论

远程调用基本过程

远程过程调用包含下列步骤:

  1. 客户程序以正常的方式调用客户端存根
  2. 客户端存根生成一个i笑傲西,然后调用本地操作系统
  3. 客户端操作系统将消息发送给远程操作系统
  4. 远程操作系统将消息交给服务器存根
  5. 服务器存根将参数提出出来,然后调用服务器
  6. 服务器执行要求的操作,操作完成以后将结果返回给服务器存根
  7. 服务器存根将结果打包成一个消息,然后调用本地操作系统
  8. 服务器操作系统将很有结果的消息发送会客户端操作系统
  9. 客户端操作系统将消息交给客户端存根
  10. .客户端存根将结果从消息中提出出来,返回给调用它的客户过程。

所以这些步骤总的效果是,将客户过程对客户存根发出的本地调用转换成服务器过程的本地调用,而客户和服务器都不会意识到中间步骤的存在。

 

Hessian的远程调用实现

Hessian做为RPC的一种实现,采取跟多数RPC非常类似的远程调用方案,对远程过程需要在本地库中生成一个客户端版本,并在使用的时候为远程方法生成客户端存根。

Hessian调用的简单Demo

远程接口定义

public interface Hello {
     String sayHello();

}

远程接口的服务器端实现

public class HelloImpl implements Hello {

    public String sayHello() {
        return "Hello World";
    }

}

客户端代码

public final class HelloServiceTest {
    public static void main(String[] args) throws Exception {
         String url = "http://localhost:8080/hessian/hello.xsp";
         HessianProxyFactory factory = new HessianProxyFactory();
         Hello hello = (Hello) factory.create(Hello.class, url);
         System.out.println("远程调用结果: " + hello.sayHello());
      }
}

web.xml中的Servlet配置

<servlet>
             <servlet-name>hello</servlet-name>
             <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
             <init-param>
                 <param-name>home-class</param-name>
                 <param-value>com.danieljourney.hessiandemo.HelloImpl</param-value>
             </init-param>
             <init-param>
                 <param-name>home-api</param-name>
                 <param-value> com.danieljourney.hessiandemo.Hello</param-value>
             </init-param>
         </servlet>
         <servlet-mapping>
             <servlet-name>hello</servlet-name>
             <url-pattern>/hello.xsp</url-pattern>
         </servlet-mapping> 

Hessian客户端存根程序的实现

Hession的客户端存根是通过JDK的动态代理来实现的。JDK的动态代理可以参考JDK的动态代理深入解析(Proxy,InvocationHandler)-IJDK的动态代理深入解析(Proxy,InvocationHandler)-II这两篇文章

HessianProxy.invode(Object proxy, Method method, Object []args)的主要流程如下:

  1. 为远程调用的method生成方法调用的标识符
  2. 根据Hessian的二进制协议,将方法信息以及方法调用的参数序列化
  3. 通过Http协议将内容发送出去,并等待http的返回
  4. 将Http返回的内容,根据本地接口方法的返回类型反序列化,生成返回值。
  5. 清理并结束调用。

Hessian服务器存根程序的实现

Hessian在服务器端主要有两个类HessianServlet 和 HessianSkeleton.

HessianServlet 主要的职责是初始化服务器端服务类及远程方法的元数据。HessianSkeleton负责远程方法的定位、调用服务类、服务方法参数的处理,以及结果的返回。主要的流程如下:

  1. HessianServlet 初始化服务类及远程方法的元数据。
  2. 接口远程调用的HTTP请求,初始化服务上下文信息
  3. HessianSkeleton从HTTP请求中获取远程调用的方法及参数信息
  4. HessianSkeleton根据远程服务的信息找到本次远程调用的服务类及方法。
  5. 调用服务器类的方法,并获取返回值
  6. 将返回值输出到HTTP的Response中。
  7. 结束远程调用的HTTP请求,清理服务上下文信息

总结

这篇文章是对同步类型的远程调用的基本过程以及Hessian基本原理的简单分析,接下来还会在分析异步的远程调用的相关原理和实现。

参考资料

JDK的动态代理深入解析(Proxy,InvocationHandler)-I

JDK的动态代理深入解析(Proxy,InvocationHandler)-II

Hessian 2.0 序列化协议规范

Hessian 学习笔记

分类: 软件设计 标签: ,

Facade模式&远程Facade模式

2011年5月2日 admin 没有评论

Facade 模式

Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。他是为子系统中的一组接口所提供的一个一致的界面。这在某种意义上与Adapter及Proxy有类似之处,但是,Proxy(代理)注重在为Client-Subject提供一个访问的中间层,如CORBA可为应用程序提供透明访问支持,使应用程序无需去考虑平台及网络造成的差异及其它诸多技术细节;Adapter(适配器)注重对接口的转换与调整;而Facade所面对的往往是多个类或其它程序单元,通过重新组合各类及程序单元,对外提供统一的接口/界面。

在遇到以下情况使用Facade模式

  1. 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
  2. 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  3. 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。

Facade模式的优点

  1. 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
  2. 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要.在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用Facade可以降低编译依赖性,限制重要系统中较小的变化所需的重编译工作。Facade模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。

远程外观模式

远程外观模式为细粒度对象提供粗粒度的外观来改进网络上的效率。任何对象可能作为远程对象使用时,经常需要一个粗粒度的接口来减少完成某些任务所需要的调用次数。一个粗粒度的远程外观建立在大量的细粒度之上。所有细粒度对象都没有远程接口,并且远程外观不包括领域逻辑。远程外观所要完成的就是把粗粒度的方法转换到低层的细粒度对象上。

远程外观有有状态和无状态之分。无状态的远程外观可以组成池,这样就可以提高资源的使用率和和效率。

除了提供一个粗粒度的接口以外,远程外观还增加了其他几个功能。例如,它的方法中可以提供安全检查。远程外观同样也提供事务控制。远程外观中的方法可以开始一个事务,当做完需要工作后提交事务。每次调用都是一次完整的事务,应为当结果返回客户的时候,你不会希望这次事务还在运行中,因为这种长时间的运行将会使事务变得效率很低。

分类: 软件设计 标签: ,

Gateway模式

2011年5月2日 admin 没有评论

Gateway模式说明

事实上Gateway是一个非常简单的包装类(wrapper)模式。封转外部资源,创建一个简单的API,并用这个Gateway将对该API的调用转移到外部资源上。

Gateway模式的使用场景

如果必须通过一个复杂的接口与可能的位于系统之外的事物交互,你应当考虑入口模式。使用入口将复杂性封装起来,而不要让复杂性蔓延到整个系统中。使用Gateway几乎没有什么弊端,同时又可以使系统中Gateway之外的代码可读性显著提高。使用Gateway一个显而易见的优点是是你资源来替换另一种资源变得更容易。但是即使你认为资源不会发生任何变化,你仍可以使用入口模式所带来的简单性和可测性中获益。如你有数个子系统,将他们解耦的另一种选择是映射器,但是,映射器远比入口复杂。我们使用入口来完成大部分资源访问。

Gateway模式和其他模式的区别

  • 外观模式对较复杂的API进行简化,通常由服务的作者提供,而且是通用的。gateway则是客户方为了其特定应用而撰写的。此外,一个外观通常暗示一个与原始接口不同的接口,但在入口中可以只是简单地照搬被包装的接口,这种Gateway用于将来替换资源或测试目的。
  • 适配器模式修改一个已经实现的接口,使其与另一个你所用到的接口相匹配。Gateway模式中通常没有一个已经存在的接口,虽然你可能会使用一个适配器来讲一个实现映射到一个Gateway的接口上。此时适配器是入口类实现的一部分。
  • Media模式通常用将多个对象解耦,使得他们无需相互引用,而只要与Media发生关联。Gateway模式中通常只涉及两个对象,而且被包装的资源并不知道入口的存在。
分类: 软件设计 标签:

缓存资料整理

2011年5月1日 admin 没有评论

Caching is a technique for sharing data among multiple consumers or servers that are expensive to either compute or fetch.  Data are stored and retrieved  in a subsystem that provides quick access to a copy of the frequently accessed data.

Caches are implemented as an indexed table where a unique key is used for referencing some datum.  Consumers access data by checking (hitting) the cache first and retrieving the datum from it.  If it’s not there (cache miss), then the costlier retrieval  operation takes place and the consumer or a subsystem inserts the datum to the cache.

image

写入的策略

当出现后台数据发生变动而没有更新Cache的时候,Cache中的数据就会变得陈旧.缓存的写入策略定义了缓存中的数据时如何更新的。

策略 实现说明
Write-through

every write to the cache follows a synchronous write to the backing store

Write-behind

updated entries are marked in the cache table as dirty  and it’s updated only when a dirty datum is requested

No-write allocation:

only read requests are cached under the assumption that the data won’t change over time but it’s expensive to retrieve

分类: 软件设计 标签:

记录“封装”

2011年4月30日 admin 没有评论

封装的定义

在程序上,隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过 外部接口,一特定的访问权限来使用类的成员。

封装的大致原则

  1. 把尽可能多的东西藏起来.对外提供简捷的接口.
  2. 把所有的属性藏起来

参考资料

封装_百度百科
分类: 软件设计 标签:

SOA 模式学习——基础概念

2011年4月24日 admin 3 条评论

作者将模式分为三类:(1)基本服务模式,是最小粒度的服务模式,已经无法再次拆分,可以认为是模式原语,多用在服务层面使用。(2)架构模式,通用的 SOA系统设计要素,多用在系统设计时,做为架构风格的一个元素。(3)复合模式,基本服务模式的组合,用来定义系统的衔接特征,多用在应用系统整合场景。

493a8455t8702668433f4&690

基本服务模式

模式名称 模式定义
Aggregator – 聚合器

因为SOA系统只能保证消息送达,而不能保证按顺序送达,所以应该把几个有顺序要求的消息聚合在一起发送。这样避免了额外考虑消息状态和顺序的开销,更有利于异步处理消息。

Service Bus– 服务总线 将多个独立的系统(已有系统、新系统)通过统一机制整合起来。
Dynamic Routing – 动态路由 可配置、非过滤的消息路由。过滤方式的路由会导致endpoint接收所有的消息,而这个动态路由则位于过滤器之前,而起它的过滤规则可配置,甚至是由endpoint一侧的应用配置。
Event-Driven Consumer – 事件驱动的消费者 阻塞的监听或轮询方式浪费资源。通过基于总线或者特定于应用的callback机制可以更高效的处理消息。
Filter – 过滤器 系统间独立于平台的一种消息处理方式,并且不引入新的系统依赖或者不必要的耦合性。注意过滤器的对外接口要一致,以便灵活组合。
Router 根据消息内容、类型等规则解耦不同应用之间的耦合,过滤规则可配置。路由可以是串行或者并行。
Translator or Transformer – 翻译转换器 各种不同系统之间消息可能采用了不同的输入输出格式。翻译转换器的作用就是把这些不同的格式转换为应用理解的格式。它位于endpoint一侧,因为一般来说根据系统和协议的不同,消息采用的格式也有所不同。有了消息翻译转换机制,系统内部就不用考虑外面的消息格式可以专心业务开发。

架构模式

模式名称 模式定义
Asynchronous Processing 异步事件风格解决服务之间的交互,例如,ATM到银行账务系统,多用该模式。在实践中,也鼓励使用这种风格,实现SOA,以弱化一致性、事务性,缓解资源受限等场景,该模式需要业务配合才能完全发挥。同步机制降低性能和可靠性,所以当然能异步尽量异步。一般会需要个消息队列之类的。异步机制能提供可扩展性,包括前端和后端
Bridge 常用在2中体系之间的交互,把一种协议转换为另一种协议。它与Translator的区别在,Translator是针对消息的,它针对链路。
Cross-Service Operation 把多个服务封装为一个服务来使用,以确保一致性、事务性,可以把该风格弱化为分布式事务风格.不同服务组成原子服务,事务性应用需要这个。
Event-Driven Dispatching 基于事件的派发,常用在pub/sub场景。
Process Aggregation 把多个过程聚合在一起,按照一定顺序进行消息处理,此时不要求一致性,每个过程都是独立的,用BPM技术更容易理解该场景。
Routing and Filtering 实现一个消息的多种渠道应用,例如,一个业务通知,通过Mail,IM、SMS、语言、甚至特殊应用传达给用户的场景,一个产品为多个渠道提到内容的场景。
Replicator 实现一份消息,服务处理的时候,同时往数据库复制一份,例如,数据备份容灾、数据采集、数据审计等。

复合模式

模式名称 模式定义
Centralized Schema 解决跨应用边界的数据共享问题
Concurrent Contracts 解决一个服务多个消费者时,每个消费者接口不同的问题。
Decomponse Capability 在业务流程不断膨胀和进化的过程中减少功能拆分影响的方法。在物理上分开的数据模型和服务定义,仅在二者生成特定服务实现的时候才组装,这样二者可以分别进化。
Enterprise Service Bus 通过一个消息总线,解决应用整合的问题,化解复杂的网状链接。
Fault-Tolerant Service Provider 在冗余的服务提供者之前加负载均衡器。需要保证服务无状态和尽可能的可重用性。
Wrapper 把遗留系统包装成通用的无状态服务。

 

参考资料

http://blog.sina.com.cn/s/blog_493a84550100iogt.html

http://www.osteching.com/2011/01/soa-patterns-simple-impression/

http://refcardz.dzone.com/refcardz/soa-patterns

分类: 软件设计 标签:

面向对象的设计原则资料汇总

2011年3月27日 admin 1 条评论

这两周都在阅读和体会了Bob大叔的面向对象设计原则方面的一些文章。这些文章除了对这些面向对象的设计原则做了详细的释义以外,另外还有不少的观点可以让我们在架构、设计的过程中使用。其中的五个面向对象的设计原则,他们是:

SRP The Single Responsibility Principle(单一责任原则) 一个类应该有一个,且只有一个理由去改变
OCP The Open Closed Principle(开闭原则)

对扩展是开放的,而对修改是封闭的

LSP The Liskov Substitution Principle(Liskov替换原则) 派生类必须对他们的基类替代。
DIP The Dependency Inversion Principle(依赖转置)

高层模块不应该依赖于低层模块。二者都应该依赖于抽象。 抽象不应该依赖于细节。细节应该依赖于抽象。

ISP The Interface Segregation Principle(接口分隔原则) 不应该强迫用户依赖于他们不用的方法

每个原则有专门的PDF文档可以阅读。

主要的文章入口时http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

另外还有一个PPT是也是介绍这些原则的。http://butunclebob.com/files/SDWest2006/AdvancedPrinciplesOfClassDesign.ppt

关于面向对象的这些内容几乎每年都会重读一遍,而每次都有新的收获,也正印证了“温故而知新”这句老话。

分类: 软件设计 标签:

如何编写优秀的设计文档

2010年11月14日 admin 4 条评论

不知不觉自己自己做软件开发已经快6年了,虽然工作中离不开编写或者阅读设计文档,而且公司都提供了详尽的设计文档模板,可在具体的工作中对设计文档的编写依然会觉得存在很多的障碍,例如:

  • 不清楚设计文档应该包含哪些内容,该如何组织。
  • 该用什么样的方式来体现自己的设计思路不明确
  • 对设计文档所需要描述的程度没有把握,太概括了起不到效果,而过于详细的描述,又怕浪费太多的时间和精力

在2010年11月12日的一次周会上我们几个童鞋针对一份具体且典型的设计文档进行了一次“从文档的本身来审视一下这篇文档”的讨论,在这次讨论以后,对设计文档的编写有了更进一步的掌握,也就形成了这篇文章。

先说明一下我对“如何编写优秀的设计文档”这个问题的一个总结“从多种视角、分层次并采用合适的形式来编写设计文档”。下面我们逐一展开.

多视角

通常设计文档的编写多数采用的就一个视角——“设计者的视角”来编写文档,在内容上主要是说明设计的最终结果和设计的意图。这就是我们目前“典型性”的设计文档,这样的文档主要是仅仅达到了设计文档编写人员“自圆其说”的效果,离一批优秀的设计文档相差的还很远。一篇优秀的设计文档应该包括以下几个视角:普通文档阅读人员的视角、设计文档评审人员的视角,使用者或二次开发人员的视角,需求方或者产品经理的视角、测试人员的视角。

普通文档阅读人员的视角:普通文档阅读人员至少要求文档结构清晰,目的明确,文章的层次分明等等。

设计文档评审人员的视角:设计文档评审人员在阅读设计文档的时候需要能从设计文档中清晰、直接地了解本次设计所要解决的问题,如果是解决具体问题而进行的设计,就可以对这些问题做一些陈述。这样评审人员和包括以后的文档阅读人员对设计所要解决实际问题就会有一个很好的了解。接下来就是要注意设计或者解决方案在表达时候的效率和效益,这一点我们留到对设计文档的形式描述环节来说明。

使用者和二次开发人员的视角:很多设计为的是解决一组相同的问题,当有其他新的问题解决需需要利用这个设计的时候,使用者或者二次开发人员理所当然的想要从设计文档中得到直接地指导。例如接口的设计文档,要有完整的接口定义、错误码定义,如果有更直接地接口调用样例代码就更好了,如果设计需要进行一些配置,就需要对配置的过程有说明。

需求方或者产品经理:需求方或者产品经理更多的会关注设计文档对那些问题具体问题是如何解决的,而在设计的可扩展性、耦合度的降低等方面不是特别的关注。所以设计文档处理要描述模块、框架、组件的设计以外,还要对实际问题的解决来一个明确的说明,从而让需求方或者产品经理对问题的解决充满信心。

测试人员的视角:从测试人员的视角出发文档中应该要说明设计本身对测试的影响,例如对设计内容对测试内容和范围的影响,设计对分模块和分层设计有哪些帮助和影响。

一篇设计文档只有从“多视角”进行阐述和说明,才能最大程度的满足文档阅读者的需求,给他们想要的答案。

分层次

一篇设计的文档应当要全面阐述设计所要解决的问题、设计所依赖历史背景、设计所需面对的限制和约束以及设计的理由以及遵循的原则。

设计所需解决的问题:通常可以对具体的业务需求和目的进行阐述;如果是对目前存在的设计进行重构和改进,我们也需要对历史设计方案的做介绍和说明,如果新设计方案着重要克服历史设计方案的诟病,那就要对历史方案存在的问题加以阐述和说明。

设计所需面对的限制和约束:很少有系统设计的时候是没有约束和限制的,例如并发的要求、数据量的要求、操作系统的限制、服务器的配置等等。很多时候这些约束和限制影响甚至决定最后的设计方案

还是另外,由于绝大多数设计通常不是build in one Day,通常都有一个设计变迁的过程,如果能够记录设计变迁的过程或者设计决策的理由。这样设计文档在很多场合都会派上用场(以下几点参考至《软件架构师应该知道的97件事》#记录决策理由):

  • 可以作为和开发人员进行沟通的工具,说明应遵循的重要架构原则
  • 当开发人员对架构背后的逻辑提出疑问时,使团队成员能够“就事论事”,甚至能够避免一场“兵变”
  • 向经理和利益相关者说明这样构建软件的确切原因(比如,采用某种较为昂贵的硬件或软件的必要性等)。
  • 把项目移交给下任架构师(你有多少次在接手软件时很疑惑原来的设计者究竟为什么要采用那种做法?)。
  • 逼着你明确说明出理由,有助于确保基础(fundation)是扎实稳固的
  • 如果相关条件发生变化,需要对决策进行重新评估,它可以作为一个起点。

采用合适的形式

    设计文档的内容包含的形式是很多的,例如UML图,表格等等。以UML图为例,设计文档中使用的最多的是时序图和类图。

时序图(Sequence Diagram),亦称为序列图或循序图,是一种UML行为图。它通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作。它可以表示用例的行为顺序,当执行一个用例行为时,时序图中的每条消息对应了一个类操作或状态机中引起转换的触发事件。

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性信息。

这些专业绘图工具,具有一定的学习成本,以UML图工具为例,用StarUML和Vision制作的UML相差很大,所以最好选择在你的team中普遍掌握的工具加以使用。

除了这些专业的图形以外,用PPT或Word制作的普通图形,在描述系统模块、系统和系统之前的关系时也很有用。

以上是对”如何编写优秀的设计文档”这个话题的一些总结,这点内容倒腾了一个下午才陆续完成的,感觉“分层次”和“采用合适的形式”这两点总结的比较简单,先写下来,以后再补充完善。

分类: 软件设计 标签: