Amazon云计算服务CloudFront加入对Flash Media Server的支持

昨日,Amazon宣布在其云计算服务CloudFront中加入对Adobe Flash Media Server的流媒体服务支持,目的是为了让使用亚马逊云服务的客户能够更加容易的使用面向音频和视频的内容分发服务。在面向全球的客户提供流媒体支撑服务的架构之上,一个世界级的分布式云计算服务体系至关重要,所以,强强联手是自然而然的事情,Amazon CloudFront支持了Adobe Flash Media Server Streaming服务,使用亚马逊服务的客户可以直接从中受益,而不需要了解大规模FMS的技术部署,一切都有Amazon的CloudFront搞定。

原消息地址:
http://aws.amazon.com/about-aws/whats-new/2009/12/15/announcing-cloudfront-streaming/

AIR2 & FP10.1范例上线

Adobe于今日将AIR2和FP10.1的范例放在了Tour De Flex的更新中,而且还将会陆续添加更多新功能范例进去。
没有这个应用的朋友点击我网站右侧的Tour De Flex就可以安装了。

另外,这里有一个关于Flash CS5除了iPhone之外的新功能描述:
http://gotoandlearn.com/play?id=118
来自Lee Brimelow,我的同事,美国的evangelist。

FMS4.0已经进入内测阶段

最近经常有关注FMS应用的朋友问我Flash Media Server 4.0的问题,这里我简单的给大家一些非官方的消息(非官方:就是基于我个人跟产品团队的关系和对FMS产品研发的了解)。首先,是的,Flash Media Server 4距离我们越来越近了,目前正处于公司内部以及小范围专业应用领域研发合作伙伴的内测阶段,相信会在不久开放公开测试,而正式版本的发布,按照今年Adobe在美国NAB大会上的透露信息,应该是在2010年上半年的某一天。
那么Flash Media Server 4.0将有什么令人期待的功能?从我的了解,先给各位一个概括:
1. FMS4的服务器端代码引擎将升级到SpiderMonkey的最新版本,目前是1.7,将来有可能还会变动。
2. 将支持64bit的windows和Linux系统,以前令人头疼的4GB memory的单一进程寻址问题将得到彻底解决。
3. 将支持Linux CentOS 5.3和RedHat 5.3以上版本
4. 将支持一个NetStream对象的自动重连接,比如在一路Stream的Buffer即将用完之际,客户端与服务器端断开,这将会调用重新连接的新功能,自动恢复和FMS的流连接状态。
5. 智能搜索,允许用户更加精确的对已经缓冲的内存区域的视频流做精确定位搜索。(这个功能在3.5.3中已经加入,在4.0中将被强化)
6. 原生文件格式流播放。这一功能将全面提升FMS对于DVR从录制到缓存到播放的统一格式的性能支持。
7. P2P支持,通过RTMFP支持RTMP之上的应用。比如视频聊天,Multicast,分组交互等。
8. 新的验证和File分发插件。

当然,还会有其他目前正在考虑加入的其他功能,只是今天还未确认而已。我会随时更新关于此产品的信息给各位。

Adobe中国研发中心AIR测试工程团队博客

经过一段时间的筹划,来自Adobe中国研发中心的AIR测试工程团队推出了他们的博客 — AIR爱好者。这个团队博客主要面向中国的AIR技术开发者和爱好者提供于AIR有关的技术资讯和帮助,目前已经有了20篇文章,将来会不定期更新高质量的AIR资讯,。在我看来,这是他们构建一个与外界开发者交流和分享知识的快速通道。

http://airfans.javaeye.com

用来自他们自己的话来介绍这个博客:
“我们团队的目标持续提高产品质量,满足广大客户的需求,提高满意度。团队组建于2009年4月,半年中陆续有十多位优秀工程师加入,他们中间有具备丰富经验的测试,开发工程师,也有来自于一流学府的实习生,可谓卧虎藏龙。大家来自于五湖四海,正是Adobe AIR 优秀的技术和无限的应用前景把我们吸引到一起,工作中充满着挑战,但也充满着欢乐,这是一个拥有激情,高速发展团队,所有的创新,汗水都将使我们往目标更前近一步。团队成员包括:南瓜,桂圆,胖哥,豆沙包,米酒,糯米团,球状闪电,三皮,雨珠,小榔头,虾米,民工甲,战斗机, 和我 – 瓶子。通过以后的博文交流,大家可以有深一步了解。
我们将利用这个渠道向各位朋友分享一些专注于Adobe AIR产品信息和使用技巧,最新的业内动态等等。同时,我们更希望听到您的建议,想法,困惑,或是抱怨,这都将成为我们努力的方向。 ”

好了,还不快加入订阅?Enjoy it!

装饰模式(Decorator Pattern)

一、 装饰(Decorator)模式

装饰(Decorator)模式又名包装(Wrapper)模式[GOF95]。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

引言

孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成雀儿时,就可以在天上飞行。而不管悟空怎么变化,在二郎神眼里,他永远是那只猢狲。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。

二、 装饰模式的结构

装饰模式使用原来被装饰的类的一个子类的实例,把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。

在孙猴子的例子里,老孙变成的鱼儿相当于老孙的子类,这条鱼儿与外界的互动要通过”委派”,交给老孙的本尊,由老孙本尊采取行动。

装饰模式的类图如下图所示:

Pic64 装饰模式(Decorator Pattern)

在装饰模式中的各个角色有:

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Concrete Decorator)角色:负责给构件对象”贴上”附加的责任。

三、 装饰模式示例性代码

以下示例性代码实现了装饰模式:

// Decorator pattern -- Structural example
using System;
 
// "Component"
abstract class Component
{
  // Methods
  abstract public void Operation();
}
 
