Hycz's Blog

Life is a game. Why so serious?

Monthly Archives: October 2011

[转]Java Annotation入门

Java Annotation入门

作者:cleverpig

版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
原 文:[http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html]http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html[/url]
关键字:Java,annotation,标注

摘要:
本 文针对java初学者或者annotation初次使用者全面地说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简 单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义annotation生成javabean映射xml文件) 还需要进一步的研究和探讨。涉及到深入annotation的内容,作者将在后文《Java Annotation高级应用》中谈到。

同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,

一、为什么使用Annotation:

在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另 外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利 而且减少了错误。

二、Annotation工作方式:

在5.0 版之前的Java平台已经具有了一些ad hoc annotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个 javadoc tag也是一个ad hoc annotation用来说明一个方法已过时。从Java5.0版发布以来,5.0平台提供了一个正式的annotation功能:允许开发者定义、使用 自己的annoatation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotaion 的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt)组成。
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取。
当然annotation在某种程度上使javadoc tag更加完整。一般情况下,如果这个标记对java文档产生影响或者用于生成java文档的话,它应该作为一个javadoc tag;否则将作为一个annotation。

三、Annotation使用方法:

1。类型声明方式:
通常,应用程序并不是必须定义annotation类型,但是定义annotation类型并非难事。Annotation类型声明于一般的接口声明极为类似,区别只在于它在interface关键字前面使用“@”符号。
annotation 类型的每个方法声明定义了一个annotation类型成员,但方法声明不必有参数或者异常声明;方法返回值的类型被限制在以下的范围: primitives、String、Class、enums、annotation和前面类型的数组;方法可以有默认值。

下面是一个简单的annotation类型声明:
清单1:

    /**
     * Describes the Request-For-Enhancement(RFE) that led
     * to the presence of the annotated API element.
     */
    public @interface RequestForEnhancement {
        int    id();
        String synopsis();
        String engineer() default "[unassigned]";
        String date();    default "[unimplemented]";
    }

代码中只定义了一个annotation类型RequestForEnhancement。

2。修饰方法的annotation声明方式:
annotation 是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。 annotations由“@+annotation类型+带有括号的成员-值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。

A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:

    @RequestForEnhancement(
        id       = 2868724,
        synopsis = "Enable time-travel",
        engineer = "Mr. Peabody",
        date     = "4/1/3007"
    )
    public static void travelThroughTime(Date destination) { ... }

B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:

    /**
     * Indicates that the specification of the annotated API element
     * is preliminary and subject to change.
     */
    public @interface Preliminary { }

作为上面没有成员的annotation类型声明的简写方式:
清单4:

    @Preliminary public class TimeTravel { ... }

C:如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:

    /**
     * Associates a copyright notice with the annotated API element.
     */
    public @interface Copyright {
        String value();
    }

更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:

    @Copyright("2002 Yoyodyne Propulsion Systems")
    public class OscillationOverthruster { ... }

3。一个使用实例:
结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:

    import java.lang.annotation.*;

    /**
     * Indicates that the annotated method is a test method.
     * This annotation should be used only on parameterless static methods.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test { }

值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。

在 上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的 annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的 annotation只能用于修饰方法声明。

下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:

    public class Foo {
        @Test public static void m1() { }
        public static void m2() { }
        @Test public static void m3() {
            throw new RuntimeException("Boom");
        }
        public static void m4() { }
        @Test public static void m5() { }
        public static void m6() { }
        @Test public static void m7() {
            throw new RuntimeException("Crash");
        }
        public static void m8() { }
    }

Here is the testing tool:

    import java.lang.reflect.*;

    public class RunTests {
       public static void main(String[] args) throws Exception {
          int passed = 0, failed = 0;
          for (Method m : Class.forName(args[0]).getMethods()) {
             if (m.isAnnotationPresent(Test.class)) {
                try {
                   m.invoke(null);
                   passed++;
                } catch (Throwable ex) {
                   System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                   failed++;
                }
             }
          }
          System.out.printf("Passed: %d, Failed %d%n", passed, failed);
       }
    }

这 个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法被 annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行 通过/失败的方法数量。
下面文字表示了如何运行这个基于annotation的测试工具:

清单9:

    $ java RunTests Foo
    Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
    Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
    Passed: 2, Failed 2

四、Annotation分类:

根据annotation的使用方法和用途主要分为以下几类:

1。内建Annotation——Java5.0版在java语法中经常用到的内建Annotation:
@Deprecated用于修饰已经过时的方法;
@Override用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings用于通知java编译器禁止特定的编译警告。

下面代码展示了内建Annotation类型的用法:
清单10:

package com.bjinfotech.practice.annotation;

/**
 * 演示如何使用java5内建的annotation
 * 参考资料:
 * http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
 * http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
 * http://mindprod.com/jgloss/annotations.html
 * @author cleverpig
 *
 */
