[转载]12个有趣的HTML5实例

mikel阅读(1709)

[转载]12个有趣的HTML5实例 – Alexis – 博客园.

上一周,未来的Web有了自己的logo。无论你喜不喜欢,HTML5来了并且将成为新的标准。本文将介绍一些HTML5新的实例。

HTML5logo

1.The Wilderness Downtown by Google & The Arcade Fire

很有中国风的应用,输入城市名即可。。

HTML5logo

2.Jolicloud

HTML5logo

3.Wordmark.it

使得文本拥有同你电脑中一样的字体。

HTML5logo

4.Z-Type

一款有HTML打造的游戏。

HTML5logo

5.Nike Better World

HTML5logo

6.Klowdz

这个应用让我想起了去年十分流行的一句话,“神马都是浮云”,这就是使用HTML5绘制的神马!

HTML5logo

7.Canvas Cycle

又一款十分酷的Chorme试验品。Canvas Cycle就像在你的任天堂上有一个编辑面板。你可以选择不挺的场景,控制音量、缩放宿舍,循环类型等。

HTML5logo

8.GraphyCalc

这个是有HTML5绘制的3D图形,并且能够根据你的鼠标去旋转。

HTML5logo

9.Google Maps + Dynamic Canvas

谷歌地图和动态的画板。

HTML5logo

10.Darkroom

这个Web应用可以让我们在线编辑图片。

HTML5logo

11.20 Things

一个非常清爽简洁的网站,可以阅读电子书。

HTML5logo

12.Agent 8-Ball

HTML5logo

原文链接http://webdesignledger.com/inspiration/12-fun-clever-examples-of-html5

一些HTML5的资源

http://www.chromeexperiments.com/

http://www.html5gallery.com/

http://www.apple.com/

[转载]小泥鳅解剖系列之模板机制

mikel阅读(1391)

[转载]小泥鳅解剖系列之模板机制 – 小泥鳅博客技术交流区 – 博客园.

为了给小泥鳅实现一套好的模板机制,曾经花了大量时间来分析目前互联网上的成熟,流行的通用程序,包括blog,cms,shop,bbs/.net,php,asp等,总结下来主要有以下几种:

1、CSS

通过加载不同的CSS文件来实现不同的展现效果。

相信熟悉CSS的话,便知CSS的强大,ASP.NET的默认主题基于此实现

优点:维护方便,速度快

缺点:2/8原则,可以实现大部分功能,灵活性不够

2、模板引擎

通过加载不同的模板文件(模板文件内容可不同),使用模板引擎自己的语言机制动态替换数据,来实现不同的展现效果。

优点:灵活,可实现绝大部分需求

缺点:影响性能,需要引入第三方模板引擎(当然可以自己开发),增加复杂度。

3、.ascx用户控件,母版页.Net平台独有

通过加载不同的.ascx用户控件来实现不同的展现效果。当然也可以动态加载. master母版页。

原理和使用第三方模板引擎类似。

优点:可使用.net语言,比模板引擎更灵活,可实现任何需求

缺点:通过反射机制影响性能(据说,未验证),.net特征偏多,对美工不友好

4、生成.aspx文件

需要使用一次模板引擎生成.aspx文件,利用URL重写加载不同的.aspx文件,运用时与模板引擎和模板文件无关。

优点:灵活,速度快

缺点:维护相对麻烦,修改模板后需要重新生成.aspx文件, 加入第三方模板引擎,增加复杂度。

5,xml+xslt

名词解释:XSL 指扩展样式表语言(EXtensible Stylesheet Language),XSLT 指XSL 转换

设计思想跟模板引擎一样,不过xslt是通用标准

优点:标准,跨平台,跨语言

缺点:不够灵活,容易出错, 对美工来说,偏复杂

问题来了,哪种更合适小泥鳅的呢?受Community Server,BlogEngine.NET等国外程序的影响,老农开始选择了第3种,通过动态加载不同用户控件的方式来实现模板机制,功能很给力,并且 在相当长的内部开发阶段使用了这种方式(胎死腹中),同时也是在这个阶段,在与其它职业的同事,朋友交流中,慢慢觉得这可能不是一种最合适的方式(实践出 真知),对于广大普通用户,特别是主题设计者,太多的.net特性会让他们看花了眼。

小泥鳅的定位一个成熟,通用,面向所有人群的博客程序,最后把焦点放在了模板引擎上,

现在.net平台的主要模板引擎有Nvelocity, StringTemplate等,经过多方面的测试,评估,最终选择了NVelocity,当然不排除今后有能力,有时间自己造轮子。

NVelocity源自Java,历史悠久,语法优雅,减少了重复学习的成本

如以下代码用于输出最新日志(简单就是美):

1 <div id="recentposts" class="widget">
2 <h3>最新日志</h3>
3 <ul>
4 #foreach($item in ${recentposts})
5 <li>${item.link}</li>
6 #end
7 </ul>
8 </div>

根据发布以来的反馈统计,效果还不错,大家也来做皮吧 ^_^

附:

源码下载:http://files.cnblogs.com/wei/Loachs_1.2_src.rar

官方网站http://www.loachs.com/

QQ群:12018034

[转载]C# 用Linq的方式实现对Xml文件的基本操作(创建xml文件、增删改查xml文件节点信息)

mikel阅读(774)

[转载]C# 用Linq的方式实现对Xml文件的基本操作(创建xml文件、增删改查xml文件节点信息) – Eric Sun – 博客园.

我的上一篇文章描述了用普通方法实现对xml文件的基本操作,感谢各位园友给我提的每一个建议,大家主要在说:用Linq去实现对xml的操作 更加方便简洁,于是乎我就现学习了一下Linq to xml,由于是刚刚接触Linq,理解肯定不会很深,所以请各位园友不吝赐教,有建议尽管说,在此先谢过大家啦~

LINQ to XML 为创建 XML 元素提供了一种称为“函数构造”的有效方式。函数构造是指在单个语句中创建 XML 树的能力。

启用函数构造的 LINQ to XML 编程接口有几个重要功能:

XElement 构造函数可以对内容采用多种类型的参数。 例如,可以传递另一个 XElement 对象,该对象将成为一个子元素。 可以传递一个 XAttribute 对象,该对象将成为该元素的一个属性。 也可以传递任何其他类型的对象,该对象将转换为字符串并成为该元素的文本内容。

XElement 函数采用类型为 Objectparams 数组,因此可以向该构造函数传递任意数目的对象。 这使您可以创建具有复杂内容的元素。如果对象实

IEnumerable<T>,则枚举对象中的集合,并添加集合中的所有项。

如果集合包含 XElementXAttribute 对象,则单独添加集合中的每一项。这一功能很重要,因为它允许您将 LINQ 查询的结果传递给构造函数。

本文的主要模块为:

① :生成xml文件

② :遍历xml文件的节点信息

③ :修改xml文件的节点信息

④ :向xml文件添加节点信息

⑤ :删除指定xml文件的节点信息


①:生成xml文件假设我们想生成这样的一个xml文件结构,如下所示:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<Users>
<User ID=”111111>
<name>EricSun</name>
<password>123456</password>
<description>Hello I’m from Dalian</description>
</User>
<User ID=”222222>
<name>Ray</name>
<password>654321</password>
<description>Hello I’m from Jilin</description>
</User>
</Users>
用我的上篇文章也能够很容的实现,不过下面我要用Linq to xml的方式实现生成这个xml文件,请看下面代码:

1 using System; 2  using System.Collections.Generic; 3  using System.Linq; 4  using System.Text; 5  using System.Xml.Linq; 6 7  namespace OperateXmlLinq 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 //xml文件存储路径 14   string myXmlPath = "E:\\MyUsers.xml"; 15 //创建xml文件 16   GenerateXmlFile(myXmlPath); 17 } 18 19 private static void GenerateXmlFile(string xmlPath) 20 { 21 try 22 { 23 //定义一个XDocument结构 24   XDocument myXDoc = new XDocument( 25 new XElement("Users", 26 new XElement("User", new XAttribute("ID", "111111"), 27 new XElement("name", "EricSun"), 28 new XElement("password", "123456"), 29 new XElement("description", "Hello I'm from Dalian") 30 ), 31 new XElement("User", new XAttribute("ID", "222222"), 32 new XElement("name", "Ray"), 33 new XElement("password", "654321"), 34 new XElement("description", "Hello I'm from Jilin") 35 ) 36 ) 37 ); 38 //保存此结构(即:我们预期的xml文件) 39   myXDoc.Save(xmlPath); 40 } 41 catch (Exception ex) 42 { 43 Console.WriteLine(ex.ToString()); 44 } 45 } 46 }

