风一更--程序设计--《实现模式》

目标:践行大师编程思想  2021-03-27

《实现模式》

1. 框架开发:

 Enum

 Annotation  平台框架程序员必备 自定义注解, Java 预定义的注解类型,IDE 或 静态分析工具 提供的任何注解,可以提升由这些工具所提供的诊断信息的质量. 注意标准化.


框架的开发中,使用 Enum 和 Annotation ,提供灵活的扩展功能接口

1. enum

String 枚举,会导致初级用户直接把字符串硬编码到客户端代码中,而不是使用对应的常量字段名。

一旦这样的硬编码的字符串常量包含书写错误,在编译时不会被检测到,但在运行时会报错,而且会导致性能问题,因为它依赖于字符串的比较。

Java 枚举的本质是 int 值.

枚举类型保证了编译时的类型安全。可以增加或重排枚举类型中的常量,无需重新编译它的客户端代码。

提供了所有Object方法的高级实现,实现了Comparable 和 Serializable 接口,并针对枚举类型的可任意改变性,设计了序列化方式.


1. 向枚举类型中添加方法或者域。 将数据与它的常量关联起来。

枚举可先作为 枚举常量的一个简单集合,随着时间的推移,再演变成为全功能的抽象.


Enum 常量关联了不同的数据,有时需要将不同的 行为与每个常量关联起来。

例如编写一个枚举类型,来表示计算器的四大基本操作(加减乘除)

提供一个方法来执行每个常量所表示的算术运算,通过启用枚举的值来实现.

解决办法:

在枚举类型中声明一个抽象的 apply 方法,并在特定于常量的类主体中,constant-specific class body

用具体的方法覆盖每个常量的抽象 apply 方法。 特定于常量的实体方法实现 constant-specific method implementation

特定于常量的方法实现有一个美中不足的地方:使得再枚举常量中共享代码变得更加困难.

枚举中的 switch 语句不是在枚举中实现特定于常量的行为的一种很好的选择,

枚举中的 switch 语句适合于给外部的枚举类型增加特定于常量的行为.

一般来说,枚举通常在性能上与int常量相当,与int 常量相比,枚举有个小小的性能缺点,即装载和初始化枚举时会需要空间和时间成本.

实践中几乎不是问题.

使用枚举的场景:

每当需要一组固定常量,且在编译时就知道其成员的时候,应该使用枚举. 例如

1. 天然的枚举类型: 行星,一周的天数以及棋子的数目。

2. 编译时就知道其所有可能值得其他集合,如菜单得选项,操作代码,命令行标记。

枚举类型中得常量值并不一定要始终保持不变。

许多枚举受益于属性与每个常量关联以及其行为受该属性影响得方法,

极少数得枚举收益于将多种行为与单个方法关联。

在这种相对少见得情况下,特定于常量得方法要优先于启用自有值得枚举,如果多个(但非所有)枚举常量同时共享相同得行为,

则要考虑策略枚举.


永远不要根据枚举得序数导出与它关联得值,而是要将它保存在一个实例域中.

public enum Ensemble{

  SOLO(1), DUET(2),TRIO(3);

  private final int numberOfMusicians;

  Ensemble(int size) { this.numberOfMusicians = size ; }

  public int numberOfMusicians() { return numberOfMusicians;}

}

oridinal 方法 用于像 EnumSet 和 EnumMap 这种基于枚举得通用数据结构的.


不要用序数来索引数组,而要使用EnumMap, 如果所表示的关系是多维的,那就使用 EnumMap<... , EnumMap<...>>  p137   (Effective Java)


用接口模拟可扩展的枚举:

有时要尽可能地让 API 的用户提供它们自己的操作,可以有效地扩展 API 所提供的操作集.

由于枚举类型可以通过给操作码类型和(属于接口的标准实现的)枚举定义接口来实现任意接口,基本的想法就是利用这一事实.

客户扩展代码:

 无法将一个枚举类型集成到另一个枚举类型。

如果实现代码不依赖与任何状态. 可以将缺省实现放在接口中.

复制代码或如果共享功能比较多, 将它封装在一个辅助类或静态辅助方法中,避免代码复制.

java.nio.file.LinkOption 枚举类型,同时实现了 CopyOption 和 OpenOption 接口.


使用注解

注解是那些可以插入到源代码中使用其他工具可以对其进行处理的标签.