import java.util.List;

public class UsingBuiltInAnnotation {
        //食物类
        class Food{}
        //干草类
        class Hay extends Food{}
        //动物类
        class Animal{
                Food getFood(){
                        return null;
                }
                //使用Annotation声明Deprecated方法
                @Deprecated
                void deprecatedMethod(){
                }
        }
        //马类-继承动物类
        class Horse extends Animal{
                //使用Annotation声明覆盖方法
                @Override
                Hay getFood(){
                        return new Hay();
                }
                //使用Annotation声明禁止警告
                @SuppressWarnings({"deprecation","unchecked"})
                void callDeprecatedMethod(List horseGroup){
                        Animal an=new Animal();
                        an.deprecatedMethod();
                        horseGroup.add(an);
                }
        }
}

2。开发者自定义Annotation:由开发者自定义Annotation类型。
下面是一个使用annotation进行方法测试的sample:

AnnotationDefineForTestFunction类型定义如下:
清单11:

package com.bjinfotech.practice.annotation;

import java.lang.annotation.*;
/**
 * 定义annotation
 * @author cleverpig
 *
 */
//加载在VM中,在运行时进行映射
@Retention(RetentionPolicy.RUNTIME)
//限定此annotation只能标示方法
@Target(ElementType.METHOD)
public @interface AnnotationDefineForTestFunction{}

测试annotation的代码如下:

清单12:

package com.bjinfotech.practice.annotation;

import java.lang.reflect.*;/**
 * 一个实例程序应用前面定义的Annotation:AnnotationDefineForTestFunction
 * @author cleverpig
 *
 */
public class UsingAnnotation {
        @AnnotationDefineForTestFunction public static void method01(){}

        public static void method02(){}

        @AnnotationDefineForTestFunction public static void method03(){
                throw new RuntimeException("method03");
        }

        public static void method04(){
                throw new RuntimeException("method04");
        }

        public static void main(String[] argv) throws Exception{
                int passed = 0, failed = 0;
                //被检测的类名
                String className="com.bjinfotech.practice.annotation.UsingAnnotation";
                //逐个检查此类的方法,当其方法使用annotation声明时调用此方法
            for (Method m : Class.forName(className).getMethods()) {
               if (m.isAnnotationPresent(AnnotationDefineForTestFunction.class)) {
                  try {
                     m.invoke(null);
                     passed++;
                  } catch (Throwable ex) {
                     System.out.printf("测试 %s 失败: %s %n", m, ex.getCause());
                     failed++;
                  }
               }
            }
            System.out.printf("测试结果: 通过: %d, 失败: %d%n", passed, failed);
        }
}

3。使用第三方开发的Annotation类型
这也是开发人员所常常用到的一种方式。比如我们在使用Hibernate3.0时就可以利用Annotation生成数据表映射配置文件,而不必使用Xdoclet。

五、总结:

1。 前面的文字说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的 annotation应用(例如使用自定义annotation生成javabean映射xml文件)还需要进一步的研究和探讨。

2。同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,因为编译时的annotation要使用annotation processing tool。

涉及以上2方面的深入内容,作者将在后文《Java Annotation高级应用》中谈到。

