Hycz's Blog

Life is a game. Why so serious?

Tag Archives: Design Pattern

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. 封装原始类型到具体的自定义类型,提供各类型的特有方法。
Advertisements

设计模式之装饰器模式(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