用于企业服务总线的类 USB 通用端口类型,第 1 部分: 当前 ESB 存在的问题

简介

在面向服务的架构 (SOA) 中,企业服务总线 (ESB) 是基础架构的一个至关重要的组件。ESB 用于间接地连接采用不同服务格式的应用程序,如图 1 所示。这些不同格式的服务包括 Web 服务、RESTful 服务、异步服务(比如使用 MQ 的服务)、基于 CORBA 的服务、基于 DCOM 的服务和 Java RMI。这些服务采用不同的通信协议和消息格式。例如,Web 服务使用 HTTP 作为通信协议,使用 SOAP 作为消息格式类型,而异步服务可能采用 MQ 作为通信协议,采用 XML 作为消息格式。

目前,可用的 ESB 提供了众多核心功能来连接采用不同服务格式的应用程序。但是,在这些目前可用的 ESB 中,每个应用程序使用一种特定类型的端口来连接 ESB,如图 1 所示。给定应用程序使用的端口类型由该应用程序使用的通信协议和消息格式类型确定。应用程序在对这些特定端口类型的使用上存在诸多问题。您将在本期(本系列的第一部分)后面一节中了解这些问题和不便性。

应用程序使用特定端口类型连接 ESB
应用程序使用特定端口类型连接 ESB

在本期的下一节中,我们将首先简单介绍 ESB,列出应用程序之间的点对点连接中固有的一些问题,以及如何使用 ESB 帮助解决这些问题。接下来,在本期的第三节中,您将了解当前可用 ESB 中各种连接的应用程序对不同端口类型的使用所引起的各种问题。本期最后一节将提供一些总结性评论。

ESB 核心功能

本节的目的在于简单介绍 ESB,简短描述使用点对点连接方法将应用程序与各种不同的服务相连接的 3 种核心功能和相关的问题。

 

连接可扩展性和路由

设想一下一家需要集成 6 个应用程序的中型公司。连接这 6 个应用程序所需的连接略图如图 2 所示。

使用点对点集成方法的 6 个应用程序连接
使用点对点集成方法的应用程序连接

您可以看到,本例中所需要的连接数目为 15 个。这一数目相当于集成模式中应用程序不同配对的数量。同样地,可以看到,如果此例中有 10 个应用程序,需要的连接数量将为 45。因此,我们可以得出结论,随着应用程序数量增加,需要的连接数量会以非线性方式迅速增加。这被称作一种毛团问题,因为它可能阻碍任何网络。事实上,可以发现,如果企业要集成 N 个应用程序,点对点方法中需要的连接数量为

N(N-1)/2

这很容易理解,因为应用程序不同配对数量也为 N(N-1)/2。因此,我们可以得出结论,点对点集成模式并不适用于大型企业中的集成,应该找到另一种扩展能力更高的解决方案。

企业服务总线为连接可扩展性问题提供了一个优秀的解决方案,因此适合需要集成大量应用程序的大中型企业。在企业服务总线交互样式中,应用程序之间不直接进行交互。相反,应用程序连接到总线,总线提供途径以供应用程序之间建立连接。这种间接交互如图 1 所示,其中展示了 6 个应用程序通过使用企业服务总线进行交互。图 1 要注意的最重要一点是,所需的连接数仅为 6。此数量比点对点模式中 6 个应用程序所需的连接数(即 15)少得多。在此图中要注意的另一点是,所需的连接数量等于集成的应用程序数量。因此,如果我们有 10 个应用程序,仅需要 10 个连接。相对于点对点方法中需要的连接数量(即 45),这个连接数量非常小。

ESB 通过提供基于内容和上下文的路由来实现间接连接应用程序的功能。因此,请求应用程序不需要知道目标应用程序的地址,或者甚至不用知道哪些应用程序在提供该服务。

 

协议错误匹配和协议转换

通信协议错误匹配问题出现的原因在于,大型企业中的各个应用程序通常会采用不同的协议进行通信。换句话说,在真实世界中,这些协议会不断增生,其中包括 HTTP、HTTPS、JRMP、IIOP 和 JMS。由于这种增生现象,服务用户应用程序与服务提供者应用程序之间有时会发生协议错误匹配。因此,没有一种工具来将一种通信协议转换为另一种,服务用户应用程序将无法调用服务提供者应用程序所提供的服务。此问题的图略如图 3 所示。

协议错误匹配问题
协议错误匹配问题

因此,ESB 中应该包含的另一个核心功能就是一个可将一种协议转换为另一种的协议转换工具。使用此转换工具,采用不同协议的应用程序就可彼此交互。值得指出的是,当前可用的商用 ESB 已提供协议转换支持。例如,我们常常看到从 HTTP(用于异步消息交换)到 MQ 异步消息的转换。出于此用途,可以使用一个关联 ID 来将请求和响应 MQ 消息联系起来。

消息/数据格式错误匹配和转换

另一种异构性问题是数据/消息格式错误匹配的问题。此问题指的是这样一个事实,有时服务用户提供的数据格式与服务提供者应用程序所需的数据格式不太匹配。这会阻止应用程序彼此交互,如图 4 所示。

数据格式错误匹配问题
流程摘要

因此,ESB 需要提供的另一种核心功能是数据或消息转换。大多数可用的商用 ESB 都采用不同技术提供了此功能。例如,可采用 XML 样式表实现不同 XML 格式(包括 SOAP)之间转换。当此功能与其他两种 ESB 功能相结合时,应用程序之间可以轻易地进行连接和交互,即使在它们的接口和协议不完全匹配时。

其他功能

除了满足这 3 种功能需求外,ESB 还实现了服务来满足非功能性需求,比如性能和可靠性、审计和安全性。此外,许多商业 ESB 产品还提供了其他一些可选的服务,比如数据扩充、消息分发、更正和监视。