六、参考资源:
·Matrix-Java开发者社区:http://www.matrix.org.cn
·http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·作者的Blog:http://blog.matrix.org.cn/page/cleverpig

Advertisements

Cassandra 0.8.0 源码分析——数据类型

一、Introduction

Cassandra中用到了一些可排序的数据类型,也就是放在db.marshal包中的那些类,他们实际上就是将一些基本类型,比如String,BigInteger,Long,ByteBuffer,UUID等,封装起来,最重要的是实现了Comparator接口,让这些封装后的数据类型可以用于排序集(例如SortedSet,TreeSet等),封装的过程中还加入了一些特有的方法。这些类的关系如下图(此外还有个异常类,不太重要):

二、设计特点

首先看看处于顶端的抽象类,AbstractType<T>,这个类虽然是抽象类,但是每次调用它的子类的构造器时总是会调用到它的构造器,这是其中的一个设计特点,先来看一个典型的AbstractType<T>的子类的域和构造器部分:

public class BytesType extends AbstractType<ByteBuffer>
{
    public static final BytesType instance = new BytesType();

    BytesType() {} // singleton
    ...
}

可以看到,这里使用了设计模式中的Singleton Pattern。仅有的那个静态常量是instance,当第一次调用到ByteType时,instance被初始化,实际上,instance成为了唯一的对外实例化ByteType的手段,而且成为了唯一的ByteType实例。这里用到的是传统的Singleton Pattern实现方法,一旦类被初始化,那么就会实例化,更好的是Bill Pugh的方案,不过已经不在本文讨论范围内。然而,可以看到,构造器中并无代码,于是,无疑是其父类的构造器被调用了,也就是AbstractType<T>中的构造器,那么,来看看AbstractType<T>的域和构造器部分:

public abstract class AbstractType<T> implements Comparator<ByteBuffer>
{
    public final Comparator<IndexInfo> indexComparator;
    public final Comparator<IndexInfo> indexReverseComparator;
    public final Comparator<IColumn> columnComparator;
    public final Comparator<IColumn> columnReverseComparator;
    public final Comparator<ByteBuffer> reverseComparator;

    protected AbstractType()
    {
        indexComparator = new Comparator<IndexInfo>()
        {
            public int compare(IndexInfo o1, IndexInfo o2)
            {
                return AbstractType.this.compare(o1.lastName, o2.lastName);
            }
        };
        indexReverseComparator = new Comparator<IndexInfo>()
        {
            public int compare(IndexInfo o1, IndexInfo o2)
            {
                return AbstractType.this.compare(o1.firstName, o2.firstName);
            }
        };
        columnComparator = new Comparator<IColumn>()
        {
            public int compare(IColumn c1, IColumn c2)
            {
                return AbstractType.this.compare(c1.name(), c2.name());
            }
        };
        columnReverseComparator = new Comparator<IColumn>()
        {
            public int compare(IColumn c1, IColumn c2)
            {
                return AbstractType.this.compare(c2.name(), c1.name());
            }
        };
        reverseComparator = new Comparator<ByteBuffer>()
        {
            public int compare(ByteBuffer o1, ByteBuffer o2)
            {
                if (o1.remaining() == 0)
                {
                    return o2.remaining() == 0 ? 0 : -1;
                }
                if (o2.remaining() == 0)
                {
                    return 1;
                }

                return -AbstractType.this.compare(o1, o2);
            }
        };
    }
    ...
}

可以看到,其实这里并没有做很多特别的操作,仅仅是初始化了5个Comparator,以供以后的子类调用,由于这5个对象仅仅实现了一个Comparator接口,所以它们存在的意义也就仅仅是提供一个compare方法,然后就可以作为参数填入。

除了上述的工作,剩下的就是实例化的意义。一旦实例化之后,就相当于得到了一个实现了Comparator接口的实例,于是在很多方法的调用中都可以将其填入。此外,Singleton的意义在于这个实例成为了某种特定的类型的一系列操作工具的入口,而不是什么保存某个特定类型数据的地方。