这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件.

注解不会改变程序的编译方式,Java 编译器都生成相同的虚拟机指令.

需要选择一个处理工具,想你的处理工具可以理解的代码中插入注解,然后 运用该处理工具处理代码.

1. 附属文件的自动生成,部署描述符或者 bean  信息类。

2.测试,日志,事务语义等代码的自动生成.

注解参考资料:

JCommander  (http://jcommander.org )   和  picocli ( http://picocli.info ) 这些类库处理命令行参数

@Test 注解自身不会做任何事情,需要Junit4工具会调用所有标识位@Test方法。

除了方法外,还可以注解类,成员以及局部变量,这些注解可以存在于任何可以放置一个像 public 或者 static 这样的修饰符

的地方。

还可以注解 包,参数变量, 类型参数 和 类型用法.

@Tartget @Retention 元注解,注解了Test注解, 即将Test 注解标识成一个只能运行到方法上的注解,且当类文件载入到JVM时,仍然可以保留下来。

 注解也可以在源码级别上对它们进行处理。

源代码生成器将产生用于添加监听器的代码。

注解也可以在字节码级别上进行处理,字节码编辑器可以将对 addActionListener的调用注入框体构造器中。有类库支持. 


 

 

默认值并不是和注解存储在一起,而是动态计算出来的。

如果将某元素默认值更改,然后重新编译 该接口,那么注解的部分将使用这个新的默认值,即使在默认值修改之前

就已经编译过的类文件也是如此。

 


所有的注解接口都隐式地扩展自 java.lang.annotation.Annotation 接口.无法扩展该接口.

注解元素的类型为下列之一:

1. 基本类型(int, short, long, byte, char, double, float 或者 boolean)

2. String.

3. Class 具有一个可选的类型参数, 例如 Class<? extends MyClass>)

4. enum 类型。

5. annotation 类型

6. 由以上类型组成的数组。

Java.lang.reflect.AnnotedElement

1. boolean isAnnotationPresent( Class< ? extends Annotation>) annotationType  如果具有给定类型的注解,返回true.

2.<T extends Annotation> T getAnnotation(Class<T> annotationType)  获得给定类型的注解,否则返回 null;

3.<T extends Annotation> T[] getAnnotationByType ( Class<T>  annotationType) 获得某个可重复注解类型的所有注解,或者返回长度为0的数组.

4. Annotation[] getAnnotations()  ; 获得作用与该项的所有注解,包括继承而来的注解。如果没有出现任何注解,返回一个长度为0的数组.

5.Annotation[]  getDeclaredAnnotations()  获得为该项声明的所有注解,不包含继承而来的注解。若没有,返回长度为0 的数组。

注解各类声明

分为两类: 1. 声明 2. 类型用法声明注解

  • 包  包的注解不能在源码级别之外 存在    @GPL(version = "3") package  com.org.xxxx
  • 类 (包括 enum)
  • 接口 (包括注解接口)
  • 方法
  • 构造器
  • 实例域 (包括 enum 常量)
  • 局部变量
  • 参数变量
  • 类型参数

对于 类 和接口, 需要将注解放置在 class 和 interface 关键词的前面:

@Entity public class User{ ... }

对于变量,放置在类型的前面

@SuppressWarning("unchecked") List<User> users = ... ;

public User getUser(@Param("id")  String userId)

泛化类 或 方法中 的类型参数:

public class Cache<@Immutable V> { ... }

对局部变量的注解只能在源码级别上进行处理, 类文件并不描述局部变量。

所有的局部变量注解在编译完一个类的时候就会被遗弃掉.

如果注解被声明可重复的,则可以重复使用。


 

注解类型用法

声明注解提供了正在被声明的项的相关信息。

public User getUser( @NotNull String userId )  断言 userId 参数不为空

@NotNull 注解是 Checker Framework  https://checkerframework.org/

可在程序中包含断言,例如某个参数不为空,某个String 包含一个正则表达式.然后,静态分析工具将检查在

给定的源代码段中这些断言是否有效.

List<String>  都不为 Null  -> List<@Notnull String>  注解在类型参数之前


类型用法注解可以出现的位置  p378 

标准注解  p380

4 个元注解, 用于描述注解接口的行为属性。

其他三个是规则接口,可以用它们来注解你的源代码中的项.