这些功能和非功能需求可由单个产品或一组产品来满足。换句话说,ESB 基本来讲是一种模式,它没有必要实现为单个产品。

当前可用 ESB 的问题

在当前可用 ESB 中,每个应用程序使用一种特定的端口类型来连接 ESB,如图 1 所示。端口类型由应用程序用于公开其功能的服务类型决定。例如,通过 Web 服务公开其功能的应用程序必须使用这样的端口类型,它提供 HTTP 作为通信协议,提供 SOAP 作为消息格式。换句话说,给定的应用程序必须通过一种具体的端口类型连接 ESB,端口类型由应用程序使用的通信协议和消息格式确定。出于许多原因,这种应用程序通过特定类型的端口进行连接的方法既不灵活,也不方便。其中一些原因是:

  • 许多时候,由于需求或运行时环境改变,应用程序希望切换通信协议。但是,这也需要更改 ESB 一端的端口类型,而且几乎肯定需要构建另一种类型的端口。这无疑会消耗不少时间和资源。
  • 一种常见情形是,一个应用程序希望使用多种协议来公开不同的功能,大型机 COBOL 应用程序常常会遇到此情况,给定应用程序使用 Web 服务公开它的部分功能,而与此同时,它使用 MQ 等消息传递协议公开另一部分功能。再一次,这需要在 ESB 一端构建新的端口类型来同时提供两种类型的协议,这非常不方便,因为它会耗时又耗资源。
  • 在目前可用 ESB 中,很难将 ESB 的用途扩大到新构建的通信协议。这是因为,它需要同时更改应用程序一端和 ESB 一端。在 ESB 一端,需要开发全新的端口类型。
  • 因为每个应用程序必须通过一种特定类型的端口连接到 ESB,所以需要花许多工夫来配置 ESB,以供应用程序使用。这些工夫可能包括构建一种新端口类型、将它部署在 ESB 中,并配置 ESB 以使用这个特定的端口。所有这些都使得难以使用 ESB 连接大量应用程序,并进而导致可扩展性问题。
  • 另外,由于每个端口必须独立部署和配置,所以很难维护和更新 ESB。

 
 
 

UNIX树型目录结构

UNIX操作系统采用树型带勾连的目录结构,如下图所示。在这种结构中,一个文件的名字是由根目录到该文件的路径上的所有节点名按顺序构成的,相互之间用“/”分开。如文件prog的全路径名为:/usr/smith/prog,根目录用“/”表示。

根文件系统常用目录  根文件系统的常用目录举例如下表:

/bin 大部分可执行的UNIX命令和共用程序
/dev 设备文件,如/dev/cd0
/etc 系统管理命令和数据文件
/lib C程序库
/usr 存放用户的家目录和用户共用程序或文件
/tmp 临时工作目录,存放一些临时文件

家目录
UNIX在创建用户名时,自动在/usr目录下创建与用户名同名的子目录,如/usr/smith子目录,这个子目录成为此用户的家目录(Home Directory)。家目录中有一个文件比较特殊:“.profile”(或“.login”)文件。当以该家目录的用户名登录UNIX时,会自动执行“.profile”文件。它有点类似于DOS的AUTOEXEC.BAT文件。“.profile”是B/K shell的启动文件,而“.login”是C shell的启动文件。“.profile”文件中常有一行:

PATH=$HOME:$HOME/bin

其中,PATH类似DOS的PATH,而$HOME表示家目录。

 

 

 

UNIX文件类型

在UNIX中文件共分为四种:

1) 一般文件(ordinary file),分为: 1. 文本文件2. 二进制文件
2) 目录文件(directory)
3) 特殊文件(special file),分为: 1. 块设备文件2. 字符设备文件
4) 符号链接文件(symbolic links)

文件类型标识  在用”ls -l”命令显示文件目录时的用下列符号表示不同的文件类型:

- 普通文件
d 目录文件
b 块设备文件
c 字符设备文件
p 管道设备
l 符号链接文件

举例:

$ ls -l
-rw——-. 1 root root 2386 Dec 20 06:22 anaconda-ks.cfg
drwxr-xr-x. 2 root root 4096 Dec 19 22:27 Desktop

其中,第一列的”-”表示anaconda-ks.cfg是普通文件,”d”表示Desktop为目录文件。

 

 

 

如何向妻子解释设计模式

设计模式是什么?

Shubho:通过我们关于面向对象设计原则(OODP,即SOLID原则)的对话,我想你已经对面向对象设计原则(OODP)有了基本的认识。希望你不要介意我把对话分享到博客上。你可以在这找到它:<如何向妻子解释OOD>.

设计模式是这些原则在某些特定公共场景下标准化的应用,接下来让我们通过一些例子学习什么是设计模式。

Farhana: 当然,我喜欢例子。

Shubho: 让我们以汽车为例讨论一下。汽车是一个很复杂的对象,由成千上万的其它对象组成,如发动机,车轮,方向盘,车座,车体等等其他不同的部分或部件。

汽车部件

当装配汽车时,制造商需要集中并装配这些更小的自成汽车子系统的不同部件。而这些不同的小部件同样也是复杂的对象,其它制造商同样要生产并组装它们。在生产汽车时,汽车公司并不会为怎么生产组装这些部件操心(前提是他们要确保这些对象/设备的质量)。当然,汽车制造商更加关心怎么装配这些不同部件以便能生产不同型号的汽车。

通过遵循不同的设计,组装不同的部件,生产不同型号的汽车

Farhana: 汽车制造公司必须有如何生产不同型号汽车的设计图或蓝图,对吗?

