作者的原标题是<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. 一次MySQL线上慢查询分析及索引使用

    本文由作者郑智辉授权网易云社区发布. 0.前言 本文通过分析线上MySQL慢查询日志,定位出现问题的SQL,进行业务场景分析,结合索引的相关使用进行数据库优化.在两次处理问题过程中,进行的思考. 1. ...

  2. java -io字符流FileWrite操作演示

    FileWriter字符输出流演示: /* * FiileWriter 字符流的操作 * FileWriter 的构造方法 可传递 File类型 还可以传递String类型 * * 方法 : * wr ...

  3. Java多线程原理及Thread类的使用

    一.进程与线程的区别 1.进程是应用程序在内存总分配的空间.(正在运行中的程序) 2.线程是进程中负责程序执行的执行单元.执行路径. 3.一个进程中至少有一个线程在负责进程的运行. 4.一个进程中有多 ...

  4. ElasticSearch NEST搜索

    var client = ElasticsearchHelper.GetElasticClient("order");QueryContainer termQuery = new ...

  5. jmeter服务器监控插件指标简单说明

    以下是下载了服务器监控插件的各个组件的功能介绍,有助于以后jmeter的性能测试 1.jp@gc - Actiive Threads Over Time:不同时间的活动用户数量展示(图表) 当前的时间 ...

  6. 考试题 T2

    题意分析 首先 要求起点终点不连通 再结合数据范围 就是最小割了 首先我们可以建一个图出来 如果\(x\)可以到\(y\)的话 那么我们就从\(x\)向\(y\)连一条代价为\(h[x]-h[y]+1 ...

  7. Calibre 3.4版中,为epub书籍嵌入中文字体

    1. 先把原版书籍epub文件添加到Calibre书库: 2. 书名上右键,选则 转换书籍 -> 逐个转换: 3. 在 界面外观 -> 字体 中,选择嵌入字体,在列表中选择中文字体,并勾选 ...

  8. webstorm缩进配置

    https://blog.csdn.net/m0_37604745/article/details/80076770 设置代码缩进1. 依次打开files —- settings —- Editor ...

  9. 何为Web App,何为Hybird App

    这些概念听起来很火,当下也很流行,真正理解起来却并非易事.如果让我来全面的解释Web App和Hybird App,我觉得还有些困难. 这篇文章只是我深入了解移动领域开发过程中的不断整理和总结,其中涉 ...

  10. JS框架设计之对象扩展一种子模块

    对象扩展 说完了,对象的创建(框架的命名空间的创建)以及如何解决多库之间的命名空间冲突问题之后,接下来,就是要扩展我们的对象,来对框架进行扩展,我们需要一种新功能,将新添加的功能整合到我们定义的对象中 ...