// "ConcreteComponent"
class ConcreteComponent : Component
{
  // Methods
  override public void Operation()
  {
    Console.WriteLine("ConcreteComponent.Operation()");
  }
}
 
// "Decorator"
abstract class Decorator : Component
{
  // Fields
  protected Component component;
 
  // Methods
  public void SetComponent( Component component )
  {
    this.component = component;
  }
 
  override public void Operation()
  {
    if( component != null )
      component.Operation();
  }
}
 
// "ConcreteDecoratorA"
class ConcreteDecoratorA : Decorator
{
  // Fields
  private string addedState;
 
  // Methods
  override public void Operation()
  {
    base.Operation();
    addedState = "new state";
    Console.WriteLine("ConcreteDecoratorA.Operation()");
  }
}
 
// "ConcreteDecoratorB"
class ConcreteDecoratorB : Decorator
{
  // Methods
  override public void Operation()
  {
    base.Operation();
    AddedBehavior();
    Console.WriteLine("ConcreteDecoratorB.Operation()");
  }
 
  void AddedBehavior()
  {
  }
}
 
/**//// 
/// Client test
/// 
public class Client
{
  public static void Main( string[] args )
  {
    // Create ConcreteComponent and two Decorators
    ConcreteComponent c = new ConcreteComponent();
    ConcreteDecoratorA d1 = new ConcreteDecoratorA();
    ConcreteDecoratorB d2 = new ConcreteDecoratorB();
 
    // Link decorators
    d1.SetComponent( c );
    d2.SetComponent( d1 );
 
    d2.Operation();
  }
}

上面的代码在执行装饰时是通过SetComponent方法实现的,在实际应用中,也有通过构造函数实现的,一个典型的创建过程可能如下:

new Decorator1(
   new Decorator2(
      new Decorator3(
         new ConcreteComponent()
         )
      )
   )

装饰模式常常被称为包裹模式,就是因为每一个具体装饰类都将下一个具体装饰类或者具体构件类包裹起来。

四、 装饰模式应当在什么情况下使用

在以下情况下应当使用装饰模式:

  1. 需要扩展一个类的功能,或给一个类增加附加责任。
  2. 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

五、 装饰模式实际应用的例子

该例子演示了通过装饰模式为图书馆的图书与录像带添加”可借阅”装饰。

// Decorator pattern -- Real World example
using System;
using System.Collections;
 
// "Component"
abstract class LibraryItem
{
  // Fields
  private int numCopies;
 
  // Properties
  public int NumCopies
  {
    get{ return numCopies; }
    set{ numCopies = value; }
  }
 
  // Methods
  public abstract void Display();
}
 
// "ConcreteComponent"
class Book : LibraryItem
{
  // Fields
  private string author;
  private string title;
 
  // Constructors
  public Book(string author,string title,int numCopies)
  {
    this.author = author;
    this.title = title;
    this.NumCopies = numCopies;
  }
 
  // Methods
  public override void Display()
  {
    Console.WriteLine( " Book ------ " );
    Console.WriteLine( " Author: {0}", author );
    Console.WriteLine( " Title: {0}", title );
    Console.WriteLine( " # Copies: {0}", NumCopies );
  }
}
 
// "ConcreteComponent"
class Video : LibraryItem
{
  // Fields
  private string director;
  private string title;
  private int playTime;
 
  // Constructor
  public Video( string director, string title,
    int numCopies, int playTime )
  {
    this.director = director;
    this.title = title;
    this.NumCopies = numCopies;
    this.playTime = playTime;
  }
 
  // Methods
  public override void Display()
  {
    Console.WriteLine( " Video ----- " );
    Console.WriteLine( " Director: {0}", director );
    Console.WriteLine( " Title: {0}", title );
    Console.WriteLine( " # Copies: {0}", NumCopies );
    Console.WriteLine( " Playtime: {0}", playTime );
  }
}
 
// "Decorator"
abstract class Decorator : LibraryItem
{
  // Fields
  protected LibraryItem libraryItem;
 
  // Constructors
  public Decorator ( LibraryItem libraryItem )
  { this.libraryItem = libraryItem; }
 
  // Methods
  public override void Display()
  { libraryItem.Display(); }
}
 
// "ConcreteDecorator"
class Borrowable : Decorator
{
  // Fields
  protected ArrayList borrowers = new ArrayList();
 
  // Constructors
  public Borrowable( LibraryItem libraryItem )
    : base( libraryItem ) {}
 
  // Methods
  public void BorrowItem( string name )
  {
    borrowers.Add( name );
    libraryItem.NumCopies--;
  }
 
  public void ReturnItem( string name )
  {
    borrowers.Remove( name );
    libraryItem.NumCopies++;
  }
 
  public override void Display()
  {
    base.Display();
    foreach( string borrower in borrowers )
      Console.WriteLine( " borrower: {0}", borrower );
  }
}
 
/**//// 
///  DecoratorApp test
/// 
public class DecoratorApp
{
  public static void Main( string[] args )
  {
    // Create book and video and display
    Book book = new Book( "Schnell", "My Home", 10 );
    Video video = new Video( "Spielberg",
      "Schindler's list", 23, 60 );
    book.Display();
    video.Display();
 
    // Make video borrowable, then borrow and display
    Console.WriteLine( " Video made borrowable:" );
    Borrowable borrowvideo = new Borrowable( video );
    borrowvideo.BorrowItem( "Cindy Lopez" );
    borrowvideo.BorrowItem( "Samuel King" );
 
    borrowvideo.Display();
  }
}

六、 使用装饰模式的优点和缺点

使用装饰模式主要有以下的优点:

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
  3. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。