Shubho: 当然,并且这些设计都是良好的,他们花费大量的时间和精力来做这些设计。一旦设计完成,生产汽车就仅仅是照葫芦画瓢了。

Farhana: 嗯。如果事先有一些好的设计,就能在短时间内遵照这些设计生产不同产品,并且制造商在每次生产某一个型号产品时就不需要重新设计或重新发明车轮,他们只需要按照已有的设计办事就行了。

生产不同型号产品(汽车)的不同设计图

Shubho: 你抓到重点了。现在假设我们是软件生产商,我们使用基于需求而来的不同组件或功能构建各种不同的软件程序。当生产这些不同软件系统时,我们常常需要为一些不同软件系统中存在的相同情况开发代码,对吗?

Farhana: 是的,在开发不同软件程序时经常遇到相同的设计问题。

Shubho: 我们尝试使用面向对象的方式开发软件,并尝试应用OOPD来让代码能易于维护,可复用,可扩展。无论什么时候,当我们遇到这些设计问题时,如果我们有一组经过谨慎开发,良好测试的对象以供使用会不会更好呢?

Farhana: 是的,这样能够节省时间,生产出更好的软件,且利于以后维护。

Shubho: 很好!从设计上来说,它的好处是你不需要开发那些对象。经过多年发展,人们已经遇到过一些类似的设计问题,并已经形成有一些公认的,良好的已标准化的设计方案。我们称之为设计模式。

我们一定好感谢四人组,他们在《设计模式:可复用面向对象软件设计》中总结出了23种基本的设计模式。四人组由Erich Gamma, Richard Helm, Ralph Johnson, 和John Vlissides组成。实际中有很多面向对象设计模式,但这23种模式被公认为是所有其他设计模式的基础。

Farhana: 我能发明一个新的模式吗?这可能吗?

Shubho: 当然,亲爱的,为什么不能呢?!设计模式不是由科学家发明创造的。它们是被发现找到的。这意味着任何通用问题场景中都有一些好的设计方案在那。如果我们能够指出一个能够解决一个新的设计相关问题的面向对象设计,那么这将会是一个由我们定义的新的设计模式。谁知道呢?!如果我们发现找到一些设计模式,或许将来有一天人们会称我们为二人组,哈哈。

Fahana: :)

我们将如何学习设计模式?

Shubho: 我一直认为例子是学习的最好途径。在我们的学习方法中,我们不会先讨论理论后讨论实现。我认为这是很糟糕的方式。设计模式不是基于理论的发明。事实上,问题场景首先出现,其次是基于这些问题的来龙去脉和需求,然后是一些设计方案的演化,最后其中的一些被标准化为模式。所以对每一个我们讨论的设计模式,我们将尝试理解并分析一些现实生活中的例子,然后一步步尝试归纳一个设计,并最后总结一些与某些模式匹配设计。设计模式就是在这些相似过程中发现的。你认为呢?

Farhana:我想这种方式对我更有用。如果我能通过分析问题和归纳方案得出设计模式,我就不用死记那些设计模式和定义了。请按照你的方式继续。

一个常见的设计问题和它的解决方案

Shubho: 让我们考虑下面的场景:

我们房间里有些电器(电灯,风扇等)。这些设备按照某些方式布局,并由开关控制。任何时候你都能替换或排查一个电器而不用碰到其他东西。例如,你可以换一个电灯而不需要换开关。同样,你可以换一个开关或排查它而不需要碰到或替换相应的电灯或风扇;甚至你可以用把电灯连接到风扇的开关上,把风扇连到电灯的开关上,而不需要碰到开关。

电器:风扇和电灯

风扇和电灯的两种不同开关,一个普通点,另一个别致点

Farhana: 是的,但就是这样子,对吗?

Shubho: 是的,确实如此,就该如此布局。当不同东西联系在一起时,它们应该按照一定方式联系:修改或替换一个系统时不会影响到另一个,或者说即便有,也应该最小化。这能够让你的系统易于管理,且成本低。想想一下,如果改一下房间里的灯同时需要改开关,你会乐意在你房子上花钱并安装这个系统吗?

Farhana: 当然不会。

Shubho: 现在,让我们思考一下电灯或风扇如何连接到开关上才能达到改变一个不会影响到另一个。你认为该如何?

Farhana: 用电线!

Shubho: 很好。把电灯/风扇和开关联系到一起的是电线和电器布局。我们可以它们看做不同系统间相互联系的桥梁。其基本的思想是,一个事物不能和另一外一个事物直接联系。当然啦,它们应当通过某些桥梁或接口联系在一起。用软件术语来说,这叫“松耦合”。

Farhana: 我知道了。

Shubho: 现在,让我们尝试推断在电灯/风扇和开关例子中的几个关键问题,并尝试推断它们是如何设计并联系起来的。

Farhana: 好,我们试一下。

例子中我们有开关,可能有几种开关,如普通的开关,漂亮的开关,但通常来说它们还是开关,并且每种开关都能够打开和关闭。

所以下面我们会有一个开关基类Switch:

public class Switch {
public void On() {
//打开开关
}
public void Off() {
//关闭开关
}
}

接下来我们可以有一些具体的开关,例如一个漂亮开关,一个普通开关等等,当然,我们会让类FancySwitch和NormalSwitchnd继承类Switch:

public class NormalSwitch : Switch {
}
public class FancySwitch : Switch {
}

这里的两个具体类有自己的特征和行为,只是此时此刻,我们简单化以下。

Shubho: 非常棒,接下来电灯和风扇怎么办?

Farhana: 我试试. 根据OODP的开放闭合原则,我们知道只要可能,就应该尝试抽象,对吗?

Shubho: 对

