Effective Java - 慎用tagged class

作者的原标题是<Prefer class hierarchies to tagged classes>,即用类层次优于tagged class。


我不知道有没有tagged class这么一说,其实作者指的tagged class的是一个类描述了多种抽象,可以根据某个field决定不同的实例。
下面是书中例子,使用shape和部分表示长度的field构成形状并计算面积,脑补一下什么是tagged class:

class Figure {

    enum Shape {

        RECTANGLE, CIRCLE

    };



    // Tag field - the shape of this figure

    final Shape shape;



    // These fields are used only if shape is RECTANGLE

    double length;

    double width;



    // This field is used only if shape is CIRCLE

    double radius;



    // Constructor for circle

    Figure(double radius) {

        shape = Shape.CIRCLE;

        this.radius = radius;

    }



    // Constructor for rectangle

    Figure(double length, double width) {

        shape = Shape.RECTANGLE;

        this.length = length;

        this.width = width;

    }



    double area() {

        switch (shape) {

        case RECTANGLE:

            return length * width;

        case CIRCLE:

            return Math.PI * (radius * radius);

        default:

            throw new AssertionError();

        }

    }

}


不难看出这个类想传达什么信息,也不难看出这样的方式有很多缺陷。
虽然能看懂是什么意思,但由于各种实现挤在一个类中,其可读性并不好。
不同的实现放到一个类里描述,即会根据实现的不同而使用不同的field,即,field无法声明为final。(难道要在构造器里处理不用的field?)
虽然微不足道,内存确实存在毫无意义的占用。
不够OO。


虽然上一篇把类层次说得一无是处,其实类层次就是用来解决这一问题的,而上面的tagged class是用非OO的方式模仿类层次。
将tagged class转为类层次,首先要将tagged class里的行为抽象出来,并为其提供抽象类。
以上面的Figure为例,我们之需要一个方法——area。
接下来需要为每一个tag定义具体子类,即例子中的circle和rectangle。
然后为子类提供相应的field,即circle中的radius和rectangle的width、length。
最后为子类提供抽象方法的相应实现。
其实都不用这样去说明转换步骤,因为OO本身就是很自然的东西。


转换结果如下:

abstract class Figure {

    abstract double area();

}





class Circle extends Figure {

    final double radius;



    Circle(double radius) {

        this.radius = radius;

    }



    double area() {

        return Math.PI * (radius * radius);

    }

}



class Rectangle extends Figure {

    final double length;

    final double width;



    Rectangle(double length, double width) {

        this.length = length;

        this.width = width;

    }



    double area() {

        return length * width;

    }

}



class Square extends Rectangle {

    Square(double side) {

        super(side, side);

    }

}


这样做的好处显而易见,
代码简单清晰,没有样板代码;
类型相互独立,不会受到无关field的影响,field可以声明为final。
子类行可以独立进行扩展,互不干扰。


回到最初的tagged class,它真的就一无是处?
如果使用这个类,我只需要在调用构造器时使用相应的参数就可以得到想要的实例。
就像策略模式那样。
当然,tagged class也许可能算是策略模式(传递的应该是某个行为的特征,而不是实例特征),但策略模式在Java中并不是这样使用。


通常,一个策略是通过调用者通过传递函数来指定特定的行为的。
但Java是没有函数指针的,所以我们用对象引用实现策略模式,即调用该对象的方法。
对于这种仅仅作为一个方法的"载体",即实例等同与方法指针的对象,作者将其称为函数对象(function object)。


举个例子,比如我们有这样的一个具体策略(concrete strategy):

class StringLengthComparator {

    private StringLengthComparator() {

    }



    public static final StringLengthComparator INSTANCE = new StringLengthComparator();



    public int compare(String s1, String s2) {

        return s1.length() - s2.length();

    }

}


具体策略的引用可以说是一个函数指针。
对具体策略再抽象一层即成为一个策略接口(strategy interface)。
对于上面的例子,java.util中正好有Comparator:

public interface Comparable<T> {



    int compare(T o1, T o2);



    boolean equals(Object obj);

}


于是,我们使用的时候可能会用匿名类传递一个具体策略:

Arrays.sort(stringArray, new Comparator<String>() {

    public int compare(String s1, String s2) {

        return s1.length() - s2.length();

    }

});


用代码描述似乎是那么回事,我只是在我想使用的地方传递了一个具体策略。
缺点很明显——每次调用的时候又需要创建一个实例。
但又能怎么做? 把每个可能用到的具体策略在某个Host类里实现策略接口后都做成field并用final声明?
感觉很傻,但确实可以考虑,因为这样做还有其他好处。
比如,相比匿名类,具体策略可以有更直观的命名,而且具体策略可以实现其他接口。


代码如下:

// Exporting a concrete strategy

class Host {

    private static class StrLenCmp

        implements Comparator<String>, Serializable {

            public int compare(String s1, String s2) {

                return s1.length() - s2.length();

            }

    }



    // Returned comparator is serializable

    public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();



    ... // Bulk of class omitted

}

你可能感兴趣的:(Effective Java)