三、基本类型与封装后类型的关系

这里有一个问题,就是实现的接口Comparator<T>中,为什么填入的是ByteBuffer,这里有一篇关于Java中的Buffer的文章(http://zcdxzsz.iteye.com/blog/310917),ByteBuffer的存在相当于是所有其他基本Buffer的综合,然后在Buffer中以Byte的形式进行操作。然后,Comparator<ByteBuffer>迫使所有的最终子类实现一个方法,作为排序的依据:

    public int compare(ByteBuffer o1, ByteBuffer o2)

实现Comparator<ByteBuffer>的意义也就止于此。至于上面提到的5个Comparator,我的理解是他们没有独特的操作,连写一个子类的价值都没有,所以就只有5个变量而已 。

下一个让人关注的问题就是泛型AbstractType<T>是怎么工作的,自然,T的填入是根据需要封装的类型而决定的,具体来说,是下面几个:String,BigInteger,Long,ByteBuffer,UUID。填入的具体的T有什么作用呢?来看一看AbstractType<T>中是怎么定义的:

    public abstract T compose(ByteBuffer bytes);

    public abstract ByteBuffer decompose(T value);

    /** get a string representation of a particular type. */
    public abstract String toString(T t);

    /** get a string representation of the bytes suitable for log messages */
    public abstract String getString(ByteBuffer bytes);

    /** get a byte representation of the given string.
     *  defaults to unsupportedoperation so people deploying custom Types can update at their leisure. */
    public ByteBuffer fromString(String source) throws MarshalException
    {
        throw new UnsupportedOperationException();
    }

    /* validate that the byte array is a valid sequence for the type we are supposed to be comparing */
    public abstract void validate(ByteBuffer bytes) throws MarshalException;

    /** @deprecated; use reverseComparator field instead */
    public Comparator<ByteBuffer> getReverseComparator()
    {
        return reverseComparator;
    }

    /* convenience method */
    public String getString(Collection<ByteBuffer> names)
    {
        StringBuilder builder = new StringBuilder();
        for (ByteBuffer name : names)
        {
            builder.append(getString(name)).append(",");
        }
        return builder.toString();
    }

    /* convenience method */
    public String getColumnsString(Collection<IColumn> columns)
    {
        StringBuilder builder = new StringBuilder();
        for (IColumn column : columns)
        {
            builder.append(column.getString(this)).append(",");
        }
        return builder.toString();
    }

    public boolean isCommutative()
    {
        return false;
    }

    /** returns the class this AbstractType represents. */
    public abstract Class<T> getType();

    //
    // JDBC metadata
    //

    public abstract boolean isSigned();
    public abstract boolean isCaseSensitive();
    public abstract boolean isCurrency();
    public abstract int getPrecision(T obj);
    public abstract int getScale(T obj);
    public abstract int getJdbcType();
    public abstract boolean needsQuotes();

可见,关于T的重要的是compose()和decompose()方法,涉及到如何被封装的类型如何与ByteBuffer之间进行转换,其他涉及到T的方法,toString(),getType(),getPrecision(),getScale(),都是不甚重要的,甚至有些子类都不会使用getPrecision()和getScale(),估计是历史遗留问题。

于是,看完这些之后,就明白,marshal包中的类的作用就是

  1. 提供ByteBuffer中的compare()方法,使其能在排序集中使用;
  2. 提供被封装的类型与ByteBuffer之间的转换;
  3. 封装原始类型到具体的自定义类型,提供各类型的特有方法。

Wildcard Type学习笔记

有三种关于wildcard的用法

  • ? extends Employee
  • ? super Manager
  • ?

一、测试背景

有一个泛型Pair<T>

package genericProgramming;

public class Pair<T> {
	public Pair(){
		first = null;
		second = null;
	}

	public Pair(T first, T second){
		this.first=first;
		this.second=second;
	}

	public T getFirst() {
		return first;
	}

	public void setFirst(T first) {
		this.first = first;
	}

	public T getSecond() {
		return second;
	}

	public void setSecond(T second) {
		this.second = second;
	}

	private T first;
	private T second;
}

两个有继承关系的类Employee和Manager,这里Manager extends Employee,Employee extends Object

package genericProgramming;

import java.util.*;

public class Employee {
	private String name;
	private double salary;
	private Date hireDay;

	public Employee(String n, double s, int year, int month, int day){
		name =n;
		salary =s;
		GregorianCalendar calendar = new GregorianCalendar(year, month-1, day);
		hireDay= calendar.getTime();
	}

	public String getName() {
		return name;
	}

	public double getSalary() {
		return salary;
	}

	public Date getHireDay() {
		return hireDay;
	}

	public void raiseSalary(double byPercent){
		double raise=salary * byPercent/100;
		salary+=raise;
	}
}
package genericProgramming;

public class Manager extends Employee {
	private double bonus;

	public Manager(String n, double s, int year, int month, int day){
		super(n,s,year,month,day);
		bonus=0;
	}

	public double getSalary(){
		double baseSalary=super.getSalary();
		return baseSalary+bonus;
	}

	public void setBonus(double b){
		bonus=b;
	}

	public double getBonus(){
		return bonus;
	}
}

二、关于三种wildcard的用法

关于 ? extends Employee :

  • 当如此定义时,调用时填入的必须是Employee本身或其子类,例如可以填入Employee,或者Manager,但是不能是Object。如下图所示:
  • Pair<? extends Employee>是Pair<Employee>和Pair<Manager>的super class
  • ? extends Employee getFirst() //合法
    void setFirst(? extends Employee) //不合法
    关于以上两行,更直观的是看使用过程中到底发生了什么

    先看看在get方法中发生了什么,如同 ? extends Employee getFirst() 定义的方法,如上图所示,显然是合法的,问题就是到底返回值究竟能赋给什么类型,实验的三种类型,Manager,Employee,Object,只有后两个能成功,所以,能接受 ? extends xxx 类型返回值的,是xxx以及其所有super class

    然后看看set方法中发生了什么,如上图所示,当尝试用 ? extends Employee 作为参数的类型时,编译器所认为的参数类型却是null,编译器无法确定究竟应该用什么类型去匹配参数,虽然看上去应该填入一个Employee的sub class。当然填入null是合法的,但是程序却无法正确进行下去了。
    实际上,从实际使用中考虑,这也是合理的。? extends Employee 是替代泛型<T>中的T的,显然在定义中,如果有两个变量a和b,他们都是T类型,那么a和b是可以相互赋值的,比如a=b,但是当T由? extends Employee替换之后,调用时,用来替换? extends Employee的可能是Employee的sub class A,或者B,将一个class A的变量和一个class B的变量相互赋值,显然是有问题的。
    所以,对于 ? extends xxx ,get方法可用,set方法不可用

关于 ? super Manager :

  • 当如此定义时,调用时填入的必须是Manager本身或其超类,例如可以填入Manager,Employee,Object,具体如下图:

  • Pair<? super Manager>是Pair<Manager>,Pair<Employee>和Pair<Object>的super class
  • ? super Manager getFirst()
    void setFirst(? super Manager)
    与上面的相反,? super Manager的缺陷是在get方法上,如下图:

    get方法虽然可以用,但是返回的类型是Object,之所以还能用的原因是Manager的super class是可数的,有终点的,于是那个终点Object就作为了返回类型,这样做是安全的。相比之下,满足? extends Employee的class数量是不确定的。
    关于set方法,如下图:

    这里很有趣,我曾以为能够填入的参数类型是Manager本身或其super class,然而实际上恰巧相反,对于set方法参数中的 ? super xxx,应该填入的类型是xxx本身或其sub class。仔细想一下,从起始的泛型到最后的调用,顺序应该是这样的:Pair<T>,Pair<? super Manager>,Pair<Manager>,Pair<Employee>或Pair<Object>,最后是setFirst(val),可见,val的类型必须同时满足使用? super Manager可能产生的所有情况,在这里,也就是Manager,Employee和Object,所以val的类型只能是Manager或其子类了

关于 ? :

  • Pair<?>似乎与Pair<T>很相像,但是Pair<?>继承了上述的诸多限制,而且是最坏的限制,比如get方法只能返回Object,set方法非法

三、泛型(generic)和通配符(wildcard)

我的理解是,泛型就像c里面的宏,用法像是直接无条件地将类型参数用给定的类名在源代码中替换掉,通配符的使用则是有条件地将类型参数用给定的类名在源代码中替换掉,然后再产生了一系列的使用限制(如上所说)

Test Windows Live Writer

Hello world!

The default editor makes all the source code I pasted from eclipse lose highlighting. It appears to me that it’s the time to use either a plugin or a offline editor while the wordpress.com does not support plugin. So Windows Live Writer becomes my choice.

Although it is still not possible to copy highlighting source code from Eclipse to WLW, it is possible to do so from Word to WLW. So a blank Word will have to always be used when there is source code to be pasted. Anyway, this is a solution.

Another way to do so is to use plugin in WLW. It may take me some time to search for a proper one, and it’s worth it.

At last, it took me 5-6 hours to config wallproxy because of GFW and SSL. Really annoying!

设计模式之装饰器模式(Decorator Pattern)

在看Cassandra源码的过程中,遇到了使用Decorator Pattern的代码,相关介绍讲的比较完整的地方是维基百科(http://en.wikipedia.org/wiki/Decorator_pattern)。下面,做简单介绍,不过,最直观的,还是看他的例子。

一、介绍

Decorator Pattern用来在run-time中扩展一个对象的功能,并且不会影响其他同class的实例,这个class仅在design time中提供基本功能。话听起来很拗口,其实呢,意思就是,当你需要一个对象拥有新的功能时,你不需要更改这个对象的源码,而是把新加的功能另写在一个类中,就像装饰一样,当然,有很多装饰,于是,在真正调用使用这个对象的代码中,就可以随心意选择需要的功能包在这个对象之外,而不需要对这些额外功能的每种组合都新写一个类。

下面这段话引用于此(http://www.dotblogs.com.tw/atowngit/archive/2010/03/04/13880.aspx),“

(原文出處 大話設計模式 )
裝飾者模式 : 動態的給物件加入一些額外的職責。
書上舉了個類似洋娃娃的例子,洋娃娃要穿甚麼衣服端看它的主人心情而定。
因為我們可以自由的任意搭配(今天嘻哈風、明天居家風),所以我們不應該將裝飾的功能寫死在類別裡。
一樣依據開放-封閉原則,將裝飾的寫成一個類別,用來擴充物件的功能。
是的,我表達能力不好。可參考

二、如何设计一个decorator pattern

简而言之,需要设计一个新的decorator class,包装在原始 class外

包装的方法需要遵循以下步骤:

  1. 首先建一个需要被装饰的类(称为Component)的子类,作为原始的装饰器(Decorator);
  2. 在装饰器类中,有一个属性,是Component的指针;
  3. 需要传递一个Component给Decorator的构造器,使得Decorator能够初始化那个Component的指针,于是,被装饰的对象总是作为参数放在了括号里,装饰的方法在外面,这使得整个代码看起来更像是一种包装;
  4. 在Decorator class中,将所有Component中的方法重定向到那个指针中去,因为这些方法暂时是不需要改动的,例如,decorated.xxxx();
  5. 最后就是具体的Decorator class,这些类需要继承那个原始的装饰器,然后,根据需要override那些需要改动的方法,或者增加新的方法。

这种模式让多个装饰器如同栈一样一个叠在另一个之上,每个装饰器都增加新的功能,方式或者是override已有的方法,或者是增加新的方法。

三、与子类的不同

装饰器模式和子类方式是两种相似然而不尽相同的方案。子类方式在编译时(compile time)增加新的行为,并且这种更改会影响到所有的实例;装饰器则是在运行时增加新的行为(run-time),并且只针对单个对象

四、例子

wiki上的例子很好,直接复制了(仅仅Java部分):

First Example (window/scrolling scenario)

The following Java example illustrates the use of decorators using the window/scrolling scenario.

// the Window interface
interface Window {
    public void draw(); // draws the Window
    public String getDescription(); // returns a description of the Window
}

// implementation of a simple Window without any scrollbars
class SimpleWindow implements Window {
    public void draw() {
        // draw window
    }

    public String getDescription() {
        return "simple window";
    }
}

The following classes contain the decorators for all Window classes, including the decorator classes themselves.

// abstract decorator class - note that it implements Window
abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated

    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    public void draw() {
        decoratedWindow.draw();
    }
}

// the first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }

    public void draw() {
        decoratedWindow.draw();
        drawVerticalScrollBar();
    }

    private void drawVerticalScrollBar() {
        // draw the vertical scrollbar
    }

    public String getDescription() {
        return decoratedWindow.getDescription() + ", including vertical scrollbars";
    }
}

// the second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }

    public void draw() {
        decoratedWindow.draw();
        drawHorizontalScrollBar();
    }

    private void drawHorizontalScrollBar() {
        // draw the horizontal scrollbar
    }

    public String getDescription() {
        return decoratedWindow.getDescription() + ", including horizontal scrollbars";
    }
}