Farhana: 跟开关不一样,风扇和电灯等是两种不同的事物。对于开关,我们能够使用一个开关基类Switch,但风扇和电灯是两个不同的事物,相比定义一个基类,接口可能更合适。一般来说,他们都是电器。所以我们可以定义一个接口,如IElectricalEquipment,作为对电灯和风扇的抽象,可以吗?

Shubho: 可以

Farhana: 好,每种电器都有些相同的功能。他们能够打开和关闭。所以接口可能如下:

public interface IElectricalEquipment {
void PowerOn(); //每种电器都能打开
void PowerOff(); //每种电器都能关闭
}

Shubho: 太好了,你很善于抽象东西。现在我们需要一座桥梁。在现实中,电线是桥梁。在我们对象设计中,开关知道如何打开和关闭电器,电器以某种方式联系到开关。这里我们没有电线,让电器连接到开关的唯一方式是封装。

Farhana: 是的,但开关不能直接知道风扇或电灯。开关应当知道一个电器IElectricalEquipment能够打开或关闭。这意味着,ISwitch应该有一个IElectricalEquipment实例,对吗?

Shubho: 对,对风扇或电灯的封装的实例是一个桥梁。所以让我们修改Switch类以便封装一个电器:

public class Switch {
public IElectricalEquipment equipment {
get;
set;
}
public void On() {
//开关打开
}
public void Off() {
//开关关闭
}
}

Farhana: 明白。让我们定义真实的电器:风扇和电灯。如我所见,一般来说它们都是电器,所以它们都简单实现了IElectricalEquipment接口。

下面是风扇类:

public class Fan : IElectricalEquipment {
public void PowerOn() {
Console.WriteLine(“风扇打开”);
}
public void PowerOff() {
Console.WriteLine(“风扇关闭”);
}
}

下面是电灯类:

public class Light : IElectricalEquipment {
public void PowerOn() {
Console.WriteLine(“电灯打开”);
}
public void PowerOff() {
Console.WriteLine(“电灯关闭”);
}
}

Shubho:太好了。现在让开关工作。当开关打开关闭的时候它应当能够打开关闭电器(它连接到的) 。

这里的关键点是:

  • 当开关按下开时,连接的电器也应该打开。
  • 当开关按下关时,连接的电器也应该关闭。

大致的代码如下:

static void Main(string[] args) {
//构造电器设备:风扇,开关
IElectricalEquipment fan = new Fan();
IElectricalEquipment light = new Light();//构造开关
Switch fancySwitch = new FancySwitch();
Switch normalSwitch = new NormalSwitch();//把风扇连接到开关
fancySwitch.equipment = fan;//开关连接到电器,那么当开关打开或关闭时电器应该打开/关闭
fancySwitch.On();
fancySwitch.Off();//把电灯连接到开关
fancySwitch.equipment = light;
fancySwitch.On(); //打开电灯
fancySwitch.Off(); //关闭电灯
}

Farhana: 明白。开关的On()方法应当内部调用电器的TurnOn()方法,Off()方法应当内部调用TurnOff()方法,所以开关类Switch应如下:

public class Switch {
public IElectricalEquipment equipment {
get;
set;
}
public void On() {
Console.WriteLine(“开关打开”);
equipment.PowerOn();
}
public void Off() {
Console.WriteLine(“开关关闭”);
equipment.PowerOff();
}
}

Shubho: 很好。这自然允许你把风扇从一个开关接到另一个上。不过你看,反过来也可以。这意味着你可以改变风扇或电灯的开关而不需要碰到风扇或电灯。例如,你可以很轻松的把点灯的开关从FancySwitch换到NormalSwitch上,如下:

normalSwitch.equipment = light;
normalSwitch.On(); //打开电灯
normalSwitch.Off(); //关闭电灯

你看,连接一个抽象电器到一个开关(通过封装)能够让你改变开关和电器而不会对对方产生影响。这个设计是优雅的,良好的。四人组为该模式取名为:桥接模式。

Farhana: 太棒了。我想我明白这个了。从根本上说,两个系统不应当直接联系或依赖与对方。 当然,他们应该联系或依赖于抽象(如依赖倒置原则和开放闭合原则所讲),所以他们是松耦合的,因此我们可以在需要时改变我们的实现而不会对系统其他部分产生过多影响。

Shubho: 你理解了,亲爱的.我们看下桥接模式的定义:

“将抽象部分与实现部分分离,使它们都可以独立的变化”

你看我们的实现完美遵循该定义。如果你有一个类设计器(如Visual Studio或其他支持该功能的IDE环境),你会看到类似的如下类图:

桥接模式类图

在这里, Abstraction 是开关基类Switch。 RefinedAbstraction 是具体开关类 (FancySwitchNormalSwitch 等等。)。 Implementor 是电器接口IElectricalEquipmentConcreteImplementorA 和ConcreteImplementorB 是电灯类Light和风扇类Fan。

Farhana: 问你个问题,只是好奇啊。如你所说有很多其他的设计模式,为什么你以桥接模式开始呢?有重要原因吗?

Shubho: 这个问题很好。是的,我以桥接模式而不以其他开始是因为一个理由。我认为桥接模式是所有面向对象模式的基础。理由如下:

  • 它教导如何思考抽象,这是面向对象设计模式的关键概念。
  • 它实现了基本的OOD原则。
  • 它容易理解。
  • 如果正确理解该模式,学习其他模式会很容易。

Farhana: 你认为我理解的对吗?

Shubho: 我认为你理解的非常正确。

Farhana: 那么接下来是什么?

Shubho: 通过理解桥接模式,我们仅仅是开始理解设计模式的思想。在我们接下的对话中,我们将会学习其他的设计模式,我希望你不会觉得它们无聊。