使用装饰模式主要有以下的缺点:

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

七、 模式实现的讨论

大多数情况下,装饰模式的实现都比上面定义中给出的示意性实现要简单。对模式进行简化时需要注意以下的情况:

(1)一个装饰类的接口必须与被装饰类的接口相容。

(2)尽量保持Component作为一个”轻”类,不要把太多的逻辑和状态放在Component类里。

(3)如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。如下图所示:

Pic65 装饰模式(Decorator Pattern)

(4)如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

八、 透明性的要求

透明的装饰模式

装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。换言之,下面的做法是对的:

Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);

而下面的做法是不对的:

ConcreteComponent c = new ConcreteDecorator()

这就是前面所说的,装饰模式对客户端是完全透明的含义。

用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把老孙变成的雀儿当成雀儿,而不是老孙,那就被老孙骗了,而这是不应当发生的。

下面的做法是不对的:

大圣本尊 c = new 大圣本尊();
雀儿 bird = new 雀儿 (c)

半透明的装饰模式

然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而雀儿有。这就意味着雀儿应当有一个新的fly()方法。

这就导致了大多数的装饰模式的实现都是”半透明”(semi-transparent)的,而不是完全”透明”的。换言之,允许装饰模式改变接口,增加新的方法。即声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:

齐天大圣 c = new 大圣本尊();
雀儿 bird = new 雀儿(c);
bird.fly()

齐天大圣接口根本没有fly()这个方法,而雀儿接口里有这个方法。

九、 装饰模式在.NET中的应用

.net中存在如下类模型:

Pic66 装饰模式(Decorator Pattern)

下面的代码段用来将XmlDocument的内容格式输出。我们可以体会Decorator模式在这里所起的作用。

// 生成ConcreteComponent(内存流ms)
MemoryStream ms = new MemoryStream();
 
// 用XmlTextWriter对内存流 ms 进行装饰
// 此处使用了半透明的装饰模式
XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting = Formatting.Indented;
 
// 对装饰xtw的操作会转而操作本体-内存流ms
xmlDoc.Save(xtw);
 
byte[] buf = ms.ToArray();
txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);

迭代器模式(Iterator Pattern)

 

一、 引言

  迭代这个名词对于熟悉Java的人来说绝对不陌生。我们常常使用JDK提供的迭代接口进行java collection的遍历:

Iterator it = list.iterator();
while(it.hasNext()){
 //using “it.next();”do some businesss logic
}

而这就是关于迭代器模式应用很好的例子。

  二、 定义与结构

  迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。

  从定义可见,迭代器模式是为容器而生。很明显,对容器对象的访问必然涉及到遍历算法。你可以一股脑的将遍历方法塞到容器对象中去;或者根本不去提供什么遍历算法,让使用容器的人自己去实现去吧。这两种情况好像都能够解决问题。

  然而在前一种情况,容器承受了过多的功能,它不仅要负责自己“容器”内的元素维护(添加、删除等等),而且还要提供遍历自身的接口;而且由于遍历状态保存的问题,不能对同一个容器对象同时进行多个遍历。第二种方式倒是省事,却又将容器的内部细节暴露无遗。

  而迭代器模式的出现,很好的解决了上面两种情况的弊端。先来看下迭代器模式的真面目吧。

  迭代器模式由以下角色组成:

  1) 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。

  2) 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。

  3) 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。

  4) 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。

  迭代器模式的类图如下:

7k2946abt12n 迭代器模式(Iterator Pattern)

从结构上可以看出,迭代器模式在客户与容器之间加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器内部细节的暴露,而且也使得设计符号“单一职责原则”。

  注意,在迭代器模式中,具体迭代器角色和具体容器角色是耦合在一起的——遍历算法是与容器的内部细节紧密相关的。为了使客户程序从与具体迭代器角色耦合的困境中脱离出来,避免具体迭代器角色的更换给客户程序带来的修改,迭代器模式抽象了具体迭代器角色,使得客户程序更具一般性和重用性。这被称为多态迭代。

  三、 举例

  由于迭代器模式本身的规定比较松散,所以具体实现也就五花八门。我们在此仅举一例,根本不能将实现方式一一呈现。因此在举例前,我们先来列举下迭代器模式的实现方式。

  1.迭代器角色定义了遍历的接口,但是没有规定由谁来控制迭代。在Java collection的应用中,是由客户程序来控制遍历的进程,被称为外部迭代器;还有一种实现方式便是由迭代器自身来控制迭代,被称为内部迭代器。外部迭代器要比内部迭代器灵活、强大,而且内部迭代器在java语言环境中,可用性很弱。

  2.在迭代器模式中没有规定谁来实现遍历算法。好像理所当然的要在迭代器角色中实现。因为既便于一个容器上使用不同的遍历算法,也便于将一种遍历算法应用于不同的容器。但是这样就破坏掉了容器的封装——容器角色就要公开自己的私有属性,在java中便意味着向其他类公开了自己的私有属性。

  那我们把它放到容器角色里来实现好了。这样迭代器角色就被架空为仅仅存放一个遍历当前位置的功能。但是遍历算法便和特定的容器紧紧绑在一起了。

  而在Java Collection的应用中,提供的具体迭代器角色是定义在容器角色中的内部类。这样便保护了容器的封装。但是同时容器也提供了遍历算法接口,你可以扩展自己的迭代器。

  好了,我们来看下Java Collection中的迭代器是怎么实现的吧。

//迭代器角色,仅仅定义了遍历接口
 
public interface Iterator {
 boolean hasNext();
 Object next();
 void remove();
}
 