Here’s a test program that creates a Window instance which is fully decorated (i.e., with vertical and horizontal scrollbars), and prints its description:

public class DecoratedWindowTest {
    public static void main(String[] args) {
        // create a decorated Window with horizontal and vertical scrollbars
        Window decoratedWindow = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator(new SimpleWindow()));

        // print the Window's description
        System.out.println(decoratedWindow.getDescription());
    }
}

The output of this program is “simple window, including vertical scrollbars, including horizontal scrollbars”. Notice how the getDescription method of the two decorators first retrieve the decorated Window‘s description and decorates it with a suffix.

Second Example (coffee making scenario)

The next Java example illustrates the use of decorators using coffee making scenario. In this example, the scenario only includes cost and ingredients.

// The Coffee Interface defines the functionality of Coffee implemented by decorator
public interface Coffee {
    public double getCost(); // returns the cost of the coffee
    public String getIngredients(); // returns the ingredients of the coffee
}

// implementation of a simple coffee without any extra ingredients
public class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }

    public String getIngredients() {
        return "Coffee";
    }
}

The following classes contain the decorators for all Coffee classes, including the decorator classes themselves..

// abstract decorator class - note that it implements Coffee interface
abstract public class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;
    protected String ingredientSeparator = ", ";

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    public double getCost() { // implementing methods of the interface
        return decoratedCoffee.getCost();
    }

    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