Farhana:不会的,相信我。

原文链接:http://www.cnblogs.com/niyw/archive/2011/05/30/2062071.html

 

如何向妻子解释OOD

前言

此文译自CodeProject上<How I explained OOD to my wife>一文,该文章在Top Articles上排名第3,读了之后觉得非常好,就翻译出来,供不想读英文的同学参考学习。

作者(Shubho)的妻子(Farhana)打算重新做一名软件工程师(她本来是,后来因为他们孩子出生放弃了),于是作者就试图根据自己在软件开发设计方面的经验帮助她学习面向对象设计(OOD)。

自作者从事软件开发开始,作者常常注意到不管技术问题看起来多复杂,如果从现实生活的角度解释并以对答的方式讨论,那么它将变得更简单。现在他们把在OOD方面有些富有成效的对话分享出来,你可能会发现那是一种学习OOD很有意思的方式。

下面就是他们的对话:

OOD简介

Shubho:亲爱的,让我们开始学习OOD吧。你了解面向对象原则吗?

Farhana:你是说封装,继承,多态对吗?我知道的。

Shubho:好,我希望你已了解如何使用类和对象。今天我们学习OOD。

Farhana:等一下。面向对象原则对面向对象编程(OOP)来说不够吗?我的意思是我会定义类,并封装属性和方法。我也能根据类的关系定义它们之间的层次。如果是,那么还有什么?

Shubho:问得好。面向对象原则和OOD实际上是两个不同的方面。让我给你举个实际生活中的例子帮你弄明白。

再你小时候你首先学会字母表,对吗?

Farhana:嗯

Shubho:好。你也学了单词,并学会如何根据字母表造词。后来你学会了一些造句的语法。例如时态,介词,连词和其他一些让你能造出语法正确的句子。例如:

“I” (代词) “want” (动词) “to” (介词) “learn” (动词) “OOD”(名词)。

看,你按照某些规则组合了单词,并且你选择了有某些意义的正确的单词结束了句子。

Farhana:OK,这意味着什么呢?

Shubho:面向对象原则与这类似。OOP指的是面向对象编程的基本原则和核心思路。在这里,OOP可以比作英语基础语法,这些语法教你如何用单词构造有意义且正确的句子,OOP教你在代 码中构造类,并在类里封装属性和方法,同时构造他们之间的层次关系。

Farhana:嗯..我有点感觉了,这里有OOD吗?

Shubho:马上就有答案。现在假定你需要就某些主题写几篇文章或随笔。你也希望就几个你擅长主体写几本书。对写好文章/随笔或书来说,知道如何造句是不够的,对吗?为了使读者能更轻   松的明白你讲的内容,你需要写更多的内容,学习以更好的方式解释它。

Farhana:看起来有点意思…继续。

Shubho:现在,如果你想就某个主题写一本书,如学习OOD,你知道如何把一个主题分为几个子主题。你需要为这些题目写几章内容,也需要在这些章节中写前言,简介,例子和其他段落。   你需要为写个整体框架,并学习一些很好的写作技巧以便读者能更容易明白你要说的内容。这就是整体规划。

在软件开发中,OOD是整体思路。在某种程度上,设计软件时,你的类和代码需能达到模块化,可复用,且灵活,这些很不错的指导原则不用你重新发明创造。确实有些原则你已经在你的类和对象中已经用到了,对吗?

Farhana:嗯…有个大概的印象了,但需要继续深入。

Shubho:别担心,你马上就会学到。我们继续讨论下去。

为什么要OOD?

Shubho:这是一个非常重要的问题。当我们能很快地设计一些类,完成开发并发布时,为什么我们需要关心OOD?那样子还不够吗?

Farhana:嗯,我早先并不知道OOD,我一直就是开发并发布项目。那么关键是什么?

Shubho:好的,我先给你一句名言:

走在结冰的河边不会湿鞋,开发需求不变的项目畅通无阻(Walking on water and developing software from a specification are easy if both are frozen)

-Edward V. Berard

Farhana:你的意思是软件开发说明书会不断变化?

Shubho:非常正确!软件开发唯一的真理是“软件一定会变化”。为什么?

因为你的软件解决的是现实生活中的业务问题,而现实生活中得业务流程总是在不停的变化。

假设你的软件在今天工作的很好。但它能灵活的支持“变化”吗?如果不能,那么你就没有一个设计敏捷的软件。

Farhana:好,那么请解释一下“设计敏捷的软件”。

Shubho:”一个设计敏捷的软件能轻松应对变化,能被扩展,并且能被复用。”

并且应用好”面向对象设计”是做到敏捷设计的关键。那么,你什么时候能说你在代码中很好的应用了OOD?

Farhana:这正是我的问题。

Shubho:如果你代码能做到以下几点,那么你就正在OOD:

  • 面向对象
  • 复用
  • 能以最小的代价满足变化
  • 不用改变现有代码满足扩展

Farhana:还有?

Shubho:我们并不是孤立的。很多人在这个问题上思考了很多,也花费了很大努力,他们试图做好OOD,并为OOD指出几条基本的原则(那些灵感你能用之于你的OOD)。他们最终也确实总结出了一些通用的设计模式(基于基本的原则)。

Farhana:你能说几个吗?

Shubho:当然。这里有很多涉及原则,但最基本的是叫做SOLID的5原则(感谢Uncle Bob,伟大OOD导师)。

S = 单一职责原则 Single Responsibility Principle
O = 开放闭合原则 Opened Closed Principle
L = Liscov替换原则 Liscov Substitution Principle
I = 接口隔离原则 Interface Segregation Principle
D = 依赖倒置原则 Dependency Inversion Principle

接下去,我们会仔细探讨每一个原则。

