存档

‘软件设计’ 分类的存档

理解:组合VS继承

2010年11月7日 admin 没有评论

什么是继承

面向对象编程 (OOP) 语言的一个主要特性就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

继承概念的实现方式有三类:实现继承、接口继承和可视继承。

  1. 实现继承是指使用基类的属性和方法而无需额外编码的能力;
  2. 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
  3. 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。 在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

什么是组合

通过创建一个由其他对象组合的对象来获得新功能的重用方法,新功能的获得是通过调用组合对象的功能实现的,有时又叫聚合。

组合&继承优缺点比较

  组合 继承
优点 1. 被包含对象通过包含他们的类来访问
2. 黑盒重用,因为被包含对象的内部细节是不可见的
3. 很好的封装
4. 每个类专注于一个任务
5. 通过获得和被包含对象的类型相同的对象引用,可以在运行时动态定义组合的方式
1.新的实现很容易,因为大部分是继承而来的
2.很容易修改和扩展已有的实现
缺点 1.结果系统可能会包含更多的对象
2.为了使组合时可以使用不同的对象,必须小心的定义接口
1.打破了封装,因为基类向子类暴露了实现细节
2.白盒重用,因为基类的内部细节通常对子类是可见的
3.当父类的实现改变时可能要相应的对子类做出改变
4.不能在运行时改变由父类继承来的实现

由此可见,组合比继承具有更大的灵活性和更稳定的结构,一般情况下应该优先考虑组合。只有当下列条件满足时才考虑使用继承:

  • 子类是一种特殊的类型,而不只是父类的一个角色
  • 子类的实例不需要变成另一个类的对象
  • 子类扩展,而不是覆盖或者使父类的功能失效
  • 组合关系和继承关系相比,前者的最主要优势是不会破坏封装,在软件开发阶段,组合关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于组合关系使系统具有较好的松耦合性,因此使得系统更加容易维护。组合关系的缺点是比继承关系要创建更多的对象。从软件构架来说,组合,耦合度比继承弱,继承是对父类方法和数据成员的兼收并蓄,而组合,可以有选择得使用某一种方法。

      Effective Java中对这个问题的说明

    继承(指的是子类扩展超类,并不包含接口)是实现代码重用的有力手段,但它并不总是完成这项工作的最佳工具。不适当地使用继承会导致脆弱。与方法调用不同的是,继承打破了封装性。换句话说子类依赖于超类中特定功能的实现细节。超类的实现可能随着发行版本而变化,就有可能影响子类。因此,子类必须要跟着超类的更新而发展。除非超类是专门为扩展而设计的,并且具有很好的说明文档。

Erich Gamma对“组合优先于类继承”的理解

在GoF的书中提到一个很重要的面向对象设计理论:“对象合成(composition)优先于类继(inheritance)。”Erich Gamma认为,继承也用它的脆弱性,因为当子类继承的方法被调用时,它很容易就能知道(父类与子类)之间的联系。父类与子类之间存在一个很强的耦合性,由于这种耦合性使得我在子类中加入的代码会被访问到。而合成具有更好的特性,通过建立一些用于插入大尺度对象中的小尺度对象,然后由大尺度的对象来调用小尺度对象就可以降低耦合性。

 

参考

Java程序设计之-复合优先于继承

组合与继承

分类: 软件设计 标签:

负载均衡知识整理

2010年9月22日 admin 1 条评论

负载均衡的定义

In computer networking, load balancing is a technique to distribute workload evenly across two or more computers, network links, CPUs, hard drives, or other resources, in order to get optimal resource utilization, maximize throughput, minimize response time, and avoid overload. Using multiple components with load balancing, instead of a single component, may increase reliability through redundancy. The load balancing service is usually provided by a dedicated program or hardware device (such as a multilayer switch or a DNS server).

It is commonly used to mediate internal communications in computer clusters, especially high-availability clusters. If the load is more on a server, then the secondary server takes some load while the other is still processing requests.

 

image

 

 

应用类型 负载均衡策略
Web Application Round Robin
Caching Pool Frequency rule and expiration algorithms
Applicatoin like music store

shift the larger number popular requests to higher performance systems, serving the rest of the requests from less powerful systems or clusters.

 

持续性的负载均衡器

有状态的应用需要持续性或粘性的负载均衡,从而消费者能够从服务器Pool中维护一次Session.

负载均衡器的常用特性

  • Content filtering – inbound or outbound
  • Distributed Denial of Service (DDoS) attack protection
  • Firewalling
  • Payload switching – send requests to different servers based on URI, port, and/or protocol
  • Priority activation – adds standing by servers to the pool
  • Rate shaping – ability to give different priority to different traffic
  • Scripting – reduces human interaction by implementing programming rules or actions
  • SSL offloading – hardware-assisted encryption frees web server resources
  • TCP buffering and offloading – throttle requests to servers in the pool image

 