元注解

@Target 限制该注解可以用在哪些项上

ANNOTATION_TYPE  注解类型声明        FIELD  成员域,包括 enum 常量

PACKAGE  包                  PARAMETER  方法或构造器参数

TYPE  类 ( 包括 enum ) 及接口 (包括注解类型)      LOCAL_VARIABLE   局部变量

METHOD  方法                   TYPE_PARAMETER   类型参数 

CONSTRUCTOR  构造器             TYPE_USE                   类型用法


@Retention 元注解 指定保留时间, 默认是 RetentionPolicy.CLASS

SOURCE          不包括在类文件中的注解

CLASS               包括在类文件中的注解, 但是虚拟机不需要将它们载入

RUNTIME          包括在类文件中的注解,并由虚拟机载入。通过反射API 可获得它们


@Inherited 元注解只能用于对类的注解,子类会自动具有相同的注解。

@Serializable 注解应该比没有任何方法的 Serializable 标记接口更适用.

一个类之所以可以被序列化,是因为存在着对它的成员域进行读写的运行期支持,而不是任何面向对象的设计原则.

一个继承注解 @Persistent 来指明一个类对象可以存储到数据库中,那么该持久类的子类 就会自动被注解为是持久性的.

@Inherited  @interface Persistant{ }

@Persistent class Employee{ .. }

class Manager  extends  Employee{ ... }

持久化机制去查找 存储在 数据库中的对象时, 就会同时探测到 Employee 对象以及 Manager 对象.


为了向后兼容,同种类型的注解:  重复注解

可重复注解的实现者现需要提供一个容器注解,将这些重复注解存储到一个数组中。

@TestCase 提供了两个或更多注解,就会自动被包装到一个 @TestCases 注解中.

 以上是运行时 注解


源码级注解 处理

自动处理源代码 以产生更多的  源代码, 配置文件, 脚本,或其他.

注解处理器, 扩展 AbstractProcessor ,实现 Processor 接口。

语言模型 API , 编译器产生一棵树. 节点实现饿了 javax.lang.model.element.Element 接口 及其 TypeElement,

VariableElement , ExecutableElement 等子接口类的实例。

处理流程 p384

适用注解来生成源码。


字节码工程

修改类文件,

在加载时修改字节码。


三种处理代码的技术: 编写脚本, 编译Java程序 和 处理注解. 


注解优先于命名模式  《Effective Java》 p141

一般使用 命名模式(naming pattern) 表明有些程序元素需要通过某种工具或者框架进行特殊处理.

缺点

1. 文字拼写会导致失败,且无提示。

2. 无法确保它们只用在相应的程序元素上.

3. 没有提供将参数值与程序元素关联起来的好方法. 具体的命名模式,将参数类型名称编码到目标方法中,但很不雅观,也很脆弱.


注解不会对目标类的语义, 只负责提供信息供相关的程序使用. 使它可以通过工具进行特殊处理.



 

 

提取参数 的值,并用它检测是否为正确的类型。

 

 

扩展: 不是特定的一个,而是集合

注解机制有一种工具,使得支持这种用法十分容易。将ExceptionTest注解的此参数类型改成 Class 对象的一个数组:


可重复注解:

加入可重复注解,提升了源代码的可读性,

记住在声明和处理可重复注解的代码中会有更多的样板代码,且容易出错。

在编写一个程序员需要给源文件添加信息的工具,就要定义一组适当的注解类型。不要用 命名模式了。


坚持使用 @Override 注解 覆盖超类型中一个方法的声明。

可防止一大类的非法错误。

例外: 如果你在编写一个没有标注为抽象的类,且确信它覆盖了超类的抽象方法,此时,无需Override注解在方法上.


标记是应用于任何程序元素而不是类或接口,就必须使用注解,只有类和接口可用来实现或者扩展接口。

如果标记只用于类和接口,要编写一个还是多个只接受这种标记的方法,入宫时,优先使用标记接口而非注解,可以用

接口作为相关方法的参数类型,提供编译时进行类型检查的好处。

如果永远不需要编写一个只接受带有标记的对象,或许最好用胡志杰。如果标记是广泛使用注解的框架一部分,显然注解。

2021-03-29 23:56:39

 

posted @ 2021-03-27 17:21  君子之行  阅读(95)  评论(0)    收藏  举报