作者的原标题是<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. python list 嵌套 dict 按照字典中的单个key进行单级排序 或 按照多个键进行多级排序

    student = [{"no": 1,"score": 90},{"no": 2,"score": 90},{&quo ...

  2. [转] Cisco路由器DNS配置

    禁用Web服务 Cisco路由器还在缺省情况下启用了Web服务,它是一个安全风险.如果你不打算使用它,最好将它关闭.举例如下: Router(config)# no ip http server 配置 ...

  3. 谷歌将对欧洲 Android 设备制造商收取其应用服务费用

    简评:欧盟就谷歌违反了<反垄断法>开出天价罚单,导致谷歌运营生态被打破,为了配合这一裁决,谷歌将调整其运营模式.欧盟似乎赢了,而这最后买单的却是消费者. 今年七月份,谷歌要求 Androi ...

  4. POJ3460 Booksort(IDA*)

    POJ3460 Booksort 题意:给定一个长度为n的序列,每次可以取出其中的一段数,插入任意一个位置,问最少需要几次操作才能使整个序列变为1~n 思路:IDA*+迭代加深搜索 小技巧:将一段数插 ...

  5. 核心API的使用(给定一个字符串,统计每个字符出现的次数)

    /** * 给定一个字符串,统计每个字符出现的次数. 如:abdaewrwqask435a1aasd */public class ReplaceString { static int length; ...

  6. ubuntu apt update时W: GPG error http://ppa.launchpad.net lucid Release没有公钥无法验证NO_PUBKEY签名问题解决

    在安装更新时,即在运行命令行sudo apt-get update 或者运行更新管理器的时候,出现W: GPG 错误: W: GPG error: http://ppa.launchpad.net/o ...

  7. 架构师养成记--35.redis集群搭建

    前记:redis哨兵经验之谈.哨兵做主从切换可能要花费一两秒,这一两秒可能会丢失很多数据.解决方法之一是在java代码中做控制,try catch 到 链接断开的异常就sleep 一两秒钟再conti ...

  8. Django中的Cookie--实现登录

    Django中的Cookie--实现登录 Django Cookie  Cookie Cookie 是什么 保存在浏览器端的键值对,让服务器提取有用的信息. 为什么要有 Cookie 因为HTTP请求 ...

  9. wireshark 抓包

    Wireshark(前称Ethereal)是一个网络数据包分析软件.网络数据包分析软件的功能是截取网络数据包,并尽可能显示出最为详细的网络数据包数据.Wireshark使用WinPCAP作为接口,直接 ...

  10. 微信 oauth 登录 ,回调两次,一个坑,记录一下。

    在做微信某个功能的时候,大致需求是:静默授权,得到openId ,然后拿着openId调用接口,判断是否关注.如果是关注的,则发放礼券.每个我网站的会员只会发放一次礼券.如果第二次则会提示已领取过礼券 ...