单一职责原则

Shubho:我先给你展示一张海报。我们应当谢谢做这张海报的人,它非常有意思。

单一职责原则海报

它说:”并不是因为你能,你就应该做”。为什么?因为长远来看它会带来很多管理问题。

从面向对象角度解释为:”引起类变化的因素永远不要多于一个。

或者说”一个类有且只有一个职责”。

Farhana:能解释一下吗?

Shubho:当然,这个原则是说,如果你的类有多于一个原因会导致它变化(或者多于一个职责),你需要一句它们的职责把这个类拆分为多个类。

Farhana:嗯…这是不是意味着在一个类里不能有多个方法?

Shubho:不。你当然可以在一个类中包含多个方法。问题是,他们都是为了一个目的。如今为什么拆分是重要的?

那是因为:

  • 每个职责是轴向变化的;
  • 如果类包含多个职责,代码会变得耦合;

Farhana:能给我一个例子吗?

Shubho:当然,看一下下面的类层次。当然这个例子是从Uncle Bob那里得来,再谢谢他。

违反单一职责原则的类结构图

这里,Rectangle类做了下面两件事:

  • 计算矩形面积;
  • 在界面上绘制矩形;

并且,有两个应用使用了Rectangle类:

  • 计算几何应用程序用这个类计算面积;
  • 图形程序用这个类在界面上绘制矩形;

这违反了SRP(单一职责原则);

Farhana:如何违反的?

Shubho:你看,Rectangle类做了两件事。在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:

在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;

图形应用中Rectangle类的变化可能导致计算几何应用变化,编译和测试,反之亦然;

Farhana:有点意思。那么我猜我们应该依据职责拆分这个类,对吗?

Shubho:非常对,你猜我们应该做些什么?

Farhana:当然,我试试。下面是我们可能要做的:

拆分职责到两个不同的类中,如:

  • Rectangle:这个类应该定义Area()方法;
  • RectangleUI:这个类应继承Rectangle类,并定义Draw()方法。

Shubho:非常好。在这里,Rectangle类被计算几何应用使用,而RectangleUI被图形应用使用。我们甚至可以分离这些类到两个独立的DLL中,那会允许我们在变化时不需要关心另一个就可以实现它。

Farhana:谢谢,我想我明白SRP了。SRP看起来是把事物分离成分子部分,以便于能被复用和集中管理。我们也不能把SRP用到方法级别吗?我的意思是,我们可以写一些方法,它们包含做很多事的代码。这些方法可能违反SRP,对吗?

Shubho:你理解了。你应当分解你的方法,让每个方法只做某一项工作。那样允许你复用方法,并且一旦出现变化,你能购以修改最少的代码满足变化。

开放闭合原则

Shubho:这里是开放闭合原则的海报

开放闭合原则海报

从面向对象设计角度看,它可以这么说:”软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。

通俗来讲,它意味着你应当能在不修改类的前提下扩展一个类的行为。就好像我不需要改变我的身体而可以穿上衣服。

Farhana:有趣。你能够按照你意愿穿上不同的衣服来改变面貌,而从不用改造身体。你对扩展开放了,对不?

Shubho:是的。在OOD里,对扩展开发意味着类或模块的行为能够改变,在需求变化时我们能以新的,不同的方式让模块改变,或者在新的应用中满足需求。

Farhana:并且你的身体对修改是闭合的。我喜欢这个例子。当需要变化时,核心类或模块的源代码不应当改动。你能用些例子解释一下吗?

Shubho:当然,看下面这个例子。它不支持”开放闭合”原则。

违反开发闭合原则的类结构

你看,客户端和服务段都耦合在一起。那么,只要出现任何变化,服务端变化了,客户端一样需要改变。

Farhana:理解。如果一个浏览器以紧耦合的方式按照指定的服务器(比如IIS)实现,那么如果服务器因为某些原因被其他服务器(如Apache)替换了,那么浏览器也需要修改或替换。这确实很可怕!

Shubho:对的。下面是正确的设计。

遵循开放闭合原则的类结构

在这个例子中,添加了一个抽象的服务器类,客户端包含一个抽象类的引用,具体的服务类实现了抽象服务类。那么,因任何原因引起服务实现发生变化时,客户端都不需要任何改变。

这里抽象服务类对修改是闭合的,实体类的实现对扩展是开放的。

Farhana:我明白了,抽象是关键,对吗?

Shubho:是的,基本上,你抽象的东西是你系统的核心内容,如果你抽象的好,很可能在扩展功能时它不需要任何修改(就像服务是一个抽象概念)。如果在实现里定义了抽象的东西(比如IIS服务器实现的服务),代码要尽可能以抽象(服务)为依据。这会允许你扩展抽象事物,定义一个新的实现(如Apache服务器)而不需要修改任何客户端代码。

Liskov’s 替换原则

Shubho:”Liskov’s替换原则(LSP)”听起来很难,却是很有用的基本概念。看下这幅有趣的海报:

Liskov替换原则海报

这个原则意思是:”子类型必须能够替换它们基类型。

或者换个说法:”使用基类引用的函数必须能使用继承类的对象而不必知道它。

Farhana:不好意思,听起来有点困惑。我认为这个OOP的基本原则之一。也就是多态,对吗?为什么一个面向对象原则需要这么说呢?

Shubho:问的好。这就是你的答案:

在基本的面向对象原则里,”继承”通常是”is a“的关系。如果”Developer” 是一个”SoftwareProfessional”,那么”Developer”类应当继承”SoftwareProfessional”类。在类设计中”Is a“关系非常重要,但它容易冲昏头脑,结果使用错误的继承造成错误设计。

Liskov替换原则“正是保证继承能够被正确使用的方法。

