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
}
Java - 慎用tagged class的更多相关文章
- java——慎用可变参数列表
说起可变参数,我们先看下面代码段,对它有个直观的认识,下方的红字明确地解释了可变参数的意思: public class VarargsDemo{ static int sum(int... args) ...
- 面向GC的Java编程
转自http://hellojava.info/?p=341 HelloJava微信公众账号网站 面向GC的Java编程 Leave a reply 这是内部一个同事(沐剑)写的文章,国外有一家专门做 ...
- 转 velocity 模板使用总结
Velocity是一个基于java的模板引擎.它允许任何人仅仅简单的使用模板语言来引用由java代码定义的对象. 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一 ...
- RocketMq消息队列使用
最近在看消息队列框架 ,alibaba的RocketMQ单机支持1万以上的持久化队列,支持诸多特性, 目前RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,bin ...
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- [改善Java代码]慎用动态编译
建议17: 慎用动态编译 //=========这篇博文暂时理解不透......... 动态编译一直是Java的梦想,从Java 6版本它开始支持动态编译了,可以在运行期直接编译.java文件,执行. ...
- 慎用Java递归调用
总结:慎用Java递归调用,测试时可以尝试该方法,否则尽量不要使用递归!递归过多调用时,最好改为for或者whlie来代替. 在java语言中,使用递归调用时,如果过多的调用容易造成java.lang ...
- 为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象
图片若无法显示,可至掘金查看https://juejin.im/post/5d425230f265da039519d248 前言 在阿里Java开发手册中,有这么一条建议:慎用 Object 的 cl ...
- Effective Java 20 Prefer class hierarchies to tagged classes
Disadvantage of tagged classes 1. Verbose (each instance has unnecessary irrelevant fields). 2. Erro ...
随机推荐
- css--一些基本属性
关于css各标签的属性: w3cschool一应俱全 设置固定的图片: body { background-image: url(bgimage.gif); background-attachment ...
- JAVA特性一:封装
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式. 封装概念详解:封装是把过程和数据包围起来,对数据的访问只能通过已定义的接口. 面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完 ...
- ClamAV学习【6】—— cli_load函数浏览
(老爸回家,就放开心和他到处走,累……趁其和老妈聊天之际,再继续看代码) 参数选项,加载病毒都浏览得七七八八了,这里就贴个简单的函数注释吧.哈哈. 代码注释如下: int cli_load(const ...
- JZOJ6096 森林
题目传送门 Description 我们定义对一棵树做一次变换的含义为:当以 1 号节点为根时,交换两个互相不为祖先的点的子树: 一棵树的权值为对它进行至多一次变换能得到的最大直径长度: 初始时 ...
- Python dict转化为string方法
dict-->string: str() string-->dict eval()(这个只是网上看的,没实测)
- 给对象和函数添加method方法
蝴蝶书中有一个method方法,用来给函数定义方法.看了之后,想着能不能给对象也定义方法呢?. 下面的代码可以实现给函数定义方法: //Function method Function.prototy ...
- 利用python 学习数据分析 (学习一)
内容学习自: Python for Data Analysis, 2nd Edition 就是这本 纯英文学的很累,对不对取决于百度翻译了 前情提要: 各种方法贴: https://w ...
- P4090 [USACO17DEC]Greedy Gift Takers
题目链接 题意分析 首先 如果当前序列中一头奶牛拿不到礼物的话 那么他后面的奶牛也拿不到礼物 所以我们可以二分 由于可以操作无限次 所以我们对于当前\([1,mid)\)的奶牛按照\(c\)值排序之后 ...
- scrapyd的安装
.安装 pip3 install scrapyd 二.配置 安装完毕之后,需要新建一个配置文件/etc/scrapyd/scrapyd.conf,Scrapyd在运行的时候会读取此配置文件. 在Scr ...
- 多线程编程(一)-Semaphore(信号量)的使用
Semaphore的介绍 单词Semaphore的中文含义就是信号.信号系统的意思,此类的主要作用就是限制线程并发的数量. 举个例子,一个屋子里有10个人,但只有一个窄门可以出去,这个窄门一次最多只能 ...