负载均衡主要算法

轮循法(Round Robin)

顺序循环将请求一次顺序循环地连接每个服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。

此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况;客户端的每一次请求服务在服务器停留的时间都可能会有较大的差异,随着工作时间的加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不同,这样的结果并不会达到真正的负载均衡。

算法实现可以参考

假设有一组服务器S = {S0, S1, …, Sn-1},一个指示变量i表示上一次选择的服务器,W(Si)表示服务器Si的权值。变量i被初始化为n-1,其中n > 0。

 

比率算法

给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配, 直到其恢复正常。

优先权法

优先权(Priority):给所有服务器分组,给每个组定义优先权,BIG-IP 用户的请求,分配给优先级最高的服务器组(在同一组内,采用轮询或比率算法,分配用户的请求);当最高优先级中所有服务器出现故障,BIG-IP 才将请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。

最少连接法

最少的连接方式(Least Connection):传递新的连接给那些进行最少连接处理的服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配, 直到其恢复正常。但由于不同应用对系统资源的消耗可能差异很大,而连接数无法反映出真实的应用负载,因此在使用重型Web服务器作为集群节点服务时(例如Apache服务器),该算法在均衡负载的效果上要打个折扣?为了减少这个不利的影响,可以对每个节点设置最大的连接数上限(通过阈值设定体现)?

最快模式

传递连接给那些响应最快的服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。

观察模式

连接数目和响应时间以这两项的最佳平衡为依据为新的请求选择服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。

响应时间算法