Farhana:我明白了。有意思。

Shubho:是的,亲爱的,确实。我们看个例子:

Liskov替换原则类结构图

这里,KingFisher类扩展了Bird基类,并继承了Fly()方法,这看起来没问题。

现在看下面的例子:

违反Liskov替换原则类结构图

Ostrich(鸵鸟)是一种鸟(显然是),并从Bird类继承。它能飞吗?不能,这个设计就违反了LSP。

所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类,Ostrich应该继承与它。

Farhana:好,明白了。那么让我来试着指出为什么LSP这么重要:

  • 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
  • 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;

对吗?

Shubho:非常正确。你能设计对象,使用LSP做为一个检查工作来测试继承是否正确。

接口分离原则

Shubho:今天我们学习”接口分离原则”,这是海报:

接口分离原则海报

Farhana:这是什么意思?

Shubho:它的意思是:”客户端不应该被迫依赖于它们不用的接口。”

Farhana:请解释一下。

Shubho:当然,这是解释:

假设你想买个电视机,你有两个选择。一个有很多开关和按钮,它们看起来很混乱,且好像对你来说没必要。另一个只有几个开关和按钮,它们很友好,且适合你使用。假定两个电视机提供同样的功能,你会选哪一个?

Farhana:当然是只有几个开关和按钮的第二个。

Shubho:对,但为什么?

Farhana:因为我不需要那些看起来混乱又对我没用的开关和按钮。

Shubho:以便外部能够知道这些类有哪些可用的功能,客户端代码也能根据接口来设计.现在,如果接口太大,包含很多暴露的方法,在外界看来会很混乱.接口包含太多的方法也使其可用性降低,像这种包含了无用方法的”胖接口”会增加类之间的耦合.你通过接口暴露类的功能,对.同样地,假设你有一些类,

这也引起了其他问题.如果一个类想实现该接口,那么它需要实现所有的方法,尽管有些对它来说可能完全没用.所以说这么做会在系统中引入不必要的复杂度,降低可维护性或鲁棒性.

接口隔离原则确保实现的接口有他们共同的职责,它们是明确的,易理解的,可复用的.

Farhana:你的意思是接口应该仅包含必要的方法,而不该包含其它的.我明白了.

Shubho:非常正确.一起看个例子.

下面是违反接口隔离原则的一个胖接口

注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞).

Farhana:确实如此。那么这个接口必须拆分了?

Shubho:是的。这个”胖接口”应该拆分未两个不同的接口,IBird和IFlyingBird,IFlyingBird继承自IBird.

这里如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird.

Farhana:所以回头看包含了很多开关和按钮的电视机的例子,电视机制造商应该有一个电视机的图纸,开关和按钮都在这个方案里。不论任何时候,当他们向制造一种新款电视机时,如果他们想复用这个图纸,他们将需要在这个方案里添加更多的开关和按钮。那么他们将没法复用这个方案,对吗?

Shubho:对的。

Farhana:如果他们确实需要复用方案,它们应当把电视机的图纸份为更小部分,以便在任何需要造新款电视机的时候复用这点小部分。

Shubho:你理解了。

依赖倒置原则

Shubho:这是SOLID原则里最后一个原则。这是海报

它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象

Shubho:考虑一个现实中的例子。你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?

Farhana:是的

Shubho:好,它们没有一个是严格的构建在一个单一单元里;换句话说,它们都是可插拔的,因此当引擎或车轮出问题时,你可以修理它(而不需要修理其它部件),甚至可以换一个。

在替换时,你仅需要确保引擎或车轮符合汽车的设计(如汽车能使用任何1500CC的引擎或任何18寸的车轮)。

当然,汽车也可能允许你在1500CC引擎的地方安装一个2000CC的引擎,事实上对某些制造商(如丰田汽车)是一样的。

现在,如果你的汽车的零部件不具备可插拔性会有什么不同?

Farhana:那会很可怕!因为如果汽车的引擎出故障了,你可能修理整部车或者需要买一个新的。

Shubho:是的,那么该如何做到”可插拔性”呢?

Farhana:这里抽象是关键,对吗?

Shubho:是的,在现实中,汽车是高级模块或实体,它依赖于低级模块或实体,如引擎或车轮。

相比直接依赖于引擎或车轮,汽车应依赖于某些抽象的有规格的引擎或车轮,以便于如果任何引擎或车轮符合抽象,那么它们都能组合到汽车中,汽车也能跑动。

一起看下面的类图

Shubho:注意到上面Car类有两个属性,它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。

Farhana:所以,如果代码中不用依赖倒置,我们将面临如下风险:

  • 使用低级类会破环高级代码;
  • 当低级类变化时需要很多时间和代价来修改高级代码;
  • 产生低复用的代码;

Shubho:你完全掌握了,亲爱的!

 总结

Shubho:除SOLID原则外还有很多其它的面向对象原则。如:

“组合替代继承”:这是说相对于继承,要更倾向于使用组合;

“笛米特法则”:这是说”你的类对其它类知道的越少越好”;

“共同封闭原则”:这是说”相关类应该打包在一起”;

“稳定抽象原则”:这是说”类越稳定,越应该由抽象类组成”;

Farhana:我应该学习那些原则吗?

Shubho:当然可以。你可以从整个网上学习。仅仅需要Google一下那些原则,然后尝试理解它。当然如果有需要,尽管问我。

Farhana:在那些设计原则之上我听说过很多设计模式。

Shubho:对的。设计模式只是对一些经常出现的场景的一些通用设计建议。这些灵感主要来自于面向对象原则。你可以把设计模式看作”框架”,把OOD原则看作”规范”.

Farhana:那么接下去我将学习设计模式吗?