//容器角色,这里以List为例。它也仅仅是一个接口,就不罗列出来了
//具体容器角色,便是实现了List接口的ArrayList等类。为了突出重点这里指罗列和迭代器相关的内容
//具体迭代器角色,它是以内部类的形式出来的。AbstractList是为了将各个具体容器角色的公共部分提取出来而存在的。
 
public abstract class AbstractList extends AbstractCollection implements List {
……
//这个便是负责创建具体迭代器角色的工厂方法
public Iterator iterator() {
 return new Itr();
}
 
//作为内部类的具体迭代器角色
 
private class Itr implements Iterator {
 int cursor = 0;
 int lastRet = -1;
 int expectedModCount = modCount;
 
 public boolean hasNext() {
  return cursor != size();
 }
 
 public Object next() {
  checkForComodification();
  try {
   Object next = get(cursor);
   lastRet = cursor++;
   return next;
  } catch(IndexOutOfBoundsException e) {
   checkForComodification();
   throw new NoSuchElementException();
  }
 }
 
 public void remove() {
  if (lastRet == -1)
   throw new IllegalStateException();
   checkForComodification();
 
  try {
   AbstractList.this.remove(lastRet);
   if (lastRet < cursor)
    cursor--;
   lastRet = -1;
   expectedModCount = modCount;
  } catch(IndexOutOfBoundsException e) {
   throw new ConcurrentModificationException();
  }
 }
 
 final void checkForComodification() {
  if (modCount != expectedModCount)
   throw new ConcurrentModificationException();
 }
}

至于迭代器模式的使用。正如引言中所列那样,客户程序要先得到具体容器角色,然后再通过具体容器角色得到具体迭代器角色。这样便可以使用具体迭代器角色来遍历容器了……

  四、 实现自己的迭代器

  在实现自己的迭代器的时候,一般要操作的容器有支持的接口才可以。而且我们还要注意以下问题:

  在迭代器遍历的过程中,通过该迭代器进行容器元素的增减操作是否安全呢?

  在容器中存在复合对象的情况,迭代器怎样才能支持深层遍历和多种遍历呢?

  以上两个问题对于不同结构的容器角色,各不相同,值得考虑。

  五、 适用情况

  由上面的讲述,我们可以看出迭代器模式给容器的应用带来以下好处:

  1) 支持以不同的方式遍历一个容器角色。根据实现方式的不同,效果上会有差别。

  2) 简化了容器的接口。但是在java Collection中为了提高可扩展性,容器还是提供了遍历的接口。

  3) 对同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每一个迭代器对象中的。

  由此也能得出迭代器模式的适用范围:

  1) 访问一个容器对象的内容而无需暴露它的内部表示。

  2) 支持对容器对象的多种遍历。

  3) 为遍历不同的容器结构提供一个统一的接口(多态迭代)。

访问者模式(Visitor Pattern)

一、 访问者(Visitor)模式

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

问题提出

System.Collection命名空间下提供了大量集合操作对象。但大多数情况下处理的都是同类对象的聚集。换言之,在聚集上采取的操作都是一些针对同类型对象的同类操作。但是如果针对一个保存有不同类型对象的聚集采取某种操作该怎么办呢?

粗看上去,这似乎不是什么难题。可是如果需要针对一个包含不同类型元素的聚集采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型做类型判断的条件转移语句。这个时候,使用访问者模式就是一个值得考虑的解决方案。

访问者模式

访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。

数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做”双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。

双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。

二、 访问者模式的结构

如下图所示,这个静态图显示了有两个具体访问者和两个具体节点的访问者模式的设计,必须指出的是,具体访问者的数目与具体节点的数目没有任何关系,虽然在这个示意性的系统里面两者的数目都是两个。

PicX00113 访问者模式(Visitor Pattern)

访问者模式涉及到抽象访问者角色、具体访问者角色、抽象节点角色、具体节点角色、结构对象角色以及客户端角色。

  • 抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
  • 抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量。
  • 具体节点(Node)角色:实现了抽象元素所规定的接受操作。
  • 结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。

三、 示意性源代码

// Visitor pattern -- Structural example
using System;
using System.Collections;
 
// "Visitor"
abstract class Visitor
{
  // Methods
  abstract public void VisitConcreteElementA(
    ConcreteElementA concreteElementA );
  abstract public void VisitConcreteElementB(
    ConcreteElementB concreteElementB );
}
 
// "ConcreteVisitor1"
class ConcreteVisitor1 : Visitor
{
  // Methods
  override public void VisitConcreteElementA(
    ConcreteElementA concreteElementA )
  {
    Console.WriteLine( "{0} visited by {1}",
      concreteElementA, this );
  }
 
  override public void VisitConcreteElementB(
    ConcreteElementB concreteElementB )
  {
    Console.WriteLine( "{0} visited by {1}",
      concreteElementB, this );
  }
}
 
// "ConcreteVisitor2"
class ConcreteVisitor2 : Visitor
{
  // Methods
  override public void VisitConcreteElementA(
    ConcreteElementA concreteElementA )
  {
    Console.WriteLine( "{0} visited by {1}",
      concreteElementA, this );
  }
  override public void VisitConcreteElementB(
    ConcreteElementB concreteElementB )
  {
    Console.WriteLine( "{0} visited by {1}",
      concreteElementB, this );
  }
}
 
// "Element"
abstract class Element
{
  // Methods
  abstract public void Accept( Visitor visitor );
}
 
// "ConcreteElementA"
class ConcreteElementA : Element
{
  // Methods
  override public void Accept( Visitor visitor )
  {
    visitor.VisitConcreteElementA( this );
  }
 
  public void OperationA()
  {
  }
}
 
// "ConcreteElementB"
class ConcreteElementB : Element
{
  // Methods
  override public void Accept( Visitor visitor )
  {
    visitor.VisitConcreteElementB( this );
  }
 