负载均衡设备对内部各服务器发出一个探测请求(例如Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。

散列算法

散列法也叫哈希法(HASH),通过单射不可逆的HASH函数,按照某种规则将网络请求发往集群节点?哈希法在其他几类均衡算法不是很有效时会显示出特别的威力?例如,在前面提到的UDP会话的情况下,由于轮转法和其他几类基于连接信息的算法,无法识别出会话的起止标记,会引起应用混乱?而采取基于数据包源地址的哈希映射可以在一定程度上解决这个问题:将具有相同源地址的数据包发给同一服务器节点,这使得基于高层会话的事务可以以适当的方式运行?相对称的是,基于目的地址的哈希调度算法可以用在Web Cache集群中,指向同一个目标站点的访问请求都被负载均衡器发送到同一个Cache服务节点上,以避免页面缺失而带来的更新Cache问题?

加权法

加权方法只能与其他方法合用,是他们的一个很好的补充?加权算法根据节点的优先级或当前的负载状况(即权值)来构成负载均衡的多优先级队列,队列中的每个等待处理的连接都具有相同处理等级,这样在同一个队列里可以按照前面的轮转法或者最少连接法进行均衡,而队列之间按照优先级的先后顺序进行均衡处理?在这里权值是基于各节点能力的一个估计值?

参考资料

Load balancing

服务器负载均衡算法

服务器负载均衡算法有很多(持续性的和非持续性的),包括轮循算法、最少连接算法、响应时间算法、散列算法、最少连接失误算法,链路带宽算法等等。此外实际服务器(Real Server)可以被分配不同的加权值来调整被分配的流量。比如性能高的大型服务器可配置较大的加权值,而为性能较低的小型服务器设置较小的加权值。为了避免服务器因过载而崩溃,可为实际服务器指定最大连接阈值来避免该服务器过载。任何服务器可被指定为另一台服务器的备份服务器或溢出服务器,从而进一步保证了应用可用性。

几种负载均衡算法

负载均衡

六类负载均衡算法概述

F5负载均衡算法及基本原理

Scalability & High Availability Refcardz

分类: 软件设计 标签:

面向对象语言VS 面向过程语言学习摘要

2010年5月1日 admin 2 条评论

今天早上打算学习一些面向对象语言的基本原理,也不打算找一本著作仔细阅读了,就从网络上搜索一些有用的信息,做个记录。

过程语言VS面向对象语言VS通用式编程

过程语言是使问题与语言相适应,他关注的是数据和算法,他一般使用自上而下的设计方式,即把一个未知的大问题分解成一个一个可解或容易求解的子问题,然后最终把问题解决。

面向对象语言是使语言与问题相适应,他关注的是类,即如何描述问题域中的对象,他一般使用的是自下而上的设计方式,即先为问题域中的每个对象建立用于描述这些对象的类,然后通过类之间的交互描述整个问题。

通用式编程,或者称为模板编程,他强调的是算法方面,他提出一种通用的,与数据类型无关的算法结构。

——感觉说的有点问题?

 

过程式语言与面向对象语言的区别

过程式语言与面向对象语言,到底有什么区别?可能是初学者常碰到的问题。简单来说,过程式语言整个是构建在动词上的语言。比如,最常见的经典过程式语言- C语言,打印一条语句的语法是printf(), 这个方法的名字本身就是一个动词,这个动词强调了一个动作的过程,所谓过程式就是这个意思。

同样的方法在面向对象的JAVA中就是这样写:System.out.println();  前面说过面向对象语言就是构建在名词基础上的系统,对象就是一个名词。大家都知道对象封装了操作和属性,所以System是一个对象,后面跟上分类在 out目录下的方法println。 这就是面向对象的写法。

谈谈面向对象的编程语言和面向过程编程语言的不同

总体而言,面向对象简单,面向过程对人员要求素质过高 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

艾兰.库伯的《软件创新之路》中提到: 面向过程和面向对象的区别并不像人们想象得那么大 面向对象的大部分思想在面向过程中也能体现 但面向过程最大的问题(也许是唯一先天的缺陷)在于随着系统的膨胀,面向过程将无法应付,最终导致系统的崩溃 面向对象的提出正是试图解决这一软件危机 目前看来,似乎有一定成效 但仍任重道远。

其实我始终认为,不管是面向对象,还是面向过程,都体现了一种软件重用的思想! 只不过面向过程中重用的是过程和函数,但是面向对象重用的是类,一种将数据和处理数据的过程及函数封装在一起的实体,其实面向对象中的过程和函数和面向过程中的分别不是很大,所以数据流图和伪代码还是有用的。 面向对象一个很大的好处就是数据 和方法的封装,由此面向对象的三大特性得到发挥

分类: 软件设计 标签:

NO SQL Reading

2010年4月10日 admin 没有评论

That No SQL Thing – Key/Value stores

The simplest No SQL databases are the Key/Value stores.

Concurrency –In Key/Value Store, concurrency is only applicable on a single key, and it is usually offered as either optimistic writes or as eventually consistent. In highly scalable systems, optimistic writes are often not possible, because of the cost of verifying that the value haven’t changed (assuming the value may have replicated to other machines), there for, we usually see either a key master (one machine own a key) or the eventual consistency model.

Queries – there really isn’t any way to perform a query in a key value store, except by the key. Even range queries on the key are usually not possible.

Transactions – while it is possible to offer transaction guarantees in a key value store, those are usually only offer in the context of a single key put. It is possible to offer those on multiple keys, but that really doesn’t work when you start thinking about a distributed key value store, where different keys may reside on different machines. Some data stores offer no transaction guarantees.

Scaling Up – In Key Value stores, there are two major options for scaling, the simplest one would be to shard the entire key space. That means that keys starting in A go to one server, while keys starting with B go to another server. In this system, a key is only stored on a single server. That drastically simplify things like transactions guarantees, but it expose the system for data loss if a single server goes down. At this point, we introduce replication.

Replication – In key value stores, the replication can be done by the store itself or by the client (writing to multiple servers). Replication also introduce the problem of divergent versions. In other words, two servers in the same cluster think that the value of key ‘ABC’ are two different things. Resolving that is a complex issue, the common approaches are to decide that it can’t happen (Scalaris) and reject updates where we can’t ensure non conflict or to accept all updates and ask the client to resolve them for us at a later date (Amazon Dynamo, Rhino DHT).

That No SQL Thing – Key / Value stores – Operations

复习Amazon Dynamo设计的一点分享

Eventual consistency其实是对一致性的一种延展,过程中允许部分不一致,但是在事务处理结束或者有限的时间内保持事务的一致性。一句话简单概括就是:“过程松,结果紧,最终结果必须保持一致性”。

load balance的几种模式

客户端实施load balance。采用客户端包来实现分发算法,同时配置分发节点情况。Memcached Cache客户端使用的一种基本方式。

b. 服务端硬件实现load balance。

c. 客户端改进模式。配制节点以及算法都可以采用集中的Master来管理和维护,包括心跳检测等手段由Master来实现。当然支持Master失效的容错性策略实施。

d. 服务端模式改进。采用preference list来分离接受和处理任务的节点。

首先采用A模式可以防止B模式在单点的情况下出现的不可用风险,也可以减轻高并发下单点的压力,提高效率(这点淘宝的同学有和我提到过,他们采用的“软负载”方式)。但是A模式会增加对于客户端包的依赖性,对于扩展和升级都会有一定的限制。

其次B模式是最省心的方式,扩展性也比较好,但是就是在上面提到的单点问题会有所限制。

C方式是对于A方式的一种改进,我以前的一篇文章中提到过,这样可以提高A的可扩展性以及可维护性,减小对于客户端包的依赖,但是增加了系统复杂度,同时Master也是会有单点的问题,不过问题不大(失效的情况下就是退化到了A模式)。

D方式是解决服务端简单的分发而导致处理的不均衡性,其实这种模式也可以改进客户端的算法。因为通过Hash算法未必能够将压力分摊均匀,就好比一些处理需要耗时比较久一些处理耗时比较少,系统对于key的映射不均衡等等问题,不过在Dynamo中描述的并不很明确,其中的算法还是要根据实际情况来做的。

How I learned to say ‘No’ to SQL

consistent-hashing算法
High Performance Scalable Data Stores

I Can’t Wait for NoSQL to Die

NoSQL DZone Poll Results

分类: 软件设计 标签:

Oberserver模式的变种AsyncToken

2010年3月27日 admin 2 条评论

在周末的设计过程中,需要用到Observer模式,但当时纠结了一下是否真的需要使用Observer模式,在Google了一下以后,找到了[设计模式]AsyncToken模式,替换通常的Listener模式这篇文章。先贴一下样例代码吧,完整的代码可以参考这个连接

package cn.org.rapid_framework.util.concurrent.async; 

 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong; 

 import javax.swing.SwingUtilities; 

 /**
  * <pre>
  *      public void testSendEmail() {
  *          final String address = "badqiu(a)gmail.com";
  *          final String subject = "test";
  *          final String content = "async token test";
  *          AsyncToken token = sendAsyncEmail(address,subject,content);
  *
  *          token.addResponder(new IResponder() {
  *              public void onFault(Exception fault) {
  *                  System.out.println("email send fail,cause:"+fault);
  *                  sendAsyncEmail(address,subject,content);
  *              }
  *              public void onResult(Object result) {
  *                  System.out.println("email send success,result:"+result);
  *              }
  *          });
  *      }
  *
  *      public AsyncToken sendAsyncEmail(String address,String subject,String content) {
  *          final AsyncToken token = new AsyncToken();
  *
  *          Thread thread = new Thread(new Runnable() {
  *              public void run() {
  *                  try {
  *                      //do send email job...
  *                      token.setComplete(executeResult);
  *                  }catch(Exception e) {
  *                      token.setFault(e);
  *                  }
  *              }
  *          });
  *          thread.start();
  *
  *          return token;
  *      }
  * </pre>
  * @see AsyncTokenTemplate
  * @author badqiu
  */
 public class AsyncToken<T>  {
         public static final String DEFAULT_TOKEN_GROUP = "default";
         private static AtomicLong tokenIdSequence = new AtomicLong(1); 

         //tokenGroup tokenName tokenId   
         private String tokenGroup = DEFAULT_TOKEN_GROUP;
         private String tokenName;
         private long tokenId; 

         private List<IResponder> _responders = new ArrayList(2); 

         private UncaughtExceptionHandler uncaughtExceptionHandler;
         private T _result;
         private Exception _fault;
         private boolean _isFiredResult; 

         private CountDownLatch awaitResultSignal = null; 

         public AsyncToken(){
                 this(null);
         } 

         public AsyncToken(UncaughtExceptionHandler uncaughtExceptionHandler) {
                 this(DEFAULT_TOKEN_GROUP,null);
                 this.uncaughtExceptionHandler = uncaughtExceptionHandler;
         } 

         public AsyncToken(String tokenGroup,String tokenName) {
                 setTokenGroup(tokenGroup);
                 setTokenName(tokenName);
                 this.tokenId = tokenIdSequence.getAndIncrement();
         } 

         public String getTokenGroup() {
                 return tokenGroup;
         } 

         public void setTokenGroup(String tokenGroup) {
                 if(tokenGroup == null) throw new IllegalArgumentException("'tokenGroup' must be not null");
                 this.tokenGroup = tokenGroup;
         } 

         public String getTokenName() {
                 return tokenName;
         } 

         public void setTokenName(String tokenName) {
                 this.tokenName = tokenName;
         } 

         public long getTokenId() {
                 return tokenId;
         } 

         /**
          * addResponder(responder,false);
          * @param responder
          */
         public void addResponder(final IResponder<T> responder) {
                 addResponder(responder,false);
         }
         /**
          */
         public void addResponder(final IResponder<T> responder,boolean invokeResponderInOtherThread) {
                 _responders.add(responder); 

                 if(_isFiredResult) {
                         if(invokeResponderInOtherThread) {
                                 SwingUtilities.invokeLater(new Runnable() {
                                         public void run() {
                                                 fireResult2Responder(responder);
                                         }
                                 });
                         }else {
                                 fireResult2Responder(responder);
                         }
                 }
         } 

         public List<IResponder> getResponders() {
                 return _responders;
         } 

         public boolean hasResponder() {
                 return _responders != null && _responders.size() > 0;
         } 

         public UncaughtExceptionHandler getUncaughtExceptionHandler() {
                 return uncaughtExceptionHandler;
         } 

         public void setUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler) {
                 this.uncaughtExceptionHandler = uncaughtExceptionHandler;
         }        

         private void fireResult2Responder(IResponder responder) {
                 try {
                         if(_fault != null) {
                                 responder.onFault(_fault);
                         }else {
                                 responder.onResult(_result);
                         }
                 }catch(RuntimeException e) {
                         if(getUncaughtExceptionHandler() != null) {
                                 getUncaughtExceptionHandler().uncaughtException(responder, e);
                         }else {
                                 throw e;
                         }
                 }catch(Error e) {
                         if(getUncaughtExceptionHandler() != null) {
                                 getUncaughtExceptionHandler().uncaughtException(responder, e);
                         }else {
                                 throw e;
                         }
                 }
         } 

         private void fireResult2Responders() {
                 synchronized (this) {
                         _isFiredResult = true;
                         if(awaitResultSignal != null) {
                                 awaitResultSignal.countDown();
                         }
                 } 

                 for(IResponder r : _responders) {
                         fireResult2Responder(r);
                 }
         } 

         public void setComplete(){
                 setComplete(null);
         } 

         public void setComplete(T result) {
                 if(_isFiredResult) throw new IllegalStateException("token already fired");
                 this._result = result;
                 fireResult2Responders();
         } 

         public void setFault(Exception fault) {
                 if(fault == null) throw new NullPointerException();
                 if(_isFiredResult) throw new IllegalStateException("token already fired");
                 this._fault = fault;
                 fireResult2Responders();
         } 

         public boolean isDone() {
                 synchronized (this) {
                         return _isFiredResult;
                 }
         } 

         /**
          * @see Future
          */
         public Object waitForResult() throws InterruptedException,Exception {
                 return waitForResult(-1, null);
         }
         /**
          *  @see Future
          */
         public Object waitForResult(long timeout,TimeUnit timeUnit) throws InterruptedException,Exception {
                 synchronized(this) {
                         if(_isFiredResult) {
                                 if(_fault != null) {
                                         throw _fault;
                                 }else {
                                         return _result;
                                 }
                         } 

                         awaitResultSignal = new CountDownLatch(1);
                 } 

                 if(timeout > 0) {
                         awaitResultSignal.await(timeout,timeUnit);
                 }else {
                         awaitResultSignal.await();
                 } 

                 if(_fault != null) {
                         throw _fault;
                 }else {
                         return _result;
                 }
         } 

 } 

