作者的原标题是<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
}

Java - 慎用tagged class的更多相关文章

  1. java——慎用可变参数列表

    说起可变参数,我们先看下面代码段,对它有个直观的认识,下方的红字明确地解释了可变参数的意思: public class VarargsDemo{ static int sum(int... args) ...

  2. 面向GC的Java编程

    转自http://hellojava.info/?p=341 HelloJava微信公众账号网站 面向GC的Java编程 Leave a reply 这是内部一个同事(沐剑)写的文章,国外有一家专门做 ...

  3. 转 velocity 模板使用总结

    Velocity是一个基于java的模板引擎.它允许任何人仅仅简单的使用模板语言来引用由java代码定义的对象. 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一 ...

  4. RocketMq消息队列使用

    最近在看消息队列框架 ,alibaba的RocketMQ单机支持1万以上的持久化队列,支持诸多特性, 目前RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,bin ...

  5. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

  6. [改善Java代码]慎用动态编译

    建议17: 慎用动态编译 //=========这篇博文暂时理解不透......... 动态编译一直是Java的梦想,从Java 6版本它开始支持动态编译了,可以在运行期直接编译.java文件,执行. ...

  7. 慎用Java递归调用

    总结:慎用Java递归调用,测试时可以尝试该方法,否则尽量不要使用递归!递归过多调用时,最好改为for或者whlie来代替. 在java语言中,使用递归调用时,如果过多的调用容易造成java.lang ...

  8. 为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象

    图片若无法显示,可至掘金查看https://juejin.im/post/5d425230f265da039519d248 前言 在阿里Java开发手册中,有这么一条建议:慎用 Object 的 cl ...

  9. Effective Java 20 Prefer class hierarchies to tagged classes

    Disadvantage of tagged classes 1. Verbose (each instance has unnecessary irrelevant fields). 2. Erro ...

随机推荐

  1. HBase原理–所有Region切分的细节都在这里了

    本文由  网易云发布.   作者:范欣欣(本篇文章仅限内部分享,如需转载,请联系网易获取授权.)   Region自动切分是HBase能够拥有良好扩张性的最重要因素之一,也必然是所有分布式系统追求无限 ...

  2. ClamAV学习【7】——病毒库文件格式学习

    搜查到一份详细的ClamAV病毒文件格式资料(http://download.csdn.net/detail/betabin/4215909),英文版,国内这资料不多的感觉. 重点看了下有关PE的病毒 ...

  3. Bootstrap框架常用总结

    Bootstrap框架常用标签:    标题标签:<h1>-<h6>        bootstrap中也设置的相同的样式  - 若要使用 必须使用空标签来定义 比如<s ...

  4. 查域名对应的ip的命令:Nslookup

    使用Nslookup命令可查询域名对应的ip,如下所示

  5. LOJ#6360. 复燃「恋之埋火」(最小圆覆盖+高斯消元)

    题面 传送门 题解 不难发现最小圆覆盖的随机增量法复杂度还是正确的 所以现在唯一的问题就是给定若干个点如何求一个\(m\)维的圆 其实就是这一题 //minamoto #include<bits ...

  6. robot framework踩坑记录

    一.报错:FAIL : 'Return' is a reserved keyword. 给Retrun加上中括号即可 二.报错:True != true 三.报错 /Library/Python/2. ...

  7. php性能优化二(PHP配置php.ini)

    PHP优化对于PHP的优化主要是对php.ini中的相关主要参数进行合理调整和设置,以下我们就来看看php.ini中的一些对性能影响较大的参数应该如何设置. # vi /etc/PHP.ini (1) ...

  8. [javascript]——将变量转化为字符串

    这是一个非常常用,但是我自己却经常忘记的一个方法: var item = 'textssdf'; console.log("'"+item+"'") > ...

  9. django model改变后,同步数据库

    在使用django进行开发时,往往需要根据不同的需求对model进行更改.而这时候,python manage.py syncdb就不好使了. 目前有个很好的工具,是south,这个是专门用来更改mo ...

  10. P1975 [国家集训队]排队

    题目链接 题意分析 我们考虑 交换两个数\([le,ri]\)的贡献 减少的逆序对数\([le,ri]\)中小于\(num[le]\)以及大于\(num[ri]\)的数 增加的\([le,ri]\)中 ...