  public void OperationB()
  {
  }
}
 
// "ObjectStructure"
class ObjectStructure
{
  // Fields
  private ArrayList elements = new ArrayList();
 
  // Methods
  public void Attach( Element element )
  {
    elements.Add( element );
  }
 
  public void Detach( Element element )
  {
    elements.Remove( element );
  }
 
  public void Accept( Visitor visitor )
  {
    foreach( Element e in elements )
      e.Accept( visitor );
  }
}
 
/**//// 
/// Client test
/// 
public class Client
{
  public static void Main( string[] args )
  {
    // Setup structure
    ObjectStructure o = new ObjectStructure();
    o.Attach( new ConcreteElementA() );
    o.Attach( new ConcreteElementB() );
 
    // Create visitor objects
    ConcreteVisitor1 v1 = new ConcreteVisitor1();
    ConcreteVisitor2 v2 = new ConcreteVisitor2();
 
    // Structure accepting visitors
    o.Accept( v1 );
    o.Accept( v2 );
  }
}

结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中就是节点ConcreteElementA和节点ConcreteElementB。首先ConcreteElementA会被访问到,这个访问是由以下的操作组成的:

  1. ConcreteElementA对象的接受方法被调用,并将VisitorA对象本身传入;
  2. ConcreteElementA对象反过来调用VisitorA对象的访问方法,并将ConcreteElementA对象本身传入;
  3. VisitorA对象调用ConcreteElementA对象的商业方法operationA( )。

从而就完成了双重分派过程,接着,ConcreteElementB会被访问,这个访问的过程和ConcreteElementA被访问的过程是一样的。

因此,结构对象对聚集元素的遍历过程就是对聚集中所有的节点进行委派的过程,也就是双重分派的过程。换言之,系统有多少个节点就会发生多少个双重分派过程。

四、 一个实际应用Visitor模式的例子

以下的例子演示了Employee对象集合允许被不同的Visitor(IncomeVisitor与VacationVisitor)访问其中的内容。

// Visitor pattern -- Real World example
using System;
using System.Collections;
 
// "Visitor"
abstract class Visitor
{
  // Methods
  abstract public void Visit( Element element );
}
 
// "ConcreteVisitor1"
class IncomeVisitor : Visitor
{
  // Methods
  public override void Visit( Element element )
  {
    Employee employee = ((Employee)element);
 
    // Provide 10% pay raise
    employee.Income *= 1.10;
    Console.WriteLine( "{0}'s new income: {1:C}",
      employee.Name, employee.Income );
  }
}
 
// "ConcreteVisitor2"
class VacationVisitor : Visitor
{
  public override void Visit( Element element )
  {
    Employee employee = ((Employee)element);
 
    // Provide 3 extra vacation days
    employee.VacationDays += 3;
    Console.WriteLine( "{0}'s new vacation days: {1}",
      employee.Name, employee.VacationDays );
  }
}
 
// "Element"
abstract class Element
{
  // Methods
  abstract public void Accept( Visitor visitor );
}
 
// "ConcreteElement"
class Employee : Element
{
  // Fields
  string name;
  double income;
  int vacationDays;
 
  // Constructors
  public Employee( string name, double income,
    int vacationDays )
  {
    this.name = name;
    this.income = income;
    this.vacationDays = vacationDays;
  }
 
  // Properties
  public string Name
  {
    get{ return name; }
    set{ name = value; }
  }
 
  public double Income
  {
    get{ return income; }
    set{ income = value; }
  }
 
  public int VacationDays
  {
    get{ return vacationDays; }
    set{ vacationDays = value; }
  }
 
  // Methods
  public override void Accept( Visitor visitor )
  {
    visitor.Visit( this );
  }
}
 
// "ObjectStructure"
class Employees
{
  // Fields
  private ArrayList employees = new ArrayList();
 
  // Methods
  public void Attach( Employee employee )
  {
    employees.Add( employee );
  }
 
  public void Detach( Employee employee )
  {
    employees.Remove( employee );
  }
 
  public void Accept( Visitor visitor )
  {
    foreach( Employee e in employees )
      e.Accept( visitor );
  }
}
 
/**//// 
/// VisitorApp test
/// 
public class VisitorApp
{
  public static void Main( string[] args )
  {
    // Setup employee collection
    Employees e = new Employees();
    e.Attach( new Employee( "Hank", 25000.0, 14 ) );
    e.Attach( new Employee( "Elly", 35000.0, 16 ) );
    e.Attach( new Employee( "Dick", 45000.0, 21 ) );
 
    // Create two visitors
    IncomeVisitor v1 = new IncomeVisitor();
    VacationVisitor v2 = new VacationVisitor();
 
    // Employees are visited
    e.Accept( v1 );
    e.Accept( v2 );
  }
}

五、 在什么情况下应当使用访问者模式

有意思的是,在很多情况下不使用设计模式反而会得到一个较好的设计。换言之,每一个设计模式都有其不应当使用的情况。访问者模式也有其不应当使用的情况,让我们
先看一看访问者模式不应当在什么情况下使用。

倾斜的可扩展性

访问者模式仅应当在被访问的类结构非常稳定的情况下使用。换言之,系统很少出现需要加入新节点的情况。如果出现需要加入新节点的情况,那么就必须在每一个访问对象里加入一个对应于这个新节点的访问操作,而这是对一个系统的大规模修改,因而是违背”开一闭”原则的。

访问者模式允许在节点中加入新的方法,相应的仅仅需要在一个新的访问者类中加入此方法,而不需要在每一个访问者类中都加入此方法。