文章总结了AsyncTokenVS Observer好处和异同

使用AsyncToken的好处:

1. token可以无限传递,只要对方法的执行结果感兴趣,都可以监听方法的执行结果.

2. 拥有上下文,还可以引用前面的参数,以执行任务email重发这种任务

3. 一个token与一个方法对应,方法调用时你即知道token对应的事件,不需要使用listener模式中的一般用EventType来区别不现的事件

3. 灵活转换,也可以将上面的token再转至listener,再由listener以事件的方式派发事件

与Listener的异同:

1.token可以无限传递

2.没有使用事件或是监听不同的方法,listener一般配合需要使用事件,然后由事件进行参数的绑定.

3.listener模式一般是先设置好listener,而AsyncToken可以得到token后再添加监听方法

分类: 软件设计 标签:

阅读笔记:《程序员修炼之道》——2.8 正交性

2010年3月27日 admin 没有评论

这两天在做一个系统分析和设计的过程中,突然想起《程序员修炼之道》中有的一节是介绍正交性理论的,乘热打铁做了一次阅读笔记。

如果你想要制作易于设计、构建、测试和扩展的系统,正交性是一个十分关键的概念,但是,正交性的概念很少被直接讲授,而常常是你学习的各种其他方法和技术的隐含特性。这是一个错误。一旦你学会了直接应用正交性原则,你将发现,你制作的系统的质量立刻就得到了提高。