Shubho:是的,亲爱的。

Farhana:那会很有意思,对吗?

Shubho:是,那确实令人兴奋。

原文链接:http://www.cnblogs.com/niyw/archive/2011/01/25/1940603.html

 

 

 

进程和线程

什么是系统进程
进程是指在系统中正在运行的一个应用程序的实例;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
那进程与线程的区别到底是什么?

进程是执行程序的实例。例如,当你运行记事本程序(Nodepad)时,你就创建了一个用来容纳组成 Notepad.exe的代码及其所需调用动态链接库的进程。每个进程均运行在其专用且受保护的地址空间内。因此,如果你同时运行记事本的两个拷贝,该程序正在使用的数据在各自实例中是彼此独立的。在记事本的一个拷贝中将无法看到该程序的第二个实例打开的数据。
以沙箱为例进行阐述。一个进程就好比一个沙箱。线程就如同沙箱中的孩子们。孩子们在沙箱子中跑来跑去,并且可能将沙子攘到别的孩子眼中,他们会互相踢打或撕咬。但是,这些沙箱略有不同之处就在于每个沙箱完全由墙壁和顶棚封闭起来,无论箱中的孩子如何狠命地攘沙,他们也不会影响到其它沙箱中的其他孩子。因此,每个进程就象一个被保护起来的沙箱。未经许可,无人可以进出。
实际上线程运行而进程不运行。两个进程彼此获得专用数据或内存的唯一途径就是通过协议来共享内存块。这是一种协作策略。

下面让我们分析一下任务管理器里的进程选项卡。
这里的进程是指一系列进程,这些进程是由它们所运行的可执行程序实例来识别的,这就是进程选项卡中的第一列给出了映射名称的原因。请注意,这里并没有进程名称列。进程并不拥有独立于其所归属实例的映射名称。换言之,如果你运行5个记事本拷贝,你将会看到5个称为Notepad.exe的进程。它们是如何彼此区别的呢?其中一种方式是通过它们的进程ID,因为每个进程都拥有其独一无二的编码。该进程ID由Windows NT或Windows 2000生成,并可以循环使用。因此,进程ID将不会越编越大,它们能够得到循环利用。第三列是被进程中的线程所占用的CPU时间百分比。它不是CPU的编号,而是被进程占用的CPU时间百分比。此时我的系统基本上是空闲的。尽管系统看上去每一秒左右都只使用一小部分CPU时间,但该系统空闲进程仍旧耗用了大约99%的CPU时间。
第四列,CPU时间,是CPU被进程中的线程累计占用的小时、分钟及秒数。请注意,我对进程中的线程使用占用一词。这并不一定意味着那就是进程已耗用的CPU时间总和,因为,如我们一会儿将看到的,NT计时的方式是,当特定的时钟间隔激发时,无论谁恰巧处于当前的线程中,它都将计算到CPU周期之内。通常情况下,在大多数NT系统中,时钟以10毫秒的间隔运行。每10毫秒NT的心脏就跳动一下。有一些驱动程序代码片段运行并显示谁是当前的线程。让我们将CPU时间的最后10毫秒记在它的帐上。因此,如果一个线程开始运行,并在持续运行8毫秒后完成,接着,第二个线程开始运行并持续了2毫秒,这时,时钟激发,请猜一猜这整整10毫秒的时钟周期到底记在了哪个线程的帐上?答案是第二个线程。因此,NT中存在一些固有的不准确性,而NT恰是以这种方式进行计时,实际情况也如是,大多数32位操作系统中都存在一个基于间隔的计时机制。请记住这一点,因为,有时当你观察线程所耗用的CPU总和时,会出现尽管该线程或许看上去已运行过数十万次,但其CPU时间占用量却可能是零或非常短暂的现象,那么,上述解释便是原因所在。上述也就是我们在任务管理器的进程选项卡中所能看到的基本信息列。

什么是线程?

究竟什么是线程呢?一个线程是给定的指令的序列 (你所编写的代码),一个栈(在给定的方法中定义的变量),以及一些共享数据(类一级的变量)。线程也可以从全局类中访问静态数据、栈以及可能的一些共享数据 。
每个线程有其自己的堆栈和程序计数器(PC)。你可以把程序计数器(PC)设想为用于跟踪线程正在执行的指令,而堆栈用于跟踪线程的上下文,上下文是当线程执行到某处时,当前的局部变量的值。虽然你可以编写出在线程之间传送数据的子程序,在正常情况下,一个线程不能访问另外一个线程的栈变量。
一个线程必须处于如下四种可能的状态之一,这四种状态为:

初始态:一个线程调用了new方法之后,并在调用start方法之前的所处状态。在初始态中,可以调用start和stop方法。
Runnable:一旦线程调用了start 方法,线程就转到Runnable 状态,注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。 阻塞/ NonRunnable:线程处于阻塞/NonRunnable状态,这是由两种可能性造成的:要么是因挂起而暂停的,要么是由于某些原因而阻塞的,例如包括等待IO请求的完成。 退出:线程转到退出状态,这有两种可能性,要么是run方法执行结束,要么是调用了stop方法。

线程的状态

最后一个概念就是线程的优先级,线程可以设定优先级,高优先级的线程可以安排在低优先级线程之前完成。一个应用程序可以通过使用线程中的方法setPriority(int),来设置线程的优先级大小。

 

 

 

TODO@2012-01

2012年1月要做的事情:

1.读书

Java Concurrenc in Practice

java编程思想第四版

Android in Action 3rd Edition

2.复习java基础

集合类部分

多线程并发

网络编程方面

3.学习面向对象设计思想

 

 

 

字体大小: 减小一号 减小一号
▲ Back to top 繁體