显然,访问者模式提供了倾斜的可扩展性设计:方法集合的可扩展性和类集合的不可扩展性。换言之,如果系统的数据结构是频繁变化的,则不适合使用访问者模式。

“开一闭”原则和对变化的封装

面向对象的设计原则中最重要的便是所谓的”开一闭”原则。一个软件系统的设计应当尽量做到对扩展开放,对修改关闭。达到这个原则的途径就是遵循”对变化的封装”的原则。这个原则讲的是在进行软件系统的设计时,应当设法找出一个软件系统中会变化的部分,将之封装起来。

很多系统可以按照算法和数据结构分开,也就是说一些对象含有算法,而另一些对象含有数据,接受算法的操作。如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。

反过来,如果这样一个系统的数据结构对象易于变化,经常要有新的数据对象增加进来的话,就不适合使用访问者模式。因为在访问者模式中增加新的节点很困难,要涉及到在抽象访问者和所有的具体访问者中增加新的方法。

六、 使用访问者模式的优点和缺点

访问者模式有如下的优点:

  1. 访问者模式使得增加新的操作变得很容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。
  2. 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
  3. 访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。迭代子只能访问属于同一个类型等级结构的成员对象,而不能访问属于不同等级结构的对象。访问者模式可以做到这一点。
  4. 积累状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的节点对象中。这是有益于系统维护的优点。

访问者模式有如下的缺点:

  1. 增加新的节点类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
  2. 破坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。

状态模式(State Pattern)

一、引子

  状态模式自身结构非常简单——前面刚刚介绍了几个结构比较简单的设计模式,和他们一样,状态模式在具体实现上留下了可变换的余地。我前面已经介绍过它的孪生兄妹策略模式了,大家可以两者比较着阅读。本文将会讨论两者的区别。

  二、定义与结构

  GOF《设计模式》中给状态模式下的定义为:允许一个对象在其内部状态改变时改变它的行为。这个对象看起来似乎修改了它的类。看起来,状态模式好像是神通广大——居然能够“修改自身的类”!

  能够让程序根据不同的外部情况来做出不同的响应,最直接的方法就是在程序中将这些可能发生的外部情况全部考虑到,使用if else 语句来进行代码响应选择。但是这种方法对于复杂一点的状态判断,就会显得杂乱无章,容易产生错误;而且增加一个新的状态将会带来大量的修改。这个时候“能够修改自身”的状态模式的引入也许是个不错的主意。

  状态模式可以有效的替换充满在程序中的if else语句:将不同条件下的行为封装在一个类里面,再给这些类一个统一的父类来约束他们。来看一下状态模式的角色组成吧:

  1) 使用环境(Context)角色:客户程序是通过它来满足自己的需求。它定义了客户程序需要的接口;并且维护一个具体状态角色的实例,这个实例来决定当前的状态。

  2) 状态(State)角色:定义一个接口以封装与使用环境角色的一个特定状态相关的行为。

  3) 具体状态(Concrete State)角色:实现状态角色定义的接口。

  类图如下,结构非常简单也与策略模式非常相似。

u104af948942 状态模式(State Pattern)

三、实现

  由于状态模式结构非常简单,所以在这里罗列一些反映状态模式实现结构的代码没有什么太大的作用。如果你有兴趣的话可以按照上面类图来编写一下。

  在引子中已经提到,状态模式在具体实现上存在不同的方案。因此这里重点就这些不同的实现方式进行介绍和讨论。

  首先,实现时是否将状态角色、具体状态角色暴露给客户程序?按照GOF的建议是不希望将状态角色暴露给客户程序的,与客户程序打交道的仅仅是使用环境角色,客户是不知道系统是怎么实现的,更不关心什么有几个具体状态。但是当使用环境角色中的初始状态紧紧依赖于客户程序时,适乎暴露是在所难免的——这就与策略模式异常相似了!

  具体状态角色中的行为一般是与使用环境角色密切相关的。因此这里便有了一个小细节:我们把使用环境角色作为参数传递进入具体状态角色后,是在具体状态角色中来实现状态响应行为;还是仅仅调用在使用环境角色中已经实现了的方法?由于这些行为往往与使用环境角色相关,所以按照《重构》一书的“指导”——后一种实现方法是比较地道的。

  从定义可知,状态模式是要应对状态转换的。那么状态的转换在哪里定义呢?你可以选择在使用环境角色的代码中来表现出来,当然这便意味着状态转变的规则就固定下来了。GOF还给出了另外一种稍微灵活一点的实现方式:在每一个具体状态角色中来指定后续状态以及何时进行转换。

  其实在java强大的反射机制的支持下,我们还可以将状态的转换做的更加灵活——我们可以将状态转换的规则写在.xml等等的配置文件里面甚至是数据库中,我们姑且叫做状态转换表。进行转换前,根据状态转换表来读取下一个状态,然后利用反射获得具体的状态对象……。看起来很不错的样子,只是效率可能低一些,在企业应用中这应该不是最重要的。

  状态模式已经被我们想象着“实现”了一番。那么状态模式的引入会给我们的程序带来哪些优势呢?前面我们已经说过:状态模式的引入免除了代码中复杂而庸长的逻辑判断语句。而且具体状态角色将具体状态和它对应的行为封装了起来,这使得增加一种新的状态变得简单一些。而且如果设计合理得话,具体状态角色可以被重用(和策略模式一样,可以考虑使用享元模式来实现)。

  使用状态模式也会带来一些问题。每个状态对应一个具体的状态类,使得整体分散,逻辑不太清晰。当然对于一个状态非常多的系统,状态模式带来的优点还是大于它的缺点的。

  由上面的分析就可以很明确的知道什么时候该使用状态模式了。下面是GOF在《设计模式》中给出的状态模式的适用情况:

  1) 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。

  2) 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。

  四、状态VS策略

  仔细对比状态模式和策略模式,难免会产生疑问:这两个明明是一个东西嘛!下面我们就来分析下两者区别。

  首先我要声明,在实际应用中只要能够使得你的代码灵活漂亮起来,何必计较这些方方面面的差别呢?

  Brandon Goldfedder在《模式的乐趣》里是怎么说的:“strategy模式在结构上与state模式非常相似,但是在概念上,他们的目的差异非常大。区分这两个模式的关键是看行为是由状态驱动还是由一组算法驱动,这条规则似乎有点随意,但是在判断时还是需要考虑它。通常,State模式的“状态”是在对象内部的,Strategy模式的“策略”可以在对象外部,不过这也不是一条严格、可靠的规则。”

  我很同意Brandon Goldfedder的观点。这两个模式的划分,就在于使用的目的是不同的——策略模式用来处理算法变化,而状态模式则是处理状态变化(好玄乎阿)。

  策略模式中,算法是否变化完全是由客户程序开决定的,而且往往一次只能选择一种算法,不存在算法中途发生变化的情况。从《深入浅出策略模式》中的例子可以很好的看出。

  而状态模式如定义中所言,在它的生命周期中存在着状态的转变和行为得更改,而且状态变化是一个线性的整体;对于客户程序来言,这种状态变化往往是透明的。