什么是正交性

“正交性”是从几何学中借来的术语。如果两条直线相交成直角,它们就是正交的。用向量术语说这两条直线互不依赖。 在计算技术中,该术语用于表示某种不相依赖性或是解耦性。如果两个或更多个事务中的一个发生变化,不会影响其他事物,这些事物就是正交的。在设计良好的系统中,数据库代码与用户界面是正交的:你可以改动界面,而不影响数据库;更改数据库,而不用改动界面。

正交的好处

非正交系统的改变与控制更复杂是其固有的性质。当任何系统的各组件相互高度依赖时,就不再有局部(local fix)修正这样的事情。

提示13
Eliminate Effects Between Unrelated Things
消除无关事物之间的影响

我们想要设计自足的组件:独立,具有单一、良好定义的目的(Yourdon和Constantine称之为内聚(cohesion))。如果组件是相关隔离的,你就知道你能够改变其中之一,而不用担心其余组件。只要你不改变组件的外部接口,你就可以放心:你就不会造成波及这个系统的问题。
如果你编写正交的系统,你得到两个主要好处:提高生产率和降低风险。

提高生产率

  • 改动得以局部化,所以开发时间和测试时间得以降低。与编写单个的大块代码相比,编写多个相对较小、自足的组件更为容易。你可以设计、编写简单的组件,对其进行单元测试,然后把它们忘掉——当你增加新代码时,无需不断改动已有的代码。
  • 正交的途径还能够促进复用。如果组件具有明确而具体的、良好定义的责任,就可以用其最初的实现者未曾想过的方式,把它们与新的组件组合在一起。
  • 如果你对正交的组件进行组合,生产率会有相当微妙的提高。假定某个组件做M件事情,而另一个组件做N件事情。如果它们是正交的,而你把它们组合在一起,结果就能做M*N件事情。但是,如果这两个组件是非正交的,它们就会重叠,结果能的事情就更少。通过正交的组件,你的每一份努力都能够得到更多的功能。