// Decorator Milk that mixes milk with coffee
// note it extends CoffeeDecorator
public class Milk extends CoffeeDecorator {
    public Milk(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() { // overriding methods defined in the abstract superclass
        return super.getCost() + 0.5;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Milk";
    }
}

// Decorator Whip that mixes whip with coffee
// note it extends CoffeeDecorator
public class Whip extends CoffeeDecorator {
    public Whip(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() {
        return super.getCost() + 0.7;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Whip";
    }
}

// Decorator Sprinkles that mixes sprinkles with coffee
// note it extends CoffeeDecorator
public class Sprinkles extends CoffeeDecorator {
    public Sprinkles(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() {
        return super.getCost() + 0.2;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Sprinkles";
    }
}

Here’s a test program that creates a Coffee instance which is fully decorated (i.e., with milk, whip, sprinkles), and calculate cost of coffee and prints its ingredients:

public class Main
{
    public static void main(String[] args)
    {
        Coffee c = new SimpleCoffee();
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Milk(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Whip(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        // Note that you can also stack more than one decorator of the same type
        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
    }
}

The output of this program is given below:

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
Cost: 2.4; Ingredients: Coffee, Milk, Sprinkles, Whip
Cost: 2.6; Ingredients: Coffee, Milk, Sprinkles, Whip, Sprinkles