using System; 

namespace DesignPattern.State
{
    /**//**//**////
    /// 抽象状态 状态接口
    ///
    public interface ITcpState
    {
        void Open();
        void Close();
        void Acknowledge();
    } 

    /**//**//**////
    /// 此类相当于Context
    ///
    public class TcpConnection
    {
        private ITcpState state; 

        public ITcpState State
        {
            set{this.state=value;}
        } 

        public void Open()
        {
            this.State=new TcpEstablished();
            this.state.Open();
            this.State=new TcpListen();
        } 

        public void Close()
        {
            this.state.Close();
            this.State=new TcpClosed();
        } 

        public void Acknowledge()
        {
            this.state.Acknowledge();
        }
    } 

    public class TcpEstablished : ITcpState
    { 

        public void Open()
        {
            Console.WriteLine("Have opened,can not open again!");
        } 

        public void Close()
        {
            Console.WriteLine("Closing");
        } 

        public void Acknowledge()
        {
            Console.WriteLine("TcpEstablished!");
        } 

    } 

    public class TcpListen : ITcpState
    {
        public void Open()
        {
            Console.WriteLine("Have opened,can not open again!");
        } 

        public void Close()
        {
            Console.WriteLine("Closing");
        } 

        public void Acknowledge()
        {
            Console.WriteLine("TcpListen!");
        } 

    } 

    public class TcpClosed : ITcpState
    {
        public void Open()
        {
            Console.WriteLine("Openning");
        } 

        public void Close()
        {
            Console.WriteLine("Have closed,can not close again!");
        } 

        public void Acknowledge()
        {
            Console.WriteLine("TcpClosed!");
        } 

    } 

    public class Client
    {
        public static void Main()
        {
            TcpConnection tcpcon=new TcpConnection();
            tcpcon.Open();
            tcpcon.Acknowledge();
            tcpcon.Open();
            tcpcon.Close();
            tcpcon.Acknowledge();
            tcpcon.Close();
        }
    }
}

一键清除系统垃圾 批处理脚本代码

懒得每次去找 麻烦的要死 自己在自己BLOG里MARK一下。。。

@echo off
echo 清除系统垃圾过程中,请稍等……
del /f /s /q %systemdrive%\*.tmp
del /f /s /q %systemdrive%\*._mp
del /f /s /q %systemdrive%\*.log
del /f /s /q %systemdrive%\*.gid
del /f /s /q %systemdrive%\*.chk
del /f /s /q %systemdrive%\*.old
del /f /s /q %systemdrive%\recycled\*.*
del /f /s /q %windir%\*.bak
del /f /s /q %windir%\prefetch\*.*
rd /s /q %windir%\temp & md %windir%\temp
del /f /q %userprofile%\cookies\*.*
del /f /q %userprofile%\recent\*.*
del /f /s /q “%userprofile%\Local Settings\Temporary Internet Files\*.*”
del /f /s /q “%userprofile%\Local Settings\Temp\*.*”
del /f /s /q “%userprofile%\recent\*.*”
echo 清除系统垃圾完成!
echo. & pause

用法很简单。。
电脑里随便找个地方 新建个文本文档 或者记事本也成。。
然后把上面这段复制进去
保存后 把文本文档名字改成 *.bat
*号随便你改成什么。。
你要喜欢 改你名字也成。。。

AS3将Bitmap序列化(将BitmapData保存为原生Binary/ByteArray)

应用程序需要将位图图像保存到本地或发送到服务端时, 通常的方法是在发送数据前将图像通过PNG或JPEG编码。如果只是想保存位图图像,只要序列化BitmapData即可,将图像转换为JPEG/PNG是完全没有必要的。

BitmapData 转换为 ByteArray

获得BitmapData对应的字节数组, 所要做的只是调用getPixels()方法。getPixels()方法需要指定捕捉区域;最便捷的方法就是使用即将序列化的BitmapData的rect属性。

// ActionScript 3.0
 
// 假定“bitmapImage”是需要序列化的位图对象
 
var bytes:ByteArray = bitmapImage.bitmapData.getPixels(bitmapImage.bitmapData.rect);

这个方法会返回一个ByteArray对象,BitmapData的每个像素对应ByteArray对象中的一个4字节的无符号整型。这意味着如果是20×20的位图图像, 对应的ByteArray对象在压缩前有1600个字节(20×20x4=1600)
得到ByteArray对象后, 压缩:

var bytes:ByteArray = bitmapImage.bitmapData.getPixels(bitmapImage.bitmapData.rect);
 
bytes.compress();

得到了位图图像无损压缩的二进制数据了.

位图尺寸(宽与高)

这样看来, 得到位图图像对应的ByteArray数据很容易 – 只要调用getPixel()方法即可.当然, 将ByteArray再构造为位图图像才能证明数据是有用的. 除像素数据外, 字节数组不能为位图图像指定尺寸.就是说你得把尺寸信息也要保存在字节数组里.其实只要保存高度或宽度即可, 因为已经知道了像素总数, 通过计算便能算出另一个.

下面的代码中,字节数组前4个字节保存BitmapData的宽度, 接下来再保存图像字节数组.

var bytes:ByteArray = new ByteArray();
 
bytes.writeUnsignedInt(bitmapImage.bitmapData.width);
 
bytes.writeBytes(bitmapImage.bitmapData.getPixels(bitmapImage.bitmapData.rect));
 
bytes.compress();

保存文件

前面的工作完成后就可以使用常用的方法保存二进制数据了(发送给服务端脚本,AIR本地文件API,SharedObject以及FP10 FileReference等等).这个例子中, 我们通过使用FileReference类的save()方法(需要Flash Player 10)将二进制数据保存到本地存储器中.由于Flash Player的安全措施,save()方法只有在用户交互事件中才能够调用(例如鼠标点击事件).因此需要新建一个按钮并附加一个监听器, 在事件处理方法中调用save()方法.

// ** 需要Flash Player 10以上版本 **
 
function on_buttonClick(evt:MouseEvent):void
 
{
 
        var bytes:ByteArray = new ByteArray();
 
        bytes.writeUnsignedInt(bitmapImage.bitmapData.width); // 保存图像宽度
 
        bytes.writeBytes(bitmapImage.bitmapData.getPixels(bitmapImage.bitmapData.rect)); //保存图像字节数组
 
        bytes.compress();
 
        new FileReference().save(bytes, "image.bmd"); // 默认文件名: "image.bmd"
 
}

文件可任意命名.上面的例子中, 我使用了”.bmd”(BitmapData)做为文件扩展名,不过这只是一个自己想出的文件类型.最终保存的文件无有效MIME的, 不会当作已知的文件类型运行 – 这是我们自定义的二进制数据格式文件, 仅仅是用来保存图像数据, 方便以后我们的程序重用.

ByteArray 转换为 BitmapData

上面提到过, 我们要将保存的数据重构, 这样才能还原出原始位图图像.
首先, 通过URLLoader加载文件:

var ldr:URLLoader        = new URLLoader();
 
ldr.dataFormat        = URLLoaderDataFormat.BINARY; // ** 这里一定要指定dataFormat为URLLoaderDataFormat.BINARY **
 
ldr.addEventListener(Event.COMPLETE, on_fileLoad);
 
ldr.addEventListener(IOErrorEvent.IO_ERROR, on_fileLoadError);
 
ldr.load(new URLRequest(pathToBitmapDataFile));

事件处理方法on_fileLoad:

function on_fileLoad(evt:Event):void
 
{
 
        if (evt.type == Event.COMPLETE)
 
        {
 
                var data:ByteArray = URLLoader(evt.target).data as ByteArray;
 
                if (data)
 
                {
 
                        try
 
                        {
 
                                data.uncompress();
 
                        }
 
                        catch(e:Error)
 
                        {
 
                        }
 
                        // 此时的数据已经是解压后的字节数组了
 
                        // ... 处理数据 ...
 
                }
 
        }
 
}

现在我们来取出位图图像的尺寸. 还记得之前我们在二进制数据的头4个字节保存了宽度值吧.

// 数据解压后
 
var width:int = data.readUnsignedInteger(); // 起始的4个字节

得到高度:

// after data.uncompress()
 
var height:int = ((data.length - 4) / 4) / width;
 
// (data.length - 4) ** 去掉开始的4个字节,其余的便是位图的字节数组了 **
 
// ((data.length - 4) / 4) ** 每个像素4个字节长, 所以要除以4得到总像素数 **
 
// ((data.length - 4) / 4) / 宽度 ** 记住,因为是矩形才能这样计算出高度 **

注意:如果要忽略尺寸计算, 可以把高宽同时保存在二进制数据中.两种方法都是可行的, 可自行选择.

得到尺寸后, 就可以使用setPixels()方法重构Bitmap对象了.

var bmd:BitmapData = new BitmapData(width, height, true, 0); // 32位支持alpha通道的位图
 
bmd.setPixels(bmd.rect, data); // 数据的position指向第5个字节了
 
 
 
var bm:Bitmap = new Bitmap(bmd);
 
addChild(bm);

结论

以上方法展现了将BitmapData数据转换为ByteArray, 保存ByteArray, 然后再将已保存的ByteArray重新构造为BitmapData的整个过程.虽然基本目标是能够把位图图像保存到服务器/本地存储器, 但上述技巧放在其他情况中也是十分有用的.例如, 得到图像的ByteArray数据后, 可以将其发送(post)到服务器做进一步处理. 也可用来裁减外部的JPEG/PNG图像文件,去掉所有的JPEG/PNG编码中含有的元数据信息(meta information), 只留下原始(raw)图像数据(文件可能更小了).当然了, 最终的二进制文件不能做为JPEG/PNG打开了, 但应用程序能够在运行时很容易的重构出相应的图像来.实际上,也可认为这是一种保护外部图片不被盗链的好方法.