降低风险

  • 正交的途径能降低任何开发中固有的风险。
  • 有问题的代码区域被隔离开来。如果某个模块有毛病,它不大可能把病症扩散到系统的其余部分。要把它切掉,换成健康的新模块也更容易。
  • 所得系统更健壮。对特定区域做出的小的改动与修正,你所导致的任何问题都将局限在该区域中。
  • 正交系统很可能得到更好的测试,因为设计测试、并针对其组件运行测试更容易。
  • 你不会与特定的供应商、产品、或是平台紧绑在一起,因为与这些第三方组件的接口将被隔离在全部开发的较小部分中。

设计

大多数开发者都熟知需要设计正交的系统,尽管它们可能会使用像模块化、基于组件、或是分层这样的术语描述该过程。系统应该由一组相互协作的模块组成,每个模块都实现不依赖于其他模块的功能。有时,这些组件被组织为多个层次,每层提供一级抽象。这种分层的途径是设计正交系统的强大方式。因为每层都只使用在其下面的层次提供的抽象、在改动底层实现、而又不影响其他代码方面,你拥有极大的灵活性。分层也降低了模块间依赖关系失控的风险。

对于正交设计,有一种简单的测试方法。一旦设计好组件,问问你自己:如果我显著的改变某个特定功能背后的需求,有多少模块会受影响?在正交系统中,答案应该是“一个”。

不要依赖你无法控制的事物属性

认同正交性

正交性和DRY原则紧密相关。运用DRY原则,你是在寻求使系统中的重复降至最小;运用正交性原则,你可以降低系统的各组件的相互依赖。这样说或许有点笨拙,但如果你紧密结合DRY原则、运用正交性原则,你将会发现你开发的系统会变得更为灵活、更容易理解、更易于调试、测试和维护。

参考资料

程序员修炼之道

分类: 软件设计, 阅读 标签:

代码中的Bad Smell坏味道

2010年3月6日 admin 没有评论

最近一段时间的工作都要维护一些老代码以及做一些Code Review。代码中的那些Bad Smell真的让人很抓狂,想起之前看过的Martin Fowler的《重构》,于是就整理了一些自己常能体会到的代码中的Bad Smell.

Bad Smell列举

1.重复代码

消除重复代码的基本方法是:如果是同一个类中的重复代码就把这个方法“抽取”出来;如果是两个兄弟类之间的重复代码需要把方法抽取出来,并提升到父类中;如果是两个不相关类间的代码重复,就

2.方法过大

应该更积极进取地分解函数。我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中, 并以其用途(而非实现手法)命名。我们可以对一组或甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我 们也该毫不犹豫地那么做。关键不在于函数长度,而在于函数[做什么]和[如何做]之间的语义距离。

3.类过大

类过大往往意味着想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,代码重复、God Object之类的问题也就是接踵而至了。

解决类过大的方法就是要思考类的定义和职责,将原本一个类中完成的功能分解到其他几个类中。

4.过长参数列

方法的参数要尽量的少,超过3个以上的参数就会直接影响方法的理解。这个可以根据参数间的关联性抽取出一个对象来。

6.多层的条件语句,循环语句嵌套.

If里面嵌套If,for里面继续循环for。这种情况可以适当的将嵌套的内容抽取出来。