【注:由于使用了Linq to xml的方式,所以要引入命名空间System.Xml.Linq】,通过运行上面这段代码,就可以创建我们预想的xml文件结构,并且可以看出用 Linq这种方式,在代码中就可以很清楚明了知道我们创建xml文件的结构(如:构造函数的参数所示)

②:【遍历xml文件的节点信息】创造出了xml文件之后,我们就要知道如何获得xml文件的各个节点的信息,请看如下代码:

1 private static void GetXmlNodeInforOld(string xmlPath) 2 { 3 try 4 { 5 XDocument myXDoc = XDocument.Load(xmlPath); 6 XElement rootNode = myXDoc.Element("Users"); 7 foreach (XElement node in rootNode.Elements("User")) 8 { 9 Console.WriteLine("User ID = {0}", node.Attribute("ID").Value); 10 11 string name = node.Element("name").Value; 12 string password = node.Element("password").Value; 13 string description = node.Element("description").Value; 14 Console.WriteLine("name = {0} \npassword = {1} \ndescription = {2}", name, password, description); 15 } 16 } 17 catch (Exception ex) 18 { 19 Console.WriteLine(ex.ToString()); 20 } 21 } 22 23 private static void GetXmlNodeInformation(string xmlPath) 24 { 25 try 26 { 27 //定义并从xml文件中加载节点(根节点) 28   XElement rootNode = XElement.Load(xmlPath); 29 //查询语句: 获得根节点下name子节点(此时的子节点可以跨层次:孙节点、重孙节点......) 30   IEnumerable<XElement> targetNodes = from target in rootNode.Descendants("name") 31 select target; 32 foreach (XElement node in targetNodes) 33 { 34 Console.WriteLine("name = {0}", node.Value); 35 } 36 37 //查询语句: 获取ID属性值等于"111111"并且函数子节点的所有User节点(并列条件用"&&"符号连接) 38   IEnumerable<XElement> myTargetNodes = from myTarget in rootNode.Descendants("User") 39 where myTarget.Attribute("ID").Value.Equals("111111") && myTarget.HasElements 40 select myTarget; 41 foreach (XElement node in myTargetNodes) 42 { 43 Console.WriteLine("name = {0}", node.Element("name").Value); 44 Console.WriteLine("password = {0}", node.Element("password").Value); 45 Console.WriteLine("description = {0}", node.Element("description").Value); 46 } 47 } 48 catch (Exception ex) 49 { 50 Console.WriteLine(ex.ToString()); 51 } 52 }

上 面用了两种方法去实现对xml文件节点信息的读取,第一种方法是那种比较老的模式:通过父节点获得它的子节点(一层一层的获得),然后获取目标节点的信 息;第二中方法用到的是Linq to xml的查询模式,根据我们的需求获得符合条件的所有节点,然后对这些节点的信息进行读取。

接下来我们要简单的讲述一下Linq to xml的查询模式(语法一看便懂,这里就不过多阐述了),LINQ to XML 的一个最重要的性能优势(与 XmlDocument 相比)为:LINQ to XML 中的查询是静态编译的,而 XPath 查询则必须在运行时进行解释,此功能是 LINQ to XML 的内置功能。

在 调试程序的时候我们发现,第二种方法的IEnumerable<XElement> targetNodes = from target in rootNode.Descendants(“name”) select target;的这句话执行完毕后,得到的targetNodes依然是null,直到遍历的时候才获得相应的对象信息,这种方式就做延迟执行,【延迟执行】:意味着表达式的计算延迟,直到真正需要它的实现值为止。 当必须操作大型数据集合,特别是在包含一系列链接的查询或操作的程序中操作时,延迟执行可以大大改善性能。 在最佳情况下,延迟执行只允许对源集合的单个循环访问。

【注:查询条件写在where语句中,并列条件用”&&”符号连接,或条件用”||”符号连接】

③:【修改xml文件的节点信息】知道了如何查询xml文件的节点信息之后,对相应节点信息做相应的修改,就显得很容易了。请看如下代码:

1 private static void ModifyXmlNodeInforOld(string xmlPath) 2 { 3 try 4 { 5 XDocument myXDoc = XDocument.Load(xmlPath); 6 myXDoc.Element("Users").Element("User").Attribute("ID").Value = "777777"; 7 foreach (XElement node in myXDoc.Element("Users").Elements("User").Elements("description")) 8 { 9 node.SetValue("Hello, I'm from China."); 10 } 11 myXDoc.Save(xmlPath); 12 } 13 catch (Exception ex) 14 { 15 Console.WriteLine(ex.ToString()); 16 } 17 } 18 19 private static void ModifyXmlNodeInformation(string xmlPath) 20 { 21 try 22 { 23 //定义并从xml文件中加载节点(根节点) 24 XElement rootNode = XElement.Load(xmlPath); 25 //查询语句: 获取ID属性值等于"222222"或者等于"777777"的所有User节点(或条件用"||"符号连接) 26 IEnumerable<XElement> targetNodes = from target in rootNode.Descendants("User") 27 where target.Attribute("ID").Value == "222222" || target.Attribute("ID").Value.Equals("777777") 28 select target; 29 //遍历所获得的目标节点(集合) 30 foreach (XElement node in targetNodes) 31 { 32 //将description节点的InnerText设置为"Hello, I'm from USA." 33 node.Element("description").SetValue("Hello, I'm from USA."); 34 } 35 //保存对xml的更改操作 36 rootNode.Save(xmlPath); 37 } 38 catch (Exception ex) 39 { 40 Console.WriteLine(ex.ToString()); 41 } 42 }

这里也用了两种方法去获取相应节点信息的,具体过程请看代码就可以啦。

④:【向xml文件添加节点信息】下面的代码是向原有xml文件中添加一个节点

1 private static void AddXmlNodeInformation(string xmlPath) 2 { 3 try 4 { 5 //定义并从xml文件中加载节点(根节点) 6 XElement rootNode = XElement.Load(xmlPath); 7 //定义一个新节点 8 XElement newNode = new XElement("User", new XAttribute("ID","999999"), 9 new XElement("name", "Rose"), 10 new XElement("password", "456123"), 11 new XElement("description", "Hello, I'm from UK.")); 12 //将此新节点添加到根节点下 13 rootNode.Add(newNode); 14 //保存对xml的更改操作 15 rootNode.Save(xmlPath); 16 } 17 catch (Exception ex) 18 { 19 Console.WriteLine(ex.ToString()); 20 } 21 }

简单做一个总结:下面的方法将子内容添加到 XElement 或 XDocument 中:

方法                 说明

Add                  在 XContainer 的子内容的末尾添加内容。

AddFirst           在 XContainer 的子内容的开头添加内容。

下面的方法将内容添加为 XNode 的同级节点。 向其中添加同级内容的最常见的节点是 XElement,不过你也可以将有效的同级内容添加到其他类型的节点,

例如 XText 或 XComment。

方法                         说明

AddAfterSelf            在 XNode 后面添加内容。

AddBeforeSelf          在 XNode 前面添加内容。

⑤:【删除指定xml文件的节点信息】如何将刚刚加入的那个节点删除掉呢?请看如下代码:

1 private static void DeleteXmlNodeInformation(string xmlPath) 2 { 3 try 4 { 5 //定义并从xml文件中加载节点(根节点) 6 XElement rootNode = XElement.Load(xmlPath); 7 //查询语句: 获取ID属性值等于"999999"的所有User节点 8 IEnumerable<XElement> targetNodes = from target in rootNode.Descendants("User") 9 where target.Attribute("ID").Value.Equals("999999") 10 select target; 11 12 //将获得的节点集合中的每一个节点依次从它相应的父节点中删除 13 targetNodes.Remove(); 14 //保存对xml的更改操作 15 rootNode.Save(xmlPath); 16 } 17 catch (Exception ex) 18 { 19 Console.WriteLine(ex.ToString()); 20 } 21 }

至此,我们就完成对xml文件的基本操作:创建、读取、修改、添加、删除。由于刚刚学习Linq,理解的很浅显,如有不合适的地方,欢迎大家指出~~

[转载]android中隐藏的layout 抽屉的运用

mikel阅读(1095)

[转载]android中隐藏的layout 抽屉的运用 – noTice520 – 博客园.

最近在写一个应 用,想把设置页面和应用页面放在一起,这样就能实现用户可以实时看到自己的设置对UI的影响,从而更方便的设置用户喜欢的界面。想了一段时间,发现用 slidingDrawer这个控件可以实现这个效果。也就是一个抽屉。拉开抽屉,占据半个屏幕,另外半个屏幕还是显示应用页面。效果还是不错的。

今天就和大家分享一下Android中这个抽屉效果。其实在Android的lanucher就是一个抽屉,打开它就可以看到安装的应用。相信大家都见过用过。下面我们就来做个相同的效果,当然只是UI上差不多相同的效果。

slidingDrawer这个控件使用非常简单,基本在xml里面配置就可以。代码如下所示。

<?xml version=”1.0″ encoding=”utf-8″?>
<RelativeLayout
xmlns:Android=”http://schemas.android.com/apk/res/android
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”@string/hello”
android:textSize=”20sp”
/>
<SlidingDrawer
android:id=”@+id/sd”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:handle=”@+id/iv”
android:content=”@+id/myContent”
android:orientation=”vertical”
>

<ImageView
android:id=”@+id/iv”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:src=”@drawable/open1″
/>

<GridView
android:id=”@id/myContent”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:numColumns=”3″
android:background=”@drawable/background”
android:gravity=”center”
/>

</SlidingDrawer>
</RelativeLayout>

在SlidingDrawer这个标签下android:handle:指示的就是抽屉的图片。android:content:指向的就是抽屉里面的布局。有了这个布局,其实一个抽屉就出来了。

下面我们看Chouti这个类的代码

public class Chouti extends Activity {

private GridView gv;
private SlidingDrawer sd;
private ImageView iv;
private int[] icons={R.drawable.browser,R.drawable.gallery,
R.drawable.camera,R.drawable.gmail,
R.drawable.music,R.drawable.market,
R.drawable.phone,R.drawable.messages,R.drawable.maps};
private String[] items={“浏览器”,”图片”,”相机”,”时钟”,”音乐”,”市场”,”拨号”,”信息”,”地图”};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gv = (GridView)findViewById(R.id.myContent);
sd = (SlidingDrawer)findViewById(R.id.sd);
iv=(ImageView)findViewById(R.id.iv);
MyAdapter adapter=new MyAdapter(this,items,icons);//自定义MyAdapter来实现图标加item的显示效果
gv.setAdapter(adapter);
sd.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener()//开抽屉
{
@Override
public void onDrawerOpened()
{
iv.setImageResource(R.drawable.close1);//响应开抽屉事件 ,把图片设为向下的
}
});
sd.setOnDrawerCloseListener(new SlidingDrawer.OnDrawerCloseListener()
{
@Override
public void onDrawerClosed()
{
iv.setImageResource(R.drawable.open1);//响应关抽屉事件
}
});
}
}

在整个类里面将布局导入,同时设置开关抽屉的监听事件。这里面我们需要自定义一个MyAdapter来显示带文字下标的图片。

下面是MyAdapter这个类的代码
public class MyAdapter extends BaseAdapter
{
private Context _ct;
private String[] _items;
private int[] _icons;

public MyAdapter(Context ct,String[] items,int[] icons) //构造器
{
_ct=ct;
_items=items;
_icons=icons;
}

@Override
public int getCount()
{
return _items.length;
}

@Override
public Object getItem(int arg0)
{
return _items[arg0];
}

@Override
public long getItemId(int position)
{
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater factory = LayoutInflater.from(_ct);
View v = (View) factory.inflate(R.layout.gv, null);//绑定自定义的layout
ImageView iv = (ImageView) v.findViewById(R.id.icon);
TextView tv = (TextView) v.findViewById(R.id.text);
iv.setImageResource(_icons[position]);
tv.setText(_items[position]);
return v;
}
}

也是非常的简单,其中用到的布局如下

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<ImageView
android:id=”@+id/icon”
android:layout_width=”wrap_content”
android:layout_height=”40px”
android:layout_gravity=”center”
/>
<TextView
android:id=”@+id/text”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:gravity=”center”
android:textColor=”#ffffffff”
/>
</LinearLayout>

这样,我们的抽屉就完成啦 来看下效果

就写这么多啦。抽屉这个控件非常实用,除了我在开头所说的我在程序中的应用外,还有很多的用途, 发挥你的想象力,抽屉将为你的应用增色不少。

[转载]以异步的方式操作TCP/IP套接字—以异步方式实现简单的聊天室

mikel阅读(968)

[转载]以异步的方式操作TCP/IP套接字——以异步方式实现简单的聊天室 – 风尘浪子 – 博客园.

普通的TCP/IP开发方式大家都应该非常熟练,但在系统开发的时候往往会遇到问题。

比如:在开发一个简单的聊天室的时候,一般情况下,Windows应用程序会处于同步方式运行,当监听的客户端越多,服务器的负荷将会越重,信息发送与接收都会受到影响。这时候,我们就应该尝试使用异步的TCP/IP通讯来缓解服务器的压力。

下面以一个最简单的聊天室服务器端的例子来说明异步TCP/IP的威力,先开发一个ChatClient类作为客户管理的代理类,每当服务器接收到信息时,就会把信息处理并发送给每一个在线客户。

void Main()
{
IPAddress ipAddress = IPAddress.Parse(“127.0.0.1”
);            //默认地址
TcpListener tcpListener = new TcpListener(ipAddress,500
);
tcpListener.Start();
while
(isListen)                //以一个死循环来实现监听
{
ChatClient chatClient = new
ChatClient(tcpListener.AcceptTcpClient());    //调用一个ChatClient对象来实现监听

}
tcpListener.Stop();

}


ChatClient中存在着一个Hashtabel类的静态变量 clients,此变量用来存贮在线的客户端信息,每当对一个客户端进行监听时,系统就生成一个ChatClient对象,然后在变量clients中加 入此客户端的信息。在接收客户端信息时,信息会调用Receive(IAsyncResult async)方法,把接收到的信息发送给每一个在线客户。

值得注意的是,每当接收到客户信息时,系统都会利用Stream.BeginRead()的方法去接收信息,然后把信息发送到每一个在线客户,这样做就可以利用异步的方式把信息进行接收,从而令主线程及早得到释放,提高系统的性能。

public class ChatClient

{
private
TcpClient _tcpClient;
private byte
[] byteMessage;
private string _clientEndPoint;

public volatile string message;
public static Hashtable clients= new
Hashtable();          //以此静态变量存处多个客户端地址

public ChatClient(TcpClient tcpClient)
{
_tcpClient =
tcpClient;
_clientEndPoint =
_tcpClient.Client.RemoteEndPoint.ToString();
Console.WrtieLine(“连接成功,客户端EndPoint为”+_clientEndPoint);

ChatClient.clients.Add(_clientEndPoint, this
);       //每创建一个对象,就会将客户端的ChatClient对象存入clients;

byteMessage=new byte[_tcpClient.ReceiveBufferSize];

lock (_tcpClient.GetStream())        //接收信息,使用lock避免数据冲突

{

_tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null);

//就在此处使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放

//这样做就缓解了服务器端压力。
}
}

public void Receive(IAsyncResult iAsyncResult)
{
try

{
int length;

lock (_tcpClient.GetStream())    //信息接收,使用lock避免数据冲突
{
length=
_tcpClient.GetStream().EndRead(iAsyncResult);
}
if (length < 1
)
{
MessageBox.Show(_tcpClient.Client.RemoteEndPoint + “已经断线”
);
clients.Remove(_tcpClient);
return
;
}

message=Encoding.Unicode.GetString(byteMessage,0,length);
SendToEveryone(message);

//在此时我们可以在此处调用SendToEveryone方法,利用clients变量以Stream.Write方法为每个客户端发送信息。


lock
(_tcpClient.GetStream())    //再次监听,使用lock避免数据冲突
{
_tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null
);

//再次调用Stream.BeginRead方法,以监听以下次客户的信息
}
}
catch
(Exception ex)
{
clients.Remove(_tcpClient);
_tcpClient.GetStream().Close();
_tcpClient.Close();
}
}

//通过Send方法把信息转换成二进制数据,然后发送到客户端

public void Send(string message)
{
try
{
NetworkStream ns;
lock (_tcpClient.GetStream())
{
ns = _tcpClient.GetStream();
}
byte[] byteMessage = Encoding.ASCII.GetBytes(message);
ns.Write(byteMessage, 0, byteMessage.Length);
ns.Flush();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

//由于客户端信息记录在HashTabel变量clients中,当信息接收后,就会通过此变量把信息发送给每一个在线客户。

public void SendToEveryone(string message)
{
foreach (DictionaryEntry client in clients)
{
ChatClient chatClient = (ChatClient)client.Value;
chatClient.Send(message);
}
}

}

测试结果:

至于窗口的设计和客户端的设计在这里就省略不说,这里的目的只是要你了解服务器端多线程TCP/IP信息接收的原理。

这个例子里,ChatClient类使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放,这样做就缓解了服务器端压力。

这时候你可以做一个测试,此聊天室在默认情况下可接受大约3000个客户端连接,仍然能够正常工作。

[转载]Android 中文 API (100) —— ScrollView

mikel阅读(1034)

[转载]Android 中文 API (100) —— ScrollView – 农民伯伯 – 博客园.

前言

春节即至,谨代表Android中文翻译组全体同仁祝大家身体健 康,工作顺利!从第一篇译稿2010年8月27发布至今天2011年1月27整5个月,共发布100篇译文,3个合集,在新的一年里,翻译组仍将坚持 Android相关的翻译工作,秉承开源、合作、共享和坚持的信念打持久战,感谢大家的关心和支持!

本章内容是 Android.widget.ScrollView,版本为Android 2.3 r1,翻译来自”pengyouhong”,再次感谢”pengyouhong” !期待你一起参与Android API的翻译,联系我over140@gmail.com。

声明

欢迎转载,但请保留文章原始出处:)

博客园:http://www.cnblogs.com/

Android中文翻译组:http://goo.gl/6vJQl

正文

一、结构

public class ScrollView extends FrameLayout

java.lang.Object

android.view.View

android.view.ViewGroup

android.widget.FrameLayout

android.widget.ScrollView

二、概述

一种可供用户滚动的层次结构布局容器,允许显示比实际多的内容。ScrollView是一种FrameLayout,意味需要在其上放置有自己滚动内容的子元素。子元素可以是一个复杂的对象的布局管理器。通常用的子元素是垂直方向的LinearLayout,显示在最上层的垂直方向可以让用户滚动的箭头。

TextView类也有自己的滚动功能,所以不需要使用ScrollView,但是只有两个结合使用,才能保证显示较多内容时候的效率。但只有两者结合使用才可以实现在一个较大的容器中一个文本视图效果。

ScrollView只支持垂直方向的滚动。

三、构造函数

public ScrollView (Context context)

创建一个默认属性的ScrollView实例。

public ScrollView (Context context, AttributeSet attrs)

创建一个带有attrs属性的ScrollView 实例。

public ScrollView (Context context, AttributeSet attrs, int defStyle)

创建一个带有attrs属性,并且指定其默认样式的ScrollView实例。

四、公共方法

public void addView (View child)

添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。

参数

child 所添加的子视图

public void addView (View child, int index)

添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。

参数

child 所添加的子视图

index 添加子视图的位置

public void addView (View child, int index, ViewGroup.LayoutParams params)

根据指定的layout参数添加子视图

参数

child 所添加的子视图

index 添加子视图的位置

params 为子视图设置的layout参数

public void addView (View child, ViewGroup.LayoutParams params)

根据指定的layout参数添加子视图。

参数

child 所添加的子视图

params 为子视图设置的layout参数

public boolean arrowScroll (int direction)

响应点击上下箭头时对滚动条滚动的处理。

参数

direction 按下的箭头所对应的方向

返回值

如果我们处理(消耗)了此事件返回true,否则返回false

public void computeScroll ()

被父视图调用,用于必要时候对其子视图的值(mScrollX和mScrollY)进行更新。典型的情况如:父视图中某个子视图使用一个Scroller对象来实现滚动操作,会使得此方法被调用。

public boolean dispatchKeyEvent (KeyEvent event)

发送一个key事件给当前焦点路径的下一个视图。此焦点路径从视图树的顶层执行直到当前焦点视图。如果此视图为焦点视图,将为自己发送。否则,会为当前焦点路径的下一个节点发送。此方法也会激起一个key监听器。

参数

event 发送的key事件

返回值

事件被处理返回true,否则返回false

public void draw (Canvas canvas)

手动绘制视图(及其子视图)到指定的画布(Canvas)。这个视图必须在调用这个函数之前做好了整体布局。当实现一个视图时,不需要继承这个方法;相反,你应该实现onDraw(Canvas)方法。

参数

canvas 绘制视图的画布

public boolean executeKeyEvent (KeyEvent event)

当接收到key事件时,用户可以调用此函数来使滚动视图执行滚动,类似于处理由视图体系发送的事件。

参数

event 需要执行key的事件

返回值

事件被处理返回true,否则返回false

public void fling (int VelocityY)

滚动视图的滑动(fling)手势。(译者注: 如何监听android的屏幕滑动停止事件

参数

VelocityY Y方向的初始速率。正值表示手指/光标向屏幕下方滑动,而内容将向上滚动。

public boolean fullScroll (int direction)

对响应“home/end”短按时响应滚动处理。此方法将视图滚动到顶部或者底部,并且将焦点置于新的可视区域的最顶部/最底部组件。若没有适合的组件做焦点,当前的ScrollView会收回焦点。

参数

direction 滚动方向:FOCUS_UP表示视图向上滚动;FOCUS_DOWN表示视图向下滚动

返回值

key事件被消耗(consumed)返回true,其他情况返回false

public int getMaxScrollAmount ()

返回值

当前滚动视图响应箭头事件能够滚动的最大数。

public boolean isFillViewport ()

指示当前ScrollView的内容是否被拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。

返回值

内容填充视图返回true,否则返回false

public boolean isSmoothScrollingEnabled ()

返回值

按箭头方向滚动时,是否显示滚动的平滑效果。

public boolean onInterceptTouchEvent (MotionEvent ev)

实现此方法是为了拦截所有触摸屏幕时的运动事件。可以像处理发送给子视图的事件一样去监视这些事件,并且获取当前手势在任意点的ownership

使用此方法时候需要注意,因为它与View.onTouchEvent(MotionEvent)有相当复杂的交互,并且前提需要正确执行View.onTouchEvent(MotionEvent)。事件将按照如下顺序接收到:

1. 收到down事件

2. Down事件或者由视图组的一个子视图处理,或者被用户自己的onTouchEvent()方法处理;此处理意味你应该执行onTouchEvent()时返回true,这样才能继续看到剩下的手势(取代找一个父视图处理)。如果onTouchEvent()返回true时,你不会收到onInterceptTouchEvent()的任何事件并且所有对触摸的处理必须在onTouchEvent()中发生。

3. 如果此方法返回false,接下来的事件(up to and including the final up)将最先被传递当此,然后是目标的onTouchEvent()

4. 如果返回true,将不会收到以下任何事件:目标view将收到同样的事件但是会伴随ACTION_CANCEL,并且所有的更进一步的事件将会传递到你自己的onTouchEvent()方法中而不会再在这里出现。

参数

ev 体系向下发送的动作事件

返回值

如果将运动事件从子视图中截获并且通过onTouchEvent()发送到当前ViewGroup ,返回true。当前目标将会收到ACTION_CANCEL事件,并且不再会有其他消息传递到此。

(译者注:onInterceptTouchEventonTouchEvent调用时序

public boolean onTouchEvent (MotionEvent ev)

执行此方法为了处理触摸屏幕的运动事件。

参数

ev 运动事件

返回值

事件被处理返回true,其它返回false

public boolean pageScroll (int direction)

响应短按“page up/ down”时候对滚动的处理。此方法将向上或者向下滚动一屏,并且将焦点置于新可视区域的最上/最下。如果没有适合的component作为焦点,当前scrollView将收回焦点。

参数

direction 滚动方向:FOCUS_UP表示向上翻一页,FOCUS_DOWN表示向下翻一页。

返回值

key事件被消耗(cosumed)返回true,其他返回false

public void requestChildFocus (View child, View focused)

当父视图的一个子视图的要获得焦点时,调用此方法。

参数

child 要获得焦点的父视图的子视图。此视图包含了焦点视图。如果没有特殊徐要求,此视图实际上就是焦点视图。

focused 子视图的子孙视图并且此子孙视图是真正的焦点视图

public boolean requestChildRectangleOnScreen (View child, Rect rectangle, boolean immediate)

当组里的某个子视图需要被定位在屏幕的某个矩形范围时,调用此方法。重载此方法的ViewGroup可确认以下几点:

* 子项目将是组里的直系子项

* 矩形将在子项目的坐标体系中

重载此方法的ViewGroup应该支持以下几点:

* 若矩形已经是可见的,则没有东西会改变

* 为使矩形区域全部可见,视图将可以被滚动显示

参数

child 发出请求的子视图

rectangle 子项目坐标系内的矩形,即此子项目希望在屏幕上的定位

immediate 设为true,则禁止动画和平滑移动滚动条

返回值

进行了滚动操作的这个组(group),是否处理此操作。

public void requestLayout ()

当有改变引起当前视图重新布局时,调用此函数。它将规划一个视图树的layout路径。

public void scrollTo (int x, int y)

设置当前视图滚动到的位置。此函数会引起对onScrollChanged(int, int, int, int)函数的调用并且会让视图更新。

当前版本取消了在子视图中的滚动。

参数

x 滚动到的X位置

y 滚动到的Y位置

public void setFillViewport (boolean fillViewport)

设置当前滚动视图是否将内容高度拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。

参数

fillViewport 设置为true表示拉伸内容高度来适应视口边界;其他设为false

public void setOverScrollMode (int mode)

为视图设置over-scroll模式。有效的over-scroll模式有OVER_SCROLL_ALWAYS(缺省值),OVER_SCROLL_IF_CONTENT_SCROLLS(只允许当视图内容大过容器时,进行over-scrolling)和OVER_SCROLL_NEVER。只有当视图可以滚动时,此项设置才起作用。

译者注:这个函数是2.3 r1 中新增的,API Level 9关于over-scroll这里译为弹性滚动,即,参见帖子:类似iPhone的弹性ListView滚动

参数

mode       The new over-scroll mode for this view.

public void setSmoothScrollingEnabled (boolean smoothScrollingEnabled)

用来设置箭头滚动是否可以引发视图滚动。

参数

smoothScrollingEnabled 设置箭头滚动是否可以引起内容的滚动的bool

public final void smoothScrollBy (int dx, int dy)

类似于scrollBy(int, int),但是滚动时候是平缓的而不是立即滚动到某处。

参数

dx X方向滚动的像素数

dy Y方向滚动的像素数

public final void smoothScrollTo (int x, int y)

类似于scrollTo(int, int),但是滚动时候是平缓的而不是立即滚动到某处。

参数

x 要滚动到位置的X坐标

y 要滚动到位置的Y坐标

五、受保护方法

protected int computeScrollDeltaToGetChildRectOnScreen (Rect rect)

计算X方向滚动的总合,以便在屏幕上显示子视图的完整矩形(或者,若矩形宽度超过屏幕宽度,至少要填满第一个屏幕大小)。

参数

rect 矩形

返回值

滚动差值

protected int computeVerticalScrollOffset ()

计算垂直方向滚动条的滑块的偏移。此值用来计算滚动条轨迹的滑块的位置。

范围可以以任意单位表示,但是必须与computeVerticalScrollRange()computeVerticalScrollExtent()的单位一致。

缺省的偏移是在当前视图滚动的偏移。

返回值

滚动条的滑块垂直方向的偏移。

protected int computeVerticalScrollRange ()

滚动视图的可滚动范围是所有子元素的高度。

返回值

由垂直方向滚动条代表的所有垂直范围,缺省的范围是当前视图的画图高度。

protected float getBottomFadingEdgeStrength ()

返回滚动底部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。

返回值

滚动底部能见度,值的范围在浮点数0.0f1.0f之间。

protected float getTopFadingEdgeStrength ()

返回滚动顶部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。

返回值

滚动顶部能见度,值的范围在浮点数0.0f1.0f之间。

protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。

参数

child 需要自己测量的子视图

parentWidthMeasureSpec 当前视图要求的宽度

parentHeightMeasureSpec 当前视图要求的宽度

protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白和边界。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。

参数

child 需要测量的子视图

parentWidthMeasureSpec 当前视图要求的宽度

widthUsed 水平方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)

parentHeightMeasureSpec 当前视图要求的宽度

heightUsed 垂直方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)

protected void onLayout (boolean changed, int l, int t, int r, int b)

当前视图需要为子视图分配大小和位置时候调用,子类继承必须要重载此方法并调用自己子视图的layout函数。

参数

changed 当前视图的新的大小或者位置

l 相对父视图,左边界位置

t 相对父视图,上边界位置

r 相对父视图,右边界位置

b 相对父视图,下边界位置

protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

测量视图以确定其内容宽度和高度。此方法被measure(int, int)调用。需要被子类重写以提供对其内容准确高效的测量。

约定:当重写此方法时,你必须调用setMeasuredDimension(int, int)来保存当前视图view的宽度和高度。不成功调用此方法将会导致一个IllegalStateException异常,是由measure(int, int)抛出。所以调用父类的onMeasure(int, int)方法是必须的。

父类的实现是以背景大小为默认大小,除非MeasureSpec(测量细则)允许更大的背景。子类可以重写onMeasure(int,int)以对其内容提供更佳的尺寸。

如果此方法被重写,那么子类的责任是确认测量高度和测量宽度要大于视图view的最小宽度和最小高度(getSuggestedMinimumHeight() getSuggestedMinimumWidth()),使用这两个方法可以取得最小宽度和最小高度。

参数

widthMeasureSpec   受主窗口支配的水平空间要求。这个需求通过 View.MeasureSpec.进行编码。

heightMeasureSpec 受主窗口支配的垂直空间要求。这个需求通过 View.MeasureSpec.进行编码。

protected void onOverScrolled (int scrollX, int scrollY, boolean clampedX, boolean clampedY)

overScrollBy(int, int, int, int, int, int, int, int, boolean)调用,来对一个over-scroll操作的结果进行响应。(译者注:这个函数是2.3 r1 中新增的,API Level 9

参数

scrollX 新的X滚动像素值

scrollY 新的Y滚动像素值

clampedX scrollXover-scroll的边界限制时,值为true

clampedY scrollYover-scroll的边界限制时,值为true

protected boolean onRequestFocusInDescendants (int direction, Rect previouslyFocusedRect)

当在滚动视图的子视图中查找焦点视图时,需要注意不要将焦点设置在滚动出屏幕外的控件上。此方法会比执行缺省的ViewGroup代价高,否则此行为也会设置为缺省

参数

direction 指定下列常量之一:FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT

previouslyFocusedRect 能够给出一个较好的提示的矩形(当前视图的坐标系统)表示焦点从哪里得来。如果没有提示为null

返回值

是否取得了焦点

protected void onSizeChanged (int w, int h, int oldw, int oldh)

布局期间当视图的大小发生改变时调用。如果只是添加到视图,调用时显示的是旧值0。(译者注:也就是添加到视图时,oldwoldh返回的是0)。

参数

w 视图当前宽度

h 视图当前高度

oldw 视图改变前的宽度

oldh 视图改变前的高度

六、补充

七、译者其他译文

android 中文 api (64) —— Scroller

八、通知

由于年前事情较多,合集4将推迟到年后发布,时间约为2011年2月9日或10日,请大家谅解 🙂

[转载]Hadoop in Action 翻译 第一章

mikel阅读(832)

[转载]Hadoop in Action 翻译 第一章 – 王新的博客 – 博客园.

Hadoop介绍

内容简介:

1. 编写可扩展的,分布式的,海量数据处理的程序的基础

2. 介绍hadoopMapREduce

3. 编写一个简单的MapReduce程序

今天,我们被数据所围绕,人们上传视频,手机拍照,给朋友发信息,更新facebook状态,论坛回帖,点击广告,等等.另外,机器本身也在不停的产生大量数据.甚至也许各位看官就在电脑旁读着电子书,当然,你的购买记录已经记录在书店的系统之中了.

海量数据的不停增长,现在已经给各大厂商带来了极大地挑战.他们需要在以兆记的数据中挖掘出有用的信息,比如哪些网页是大众喜闻乐见的,等等信息.Google是第一个公布使用MapReduce系统来处理他们的快速增长的数据的大型互联网企业。

由于各大厂商也遇到了同样的问题(scaling challenges),我们不可能指望每个遇到这种问题的厂商都自己去开发一套类似与Google MapReduce的产品,所以Hadoop框架迅速引起了大家的关注 .DC 认识到这是一个机会,进而开发了一套开源的mapReduce 系统,也就是我们所说的Hadoop.不久,Yahoo等厂商就迅速投入到这个项目中,支持这个项目.今天,Hadoop 已经成为了很多大型互联网公司的基础技术设施了,其中包括Yahoo Facebook LinkedIn Twitter.特别是很多传统行业也开始使用这个项目,比如传媒,通信行业等.

很快,编写可扩展的分布式数据处理程序将成为很多程序员的重要技能之一.曾经,我们这样定义一个优秀的程序员,他必须懂关系型数据库,会网络编程,安全编程.相似的,不久,分布式数据处理的能力也将成为程序员的重要技能之一了.本书就是知道你如何快速的将hadoop实践到你的数据处理中.

这一章主要介绍hdoop为何构建distributed system,及分布式系统,以及数据处理系统的.它将让你对mapreduce有一个宏观的认识.一个简单的word counting的例子.我们将讨论hadoop的历史,以及

Why “Hadoop in Action”?

实话实说,我第一次被Hadoop引起兴趣之前也饱受hadoop中的例子教程的打击。Hadoop官方文档非常全面,但是却不能够简单明了的解决我们学习中遇到的问题。而此书的目的就是解决这个问题。此书将引导你快速创建hadoop项目,在实践中告诉你什么是hadoop,而不是关注理论细节。

What is Hadoop?

hadoop 是一个帮助开发者构建处理大量数据并发应用的开源框架。分布式是一个很广的领域。而hadoop的主要特征包括:

可接入,易于接入。—-Hadoop运行在一个大型集群上,或者运行在一些云服务上,客户端可以方便的接入到。

容错性。

可扩展性。

简单

Hadoop 的可接入性与简洁性使其在分布式程序方面有很大的优势。甚至即便是大学中的学生也可以简单快速的构建自己的Hadoop集群。另外,它的容错性与扩展性使其很适合做YahooFacebook等大型网站的后台服务。所以Hadoop在学术界和工业界都很受欢迎。

理解分布式系统与Hadoop

摩尔定律在过去很多年都正常发挥着作用。但是当大型系统遇到系统需要大规模扩展时,摩尔定律似乎就不再像以前那么准确的发挥作用了。很重要一点是程序很可能部署在很多低性能的或者廉价的机器之上。

了解一下目前流行的大型分布式系统,从目前的IO技术的价格成本方面考虑,一台高性能的服务器有4I/O channel。每一个I/O channel100MB/sec的处理能力。那么处理一个4TB的数据集,就会需要3个小时。使用Hadoop,这个数据集,将会被分割微很多小的数据块(一般来说是64MB),这些小块的数据块将会被独立的存放在Hadoop Distributed File SystemHDFS)上。而HDFS是建立在许多廉价机器上的一个集群。即便是谦虚的说,服务器集群会提供更加强大的数据读取速度。而且廉价机器集群的成本要比一个高性能服务器的成本低得多。我们所说的廉价机器就是指的PC机。

以前都用hadoop 与传统体系的系统比较,来凸显hadoop的优势。现在,我们用hadoop与其他分布式系统相比教。SETI@home(在家搜寻外星智慧(地外文明也就是我们常说的外星人)),是一个通过互联网利用家用个人计算机处理天文数据的分布式计算项目。该项目试图通过分析雷西博射电望远镜采集的无线电信号,搜寻能够证实外星智能生物存在的证据。该项目由美国加州大学伯克利分校的空间科学实验室主办。在SETI@home体系中,一个中央存储单独提同空间与服务器,其通过互联网,将分配好的数据分发给每个家用电脑,每个家用电脑经过计算,再将计算好的数据反馈给中央存储。

Hadoop 不同于seti@home之处在于Hadoop物理指向数据。seti@home将数据反复的传送与中央服务器与各个客户端之间。其中传输成本很大,但是如果是计算密集型的数据,那么这种方式是合适的,而hadoop处理的是数据密集型的数据。hadoop处理的数据本事都是非常巨型的。根本无法像SETI@home这种方式将数据在集群中自由的传输。Hadoop关注于移动代码而不是vice versa.对比图1.1,我们看到数据与计算都在hadoop集群内部.客户端只是发送MapReduce程序去执行,并且这些程序常常很小.hadoop集群中移动的是代码而不是无理数据本身数据.数据被分解并分布在集群之中.而且尽可能的每一片数据的处理都集中在一台相同的机器上,既数据数据都在本地处理.

这种传递代码,而不是传递数据的做法就是hadoop针对数据密集型程序所做的特殊设计.程序运行的代码都是短小精悍的而且易于传输的.每次都将要运行的代码传输到数据所在的机器.这样就可以不去动已经保存在机器上的数据了.

Now that you know how Hadoop fits into the design of distributed systems, let’s see

how it compares to data processing systems, which usually means SQL databases.

SQL 数据库 VS. Hadoop

Hadoop是一个数据处理的框架,对于高负荷的数据处理,什么使其优于传统的关系型数据库呢?

其中一个原因就是 SQL是用来处理结构化的数据.hadoop 使用场景往往是非结构化数据,比如text文本数据.从这个观点出发,hadoop提供了more general paradigm than SQL.如果处理的是结构化数据,那么hadoopSQL数据库的差别是微小的.理论上,SQLHadoop都可以处理,作为一种查询语言,也可以在hadoop之上实现一套SQL查询语言,并将其作为数据操作引擎.但是在具体实践中,SQL数据库基本上都已经有很多年的传统了.有一些老牌的数据库提供商,为很多历史悠久的软件提供服务.这些已有的数据库都无法解决hadoop所针对的那些问题.

(略)

理解Hadoop

你也许很喜欢像pipelines和消息队列这样的数据处理模型。这些模型再开发数据处理程序时发挥这很重要的作用。最有名的pipelineUnix pipesPipelines可以复用处理单元;其可以简单的将现有的模块与新模块相链接。消息队列可以异步的处理消息单元,程序员可以控制生产者或者消费者,而其执行时间由系统控制。类似的,MapReduce同样是一个数据处理模型。而其最大的优点在于可以在多台电脑上延展你的数据处理。在此模型之下,数据处理单元称之为mapppersreducers。其在内部将数据处理程序分解为mappersreducers。一旦你使用MapReduce编写你的数据处理程序,将其扩展为一个成百上千台机器上的集群只不过是修改一些配置那么简单而已。简单快速的扩展性正是那么多程序选择MapReduce模型的原因。

1.5.1 Scaling a simple program manually

在正式进入MapReduce之前,我们通过一小段练习,来处理一个大型的数据集,并扩展这个程序。你将看到扩展这个程序的挑战,并且将欣赏MapReduce在扩展性方面给开发人员带来的好处。

这个练习就是计算在一个文档中出现每个单词出现的次数。在这个例子中,我们待处理的文档中只有一句话。

Do as I say, not as I do.

我们将调用这个小例子

define wordCount as Multiset;

for each document in documentSet {

T = tokenize(document);

for each token in T {

wordCount[token]++;

}

}

display(wordCount);

程序将遍历所有文档,在每个文档中,将是用tokenization程序一个一个等计算单词出现的次数。最终display方法将wordCount打印出来。这个程序将运行正常,知道你要处理的文件越来越大。

比 如说,你要建立一个垃圾邮件过滤器,过滤所有的邮件,以便于统计数以百万记的垃圾邮件中每个词汇出现的频率。你将使用数台服务器遍历所有的文档。每台机器 处理不同的文件。当所有的服务器完成这项工作,下一步就是将收集说有服务器上的处理结果。之前看到的伪码,将被部署到多台服务器上,我们将会吧wordCount定义为一个集合,用来存储多台服务器上的运算结果。

第一部分处理单元的代码为:

for each document in documentSubset {

T = tokenize(document);

for each token in T {

wordCount[token]++;

}

}

sendToSecondPhase(wordCount);

第二部分处理单元为:

define totalWordCount as Multiset;

for each wordCount received from firstPhase {

multisetAdd (totalWordCount, wordCount);

}

看上去,这并不难,但是一些细节问题将会阻碍它像我们想象中的运行。

首 先,我们忽略了读取文件的性能。如果所有的文件存储在一个中央存储服务器上,那么这台服务器的总线带宽将成为一个传输瓶颈。更多的数据处理服务器将提升数 据处理的能力,但是数据存储服务器并不能一直维持性能,因为它有限的带宽无法及时的将需要处理的数据发往各个数据处理服务器上去。所以我们需要先将这些文 件分分别存到各个数据处理服务器上去。这样当各个数据处理服务器需要处理数据时,就不用再从中央存储服务器上获取文件了。特别要重申的是,对于数据密集型 应用程序来说,将数据存储与数据处理放置在统一台服武器上,是非常重要的。

另外一个问题就是最终的结果集存储在内存中。当数据量超大时,请不要将数据结果集存放在服务器内存中,这样做很可能会超出内存数据存储的极限。比如wordCount中不仅仅只是存储英文单词,还包括所有可能存在的字母组合,所有其数据集的大小很难把握。

OkwordCount也许不太适合存储在内存中了;我们将不得不修改我们的程序,将其存储在硬盘中。这意味着我们将实现一个基于硬盘的hash table, 这意味着我们将写大量的代码。此外,我们的第二部分处理单元(就是汇总所有处理结果的部分)只有一台服务器,这的确显得有点笨拙。特别是如果我们在第一部 分处理单元中使用多台服务器用于处理数据,我们的第二部分处理单元(就是汇总所有处理结果的部分)必将成为一个瓶颈。此时我们理所应当的想法就是以分布式 的形式,使用多台服务器处理这部分数据。为了在这部分数据处理中使用分布式,我们就需要将数据分割,以便与在多台服务器上独立计算。你需要将第一部分处理 完的数据以某种形式分割,比如现在有26台服务器用于处理第二部分,那么你就可以按照首字母的顺序,将所有结果集分为26个部分,将其分布到26台服务器上进行第二部分的处理,这26台服务器中的每一台服务器都处理以某个大写字母开头的单词。不同与单机版的wordConunt,此时我们将有26wordCount wordCount-a, wordCount-b等等。此时,我们要对第一部分处理单元做一下调整。当第一部分数据处理时,将数据按照首字母分割一次。这样将其处理完毕后,每台处理第一部分数据的服务器都会将wordCount-a 发往处理A字母开头的单词的服务器上去。以此类推,每台处理第二部数据单元的服务器都会处理一个字母开头的单词。终于,这个分布式的计数器程序就可以完工了。为了使其工作在分布式集群上,我们需要增加或者修改一些处理流程:

1. 再多台服务器上存储并处理数据。

2. 在硬盘上存放数据的处理结果,而不是内存中。

3. 将第一部分的数据结果集在每台服务器上先分割好,以备于第二次数据处理时用。

4. 将第一部分处理好,并分割好的数据分发给第二部分数据处理单元的机器集群。

靠,我们只是想做一个简单的单词计数器,竟然要高的如此复杂,这还没有考虑容错问题呢。这也就是为什么我们需要hadoop这种框架的原因了。

Scaling the same program in MapReduce(待续)

MapReduce programs are executed in two main phases, called mapping and reducing.

Each phase is defined by a data processing function, and these functions are called

mapper and reducer, respectively. In the mapping phase, MapReduce takes the input

data and feeds each data element to the mapper. In the reducing phase, the reducer

processes all the outputs from the mapper and arrives at a final result.

In simple terms, the mapper is meant to filter and transform the input into something

that the reducer can aggregate over. You may see a striking similarity here with the two

phases we had to develop in scaling up word counting. The similarity is not accidental.

The MapReduce framework was designed after a lot of experience in writing scalable,

distributed programs. This two-phase design pattern was seen in scaling many programs,

and became the basis of the framework.

In scaling our distributed word counting program in the last section, we also had to

write the partitioning and shuffling functions. Partitioning and shuffling are common

design patterns that go along with mapping and reducing. Unlike mapping and

reducing, though, partitioning and shuffling are generic functionalities that are not too

dependent on the particular data processing application. The MapReduce framework

provides a default implementation

that works in most situations.

[转载]Android 分享两个你学习android 平台开发必须碰到的几个知识点的组件【天气预报、日期】View 组件

mikel阅读(831)

[转载]Android 分享两个你学习android 平台开发必须碰到的几个知识点的组件【天气预报、日期】View 组件 – Terry_龙 – 博客园.

本篇文章将分享两个VIEW组件,一个天气组件和一个日期组件,这两个组件本来是一个App Widget 后来,我看着好玩,将他们弄成一个VIEW的组件,可以像使用Windows Phone 7 的用户控件一样拖放到你想要的项目中。本篇将演示这两个组件的编写过程,工程文件如下:

包名介绍:

  • com.terry.weather  程序的入口包
  • com.yaomei.adapter  天气预报组件使用到的数据源
  • com.yaomei.model  天气预报使用到的模型包
  • com.yaomei.util  获取天气信息的工具包
  • com.yaomei.widget  天气预报组件、日期组件的存放位置

从包名可以看出,编写一个天气预报所需要的代码量比编写一个日期VIEW所需要的代码量要多得多 ,那么我们先把天气预报的一些实现思路跟大家讲讲。

首先,本实例使用的天气预报是一个可以自己国际化的天气组件VIEW,可以看上图,将所需要的URL都放入Android 自己的国际化文件夹里面,比如中文的话就这样写:

<string name=”googleWeatherApi”>
<![CDATA[http://www.google.com/ig/api?hl=zh-cn&weather=]]>
</string>

那么是英语环境的就只需要在默认的VALUES里面的string.xml这样写即可:

<string name=”googleWeatherApi”>
<![CDATA[http://www.google.com/ig/api?hl=en&weather=]]>
</string>

这是本篇一个要注意的一点,另外还有需要注意的是,这个天气组件提供可供用户选择更新频率,这里比如我们使用3个小时更新一次,那么当用户退出 程序时,再打开是否还要再去Google 上面读天气呢?答案是NO,因为既然用户选择了更新频率,那么在一定的时间内,我们最好不要自动去更新,除非用户自己点击更新才去执行。那么要如何得到之 前的数据呢?

这里使用到的是SharePreference 将一些天气的信息保存进去,连同天气的图片也一并保存。保存天气图片是将google 天气的图片使用Base64转成字符串,然后保存进Sharepreference ,如果更新频率条件未满足则进去SharePrference 将天气预报数据取出来 。因为Android 并未提供将图片转成字符串的API,这里使用到的是apache 的一个Jar包,可在这里下载:点击这里

思路上面给出了,下面给出天气预报组件VIEW的核心代码,其他附属代码可在后面的附件下载得到,代码如下:

package com.yaomei.widget;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.codec.binary.Base64;
import Android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.text.Html;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.terry.weather.R;
import com.yaomei.adapter.weatherAdapter;
import com.yaomei.model.WeatherMdoel;
import com.yaomei.util.strHelpeUtil;

public class WeatherView extends LinearLayout {

private static final String Hour_COMPARE = hour_compare;
private static final String DAY_OF_WEEK = day_of_week;
private static final String LOW = low;
private static final String HIGH = high;
private static final String CONDITION = condition;
private static final String IMAGE = image;
private static final String DATE_COMPARE = date_compare;
private static final String CITYNAE_SHARE = cityNameShare;

private ImageView iv_weather;
private TextView tv_state, tv_position, tv;

WeatherMdoel model;
private List<WeatherMdoel> weatherList = null;
GridView gv;
Timer timer;
Handler handler
= new Handler() {
public void handleMessage(Message msg) {
if (msg.arg1 == 1) {
if (weatherList.size() > 0) {
gv
.setAdapter(
new weatherAdapter(getContext(),
weatherList));
init();
}
else {
Toast.makeText(getContext(),
查询不到数据, 1000).show();
}
// msg.recycle();
}
};
};

/**
* 自动加载天气
*/
private boolean autoLoad = false;

public boolean getAutoLoad() {
return autoLoad;
}

public void setAutoLoad(boolean isLoad) {
this.autoLoad = isLoad;
}

/**
* 城市名称
*/
private String cityName = “”;

public String getCityName() {
return cityName;
}

public void setCityName(String cityName) {
this.cityName = cityName;
}

/**
* 设置每几小时更新一次
*/
private int updateHour;

public int getUpdateHour() {
return updateHour;
}

public void setUpdateHour(int hour) {
this.updateHour = hour;
}

public WeatherView(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}

public WeatherView(Context context, AttributeSet attrs) {
super(context, attrs);
int resouceID = 1;
TypedArray tyedArray
= context.obtainStyledAttributes(attrs,
R.styleable.WeatherView);
int N = tyedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = tyedArray.getIndex(i);
switch (attr) {
case R.styleable.WeatherView_AutoLoad:
setAutoLoad(tyedArray.getBoolean(
R.styleable.WeatherView_AutoLoad,
false));
break;

case R.styleable.WeatherView_CityName:
resouceID
= tyedArray.getResourceId(
R.styleable.WeatherView_CityName,
0);
setCityName(resouceID
> 0 ? tyedArray.getResources().getText(
resouceID).toString() : tyedArray
.getString(R.styleable.WeatherView_CityName));
break;
case R.styleable.WeatherView_UpdateHour:
setUpdateHour(tyedArray.getInteger(
R.styleable.WeatherView_UpdateHour,
3));
break;
}
}

View view = LayoutInflater.from(getContext()).inflate(
R.layout.weather_layout,
this);

tv = (TextView) view.findViewById(R.id.tv_temperature);

gv = (GridView) view.findViewById(R.id.grid);
iv_weather
= (ImageView) view.findViewById(R.id.iv_weather);
tv_state
= (TextView) view.findViewById(R.id.tv_state);
tv_position
= (TextView) view.findViewById(R.id.tv_position);
timer
= new Timer();

if (getAutoLoad()) {
startLoadWeather();
}
tyedArray.recycle();
}

/**
* 开始加载
*/
public void startLoadWeather() {
timer.schedule(
new TimerTask() {

@Override
public void run() {

SharedPreferences share = getContext().getSharedPreferences(
weather, Activity.MODE_PRIVATE);
long time = System.currentTimeMillis();
final Calendar mCalendar = Calendar.getInstance();
mCalendar.setTimeInMillis(time);
String tempDate
= mCalendar.get(Calendar.YEAR) +
+ mCalendar.get(Calendar.MONTH) +
+ mCalendar.get(Calendar.DAY_OF_MONTH);
if (share.contains(DATE_COMPARE)) {
if (share.getString(CITYNAE_SHARE, “”).equals(cityName)) {
int time_cop = mCalendar.get(Calendar.HOUR)
share.getInt(Hour_COMPARE, 0);
String date
= share.getString(DATE_COMPARE, “”);

if (time_cop >= getUpdateHour()
|| !date.equals(tempDate)) {
saveWeatherList(mCalendar.get(Calendar.HOUR),
tempDate);

} else if (time_cop < getUpdateHour()) {
weatherList
= new ArrayList<WeatherMdoel>();
for (int i = 0; i < 4; i++) {
WeatherMdoel model
= new WeatherMdoel();
model.setWeek(share.getString(DAY_OF_WEEK
+ i,
“”));
model.setLowTemp(share.getString(LOW
+ i, “”));
model
.setHighTemp(share.getString(HIGH
+ i,
“”));
model.setConditions(share.getString(CONDITION
+ i, “”));
String image
= share.getString(IMAGE + i, “”);
byte[] base64Bytes = Base64.decodeBase64(image
.getBytes());
ByteArrayInputStream bais
= new ByteArrayInputStream(
base64Bytes);
model.setImageUrl(
“”);
model
.setImageDrawable(Drawable
.createFromStream(bais,
weather_image));

weatherList.add(model);
}
}
} else {
saveWeatherList(mCalendar.get(Calendar.HOUR), tempDate);
}

} else {
saveWeatherList(mCalendar.get(Calendar.HOUR), tempDate);
}
// 把必要的操作放在于线程中执行,不阻塞UI
if (handler.hasMessages(1))
handler.obtainMessage().recycle();

else {
Message msg
= handler.obtainMessage();
msg.arg1
= 1;
msg.sendToTarget();
}
}
},
0, getUpdateHour() * 3600 * 1000);
}

/**
* 第一次或者另外重新加载
*/
void saveWeatherList(int hour, String day) {
weatherList
= new ArrayList<WeatherMdoel>();
weatherList
= strHelpeUtil.searchWeather(Html.fromHtml(
getContext().getResources()
.getString(R.string.googleWeatherApi)).toString(),
getCityName());

SharedPreferences.Editor shareEditor = getContext()
.getSharedPreferences(
weather, Activity.MODE_PRIVATE).edit();
shareEditor.clear();
int i = 0;
for (WeatherMdoel model : weatherList) {

shareEditor.putString(DAY_OF_WEEK + i, model.getWeek());
shareEditor.putString(LOW
+ i, model.getLowTemp());
shareEditor.putString(HIGH
+ i, model.getHighTemp());
shareEditor.putString(CONDITION
+ i, model.getConditions());
/**
* 将图片存入
*/
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
((BitmapDrawable) strHelpeUtil.loadImage(model.getImageUrl()))
.getBitmap().compress(CompressFormat.JPEG,
50, baos);

String ImageBase64 = new String(Base64.encodeBase64(baos
.toByteArray()));
shareEditor.putString(IMAGE
+ i, ImageBase64);
i
++;
}
shareEditor.putString(DATE_COMPARE, day);
shareEditor.putInt(Hour_COMPARE, hour);
shareEditor.putString(CITYNAE_SHARE, cityName);
shareEditor.commit();
}

/**
* 初始化组件 信息
*/
void init() {
model
= weatherList.get(0);
iv_weather.setImageDrawable(model.getImageUrl()
== “” ? model
.getImageDrawable() : strHelpeUtil.loadImage(model
.getImageUrl()));
tv_state.setText(model.getConditions());
tv_position.setText(getCityName());
tv.setText(getContext().getResources().getString(R.string.temp_format,
model.getLowTemp(), model.getHighTemp()));
}

/**
* 释放对象
*/
public void releaseTimer() {
timer.cancel();
weatherList
= null;
}

}

学习这个类,你能够学到的知识点为:为应用程序添加属性,编写组件,SharePreference 的使用,Timer和Handler 异步处理UI等知识点。

日期VIEW显示VIEW组件,是一个显示当前系统时间的组件,当第一次运行时,得到当前的秒数在以60秒减去当前秒,得到第一次运行时下一次 运行需要的秒数,当这一次更新完毕后,下一次每次60秒更新一次时间,这个组件也是以分更新UI的操作,学习本类,你可以学到两个Handler 是如何协作处理UI,代码如下:

package com.yaomei.widget;

import java.util.Calendar;
import java.util.Date;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.terry.weather.R;
import com.yaomei.util.strHelpeUtil;

public class DateView extends FrameLayout {

private TextView tv_date_time, tv_week, tv_date;

int second;

Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
init();
handler.sendMessageDelayed(handler.obtainMessage(),
60 * 1000);
};
};

public DateView(Context context) {
this(context, null);
}

public DateView(Context context, AttributeSet attrs) {
super(context, attrs);
//this.setBackgroundDrawable(getContext().getResources().getDrawable(
// R.drawable.date_background));

View view
= LayoutInflater.from(getContext()).inflate(
R.layout.date_layout,
this);

tv_date_time = (TextView) view.findViewById(R.id.tv_date_time);
tv_week
= (TextView) view.findViewById(R.id.tv_week);
tv_date
= (TextView) view.findViewById(R.id.tv_date);
init();
final Calendar calendar = Calendar.getInstance();
second
= calendar.get(Calendar.SECOND);
handler.sendMessageDelayed(handler.obtainMessage(),
(
60 second) * 1000);
}

void init() {
java.text.DateFormat df
= new java.text.SimpleDateFormat(HH:mm);
tv_date_time.setText(df.format(
new Date()));
tv_week.setText(strHelpeUtil.getWeekOfDate(
new Date()));
strHelpeUtil str
= new strHelpeUtil(getContext());
tv_date.setText(str.toString());
}

}

上篇运行效果如下:

由于没有为其提供背景颜色,使用的同学可以自己为它们加上一个好看的背景颜色,效果会更加。

上面的天气组件,其实可以使用AsyncTask也是起到同样的效果,AsyncTask使用起来会觉得优雅一点,这里也顺便把一些AsyncTask在使用上一些注意事项跟大家谈一谈:

  • 在doInBackground 里面不要直接操作UI,比如设置UI的可见性操作。
  • 在doInBackground 所在的操作只负责帮你得到数据,然后把UI处理都放在onPostExecute 里面。
  • 同时启动几个AsyncTask 注意线程加锁,使用synchronized
  • 必须每次都创建一个新的AsyncTask 对象,否则会提示“a task can be executed only once” 的错误信息。

本篇的所有源码下载地址:组件

[转载]html5 实时股票数据展示控件- HumbleFinance

mikel阅读(959)

[转载]html5 实时股票数据展示控件- HumbleFinance – 分享web开发的最新技术、代码等 – 博客园.

HumbleFinance 一个HTML5数据可视化控件, 它 利用html5 的新特性来实现可交互的图表展示. 他的功能类似于 http://finance.google.com/上的股票展示控件. 该控件完全由 JavaScript 写的, 采用了 Prototype 和 Flotr 代码库. 可用来展示任何两组2维数据集, 只要他们的横坐标度量是一致的就可以.

附件的demo显示了google 2010年3月15号IPO的历史数据。 数据来自Google 财经频道。
值得一提的是,用户可以移动鼠标来显示某个时间点上的更详细的数据值,也可以使用鼠标放大、缩小、移动图表(类似百度地图)
组件在 FireFox, Chrome, Safari等浏览器上表现良好。

网站
http://www.humblesoftware.com/finance/index
下载地址
http://www.humblesoftware.com/finance/source
开源协议
MIT

[转载]如何向妻子解释OOD

mikel阅读(999)

[转载]如何向妻子解释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()方法;
  • Rectangle:这个类应继承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:是的。这个”胖接口”应该拆分未两个不同的接口,IBirdIFlyingBird,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:是,那确实令人兴奋。