首页 > 学院 > 开发设计 > 正文

Effective Java

2019-11-14 22:43:20
字体:
来源:转载
供稿:网友
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 strategyclass 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}

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表