7.变量名不具备说明性。

变量名应该具有足够的说明性。除了常用的i,j作为循环变量。通常一些全局变量和实例变量的变量名应该足够长,并具有足够的说明性,并保证不易跟其他变量混淆。

10.方法和类的职责要具有单一性,并且名称能够具备说明性。

 

参考资料

代码的坏味道,重构,模式
refactoring-从地狱中重生
God Object
Code Smell

对象健身操

分类: 软件设计 标签:

软件开发沉思录阅读笔记二

2010年2月18日 admin 1 条评论

这是《软件开发沉思录》这本书阅读笔记的第二篇,第一篇是软件开发沉思录阅读笔记一是对第六章——对象健身操的阅读笔迹。

第七章——迭代经理是什么角色

什么是迭代经理?

ThoughtWorks的高级架构师Fred George把迭代经理描述成“面向内部的管理角色。迭代经理负责保证故事在团队中流动的顺畅,这牵涉到合理分配任务、在技能需要改变的时候建议更换团队成员。”

怎样成为好的迭代经理?

每天,迭代经理都必须倾听团队的需要并且作出回应。其主要职责就是培养一台润滑良好的“机器”,并依据要求的质量在项目范微内交付功能。

迭代经理应该在技术熟练度和业务知识之间达到一个平衡。敏捷领袖应该“同时对客户和技术都由深刻的理解,这样才能赢得开发团队的尊重。”良好的沟通技巧是必须的。迭代经理的职责之一就是维护开团队与客户,以及与管理阶层之间的关系。

同时,迭代经理必须推动、主张和保障团队成员的权利。

迭代经理不做什么?

与项目经理不同,迭代经理需要在工作第一线,与团队成员一起面对每日的工作活动。

迭代经理与项目

作为次要目标,我期望看到因为迭代经理的工作,团队成员在项目结束的时候变得更为优秀。团队内充满信任,持续提高技能——这是迭代经理的工作。

迭代经理要争取把团队拧成一根绳,让所有的成员能同舟共济。

第十章——领域标注

在过去十年里,众多软件开发者意识到:软件应用程序中最大的复杂度来自于软件要处理的实际问题领域。正因为如此,所谓“领域驱动设计”有两个前提:

  • 大部分软件项目关注的焦点是业务领域和领域逻辑;
  • 复杂的领域设计应该基于模型来进行。

换句话说,领域驱动设计将用业务领域术语表述的业务领域的面向对象模型置于软件系统的核心。数据通常保存在关系数据库里,但看待数据的主视角是领域对象,而不是数据库表和存储过程。核心业务逻辑集中保存在领域模型中,而不是散步在用户界面和应用程序的服务层里。

如果遵循领域驱动设计方法,得到的软件系统就会清晰地区分出领域模型和基础设施(及界面):前者通常较为稳定、生命周期较长;后者通常生命周期较短,并且与具体的技术(例如O/R映射或者WEB框架)相关。这里的挑战在于如何保持两者之间的分野,从而使两者能够各自保持可复用性:一方面,领域模型应该能够在不同的应用程序、不同的服务中使用,或是在技术升级以后继续使用;另一方面,基础设施应该能用于支撑各种领域模型。当然最终的目标是实现基础设施的行业标准化(无论是采用商用软件还是开源软件),从而使应用程序开发者能专注于问题领域。

分类: Agile, 软件设计, 阅读 标签: ,

面向对象的概念和术语总结和UML类图说明

2010年2月7日 admin 1 条评论

最近打算给开发和QA的同学介绍一下面向对象和UML相关的内容。这是第一篇,介绍主要面向对象的基础概念和UML类图的绘制。

概念:类似对象的一种软件抽象,创建对象的模板。

UML图:属性和操作之前可附加一个可见性修饰符。加号(+)表示具有公共可见性。减号(-)表示私有可见性。#号表示受保护的可见性。省略这些修饰符表示具有package(包)级别的可见性。如果属性或操作具有下划线,表明它是静态的。在操作中,可同时列出它接受的参数,以及返回类型。

kmstheme

接口

概念:定义了一套内聚行为的一个或多个操作特性标记的集合。接口是确保送耦合的一种强大的方法。它们允许类参与一套共同的功能集,除了支持接口意外,不需要别的类知道任何有关他的事情。接口的设计要遵循“接口分离原则”,参见什么是面相对象设计的SOLID原则(s)

UML图:接口的UML图展现的样式会有几种变化,但内容都是一样的。interface

注:在StarUML中可以通过选择Interface元素的Format->【Stereotype Display】、【Suppress Attributes】、【Suppress Operations】来调整。

interface_ui

联系(Association)

概念:实体之间的一个结构化关系表明对象是相互连接的。箭头是可选的,它用于指定导航能力。如果没有箭头,暗示是一种双向的导航能力。

UML图:

image

  • 方向性。开口箭头指出关联的方向。只有一个开口箭头的关联是单向的:它仅可以在一个方向(箭头的方向)上移动。没有箭头时,表示关联是可以在两个方向上移动。
  • 标签。标签是可选的,一般有一到两个词组成,用来描述关联。
  • 多重性(Multiplicity)。关联的多重性在线的两端标出,每个方向有一个多重性指示器。

聚合(Aggregation)

概念:有时对象会有其他对象组成。例如,飞机由机身、机翼、引擎、起落架等组成。这些全部都是聚合这个概念的例子,它表示“is part of”关系。

UML图:

uml-aac-diff-02.png

class Node
{
  private:
    vector<Node*> itsNodes;
};
     上述代码只有当子节点不会成为父节点的父节点时(即,必须是树结构,不能是图结构),才能称之
为聚合。

组合(Composition)

     概念:组合是更强形式的聚合,其中“整体”负责部分,每个“部分”对象也仅与一个整体对象联系。例如,
在任何给定时间,引擎是且仅是飞机的一部分。而且,除了飞机以外,其他对象都不能直接与引擎对象发
生交互。
        UML图:
uml-aac-diff-03.png
class Car
{
  public:
    virtual ~Car() {delete itsCarb;}
  private:
    Carburetor* itsCarb
};

依赖(Dependency)

概念:对象之间存在临时关系,只有一个原因,它们可能会相互协作。一个对象与另一个对象进行协作,它需要了解这个对象。这意味着两个对象之间必须存在对象关系或part-of关系。当两个对象之间不存在持久关联的时候,我们需要在两个类之间建立依赖。

UML图:

dependency

泛化(Generalization)

概念:表示一个更泛化的元素和一个更具体的元素之间的关系。

UML图:

image

AbstractKmsTheme被标记为抽象的(名字是斜体)。

实现(Realization)

概念:指定两个实体之间的一个合同。换言之,一个实体定义一个合同,而另一个实体保证履行该合同。

UML图

image

总结面向对象的概念和术语汇总表

术语 描述
Abstract Class抽象类 不能实例化的类
Abstraction抽象 一个项目(可能是类或者操作)的本质特征
Aggregation聚合 两个类或者组件之间的关系,定义为“is part of”
Association关联 两个类或者对象之间的关系
Attribute属性 类了解的东西(数据/信息)
Cardinality基数 表示“有多少”的概念
Class类 类似对象的一种软件抽象,创建对象的模板
Cohesion内聚 封装单元(如组件或者类)的相关程度
Component组件 一种内聚功能单元,可以独立开发、发布、由其他组件编辑组成更大的单元。
Composition组合 强类型的聚合,其中“整体”完全负责各个组成部分,每个部分对象仅与一个“整体”对象相关联
Concrete Class具体类 可以从中实例化对象的类
Coupling耦合 两个项目之间的依赖程度
Generalization泛化 表示一个更泛化的元素和一个更具体的元素之间的关系。
Inheritance继承 定义为“is a”或者“is like”的关系
Instance实例 一个对象,它是某个类的一个示例
Instantiate实例化 从类定义中创建对象
Interface接口 定义了一套内聚行为的一个或多个操作特性标记的集合
Message消息 请求星系或者执行任务
Messaging消息传递 对象之间通过发送消息相互协作的过程
Method方法 有执行值操作的类实现的一个过程(与结构化编程中的函数相似)
Multiple Inheritance多重继承 直接继承自一个以上的类
Object对象 基于类定义的人物、地点、事件、事物等等
Optionality选择性 概念“你需要它吗?”
Override 在子类中重新定义属性和/或方法,以使它们与父类中的定义有区别
Pattern 在考虑相关因素的情况下,通用问题的一个可行性解决方案
Polymorphism多态 不同的对象可以以不同的方式响应同一消息,使对象可以交互而不需要知道确切的类型
Property 在UML2中,是一个命名的值,例如,属性和关联,包括组合,指定元素(例如类或者组件)的一个特征。在Java中,属性的组合包括Getter和Setter
Single Inheritance多重继承 仅从一个类直接继承
Stereotype构造型 建模元素的一种通用用法
Subclass子类 继承自另一个类的类
Superclass父类 另一个类从中继承的类

参考

The Ojbect Primer中文版第二章

UML中的联系、聚合与组合的区别

全面认识UML类图元素

分类: 软件设计 标签: ,

Daniel-Journey Weekly Dose-2010/2/6

2010年2月6日 admin 没有评论
分类: 软件设计, 阅读 标签: