当前位置: 首页 > article >正文

设计一个类使其具有动态属性,承接灵活可变的动态JSON

前言

在 java 中,如何让一个类具有动态属性。这里将介绍一种技巧,可以使得你的类,具有良好的动态属性的能力。普遍的做法是在类中申明一个 map 属性,把想要扩展的属性放入这个 map 中,这样就可以使得类具有动态属性的能力了。本文介绍的实现上本质也是如此,看到这里你是不是已经没兴趣往下看了,兄弟,先别着急,如果仅是样我也没必要写这个了。这里介绍的是具有良好的动态属性的能力,看完本文,你会获得很大的收益!

这里会介绍三种动态属性的实现方式

普遍的较好的良好的本文会循序渐进的从普遍的、较好的、良好的顺序来讲代码的演化过程。

一、普遍的

普遍的-类定义类中申明一个 map 属性,把想要扩展的属性放入这个 map 中,这样就可以使得类具有动态属性的能力了。

@FieldDefaults(level = AccessLevel.PRIVATE)
public class BirdAttr {
/** 动态属性 */
final Map<String, Object> attr = new HashMap<>();

public void setAttr(String attrName, Object value) {
this.attr.put(attrName, value);
}

public Object getAttr(String attrName) {
return this.attr.get(attrName);
}
}

使用示例

class BirdAttrTest {
public void test1() {
BirdAttr bird = new BirdAttr();
// 设置属性
bird.setAttr("name","塔姆");
bird.setAttr("age", 18);
// 获取属性
String name = (String) bird.getAttr("name");
int age = (int) bird.getAttr("age");
}
}

通过使用示例,我们可以看到,每次使用属性时都需要进行一次强转。

为了避免强转,我们有必要对这个类进行一次改造

普遍的-类改造1我们加了一些方法,这些方法的目的在于,当我们使用动态属性时可以省去强转的一个步骤。

@FieldDefaults(level = AccessLevel.PRIVATE)
public class BirdAttr {
/** 动态属性 */
final Map<String, Object> attr = new HashMap<>();

public void setAttr(String attrName, Object value) {
this.attr.put(attrName, value);
}

public Object getAttr(String attrName) {
return this.attr.get(attrName);
}

public String getAttrString(String attrName) {
return (String) this.attr.get(attrName);
}

public Integer getAttrInt(String attrName) {
return (Integer) this.attr.get(attrName);
}
}

使用示例

class BirdAttrTest {
public void test2() {
BirdAttr bird = new BirdAttr();
// 设置属性
bird.setAttr("name","塔姆");
bird.setAttr("age", 18);
// 获取属性
String name = bird.getAttrString("name");
int age = bird.getAttrInt("age");
}
}

这样看起来舒服多了(至少在使用者的角度),毕竟舒服是相对的,相对于上面的示例。

但细心的朋友会发现,每个类型都需要声明一个对应的类型转换方法。比如 int、bool、long、String、byte、short、char、double…等。这样做确实太麻烦,当然我们还可以使用泛型来确定类型,修改如下

普遍的-类改造2

@FieldDefaults(level = AccessLevel.PRIVATE)
public class BirdAttr {
/** 动态属性 */
final Map<String, Object> attr = new HashMap<>();

public void setAttr(String attrName, Object value) {
this.attr.put(attrName, value);
}

/** 泛型来确定类型 */
public <T> T getAttr(String attrName) {
return (T) this.attr.get(attrName);
}
}

使用示例

class BirdAttrTest {
public void test3() {
BirdAttr bird = new BirdAttr();
// 设置属性
bird.setAttr("name","塔姆");
bird.setAttr("age", 18);
// 获取属性
String name = bird.getAttr("name");
int age = bird.getAttr("age");
}
}

看起来似乎很完美,省去了类型的转换的同时使用起来也简洁了些。好了,到这里动态属性介绍完了 (开玩笑的)!

你会发现这个动态属性只属于这一个类,如果还有一个类也想拥有动态属性的功能呢?copy 在来一次是不可能的,但我们可以用接口的方式,也就是接下来要说的 较好的。

二、较好的

动态属性接口用接口的方式来实现动态属性,可以使得实现接口的类都具有现动态属性的功能。

public interface AttrDynamic {
/**
* 获取动态成员属性map
*
* @return 动态成员属性map
*/
Map<String, Object> getAttr();

/**
* 获取动态成员属性
*
* @param attrName 属性名
* @param <T> t
* @return val
*/
default <T> T getAttr(String attrName) {
Map<String, Object> map = getAttr();
return (T) map.get(attrName);;
}
}

类定义只需要实现接口,就能拥有动态属性的功能,非常的方便。但你会发现接口中是需要子类实现一个 getAttr() 方法的,类中没有重写 getAttr() 方法,确能够运行是因为巧妙的运用的 lombok 的 Getter 注解,由于 lombok 的这个注解会为属性提供的 getter 方法,正好能对得上接口的方法,所以就不需要显式的重写接口的 getAttr( ) 方法了。

@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class BirdAttrDynamic implements AttrDynamic {
/** 动态属性 */
final Map<String, Object> attr = new HashMap<>();
}
使用示例
class BirdAttrDynamicTest {
public void test1() {
BirdAttrDynamic bird = new BirdAttrDynamic();

// 设置属性
bird.setAttr("name", "塔姆");
bird.setAttr("age", 18);
// 获取属性
String name = bird.getAttr("name");
int age = bird.getAttr("age");
}
}

用接口的方式来实现动态属性有很多好处:

类只要实现接口就能拥有动态属性的功能。即使每个类型都需要声明一个对应的类型转换方法,比如上面普遍中提到的 int、bool、long、String、byte、short、char、double…等转换方法,我们只需要在动态属性接口中增加default 的方法就可以了(只维护接口)。如果使用【普遍的】方式中改造,假设有10个类需要动态属性,那么你需要修改10个类。从这里可以看出,【普遍的和较好的】在动态属性的实现方式中,都有一个很大的问题,我们先称为下一任维护者问题、华丽的简洁。

华丽的简洁这里指的是初次看上去的示例使用很简洁且,仿佛没什么毛病。但真正在实际中运用后才会发现缺陷,直至下一任维护者的运来问题暴露得更加疯狂。

class BirdQquestionTest {
public void test1() {
BirdAttrDynamic bird = new BirdAttrDynamic();

// 假设你是下一任的维护者,你知道这个属性名的类型吗
bird.getAttr("fuck");
}
}

从上面的示例中,你无法知道属性 “fuck” 的具体类型,这就是华丽的简洁。如果你想知道,只能阅读该项目其他地方的源码来寻找。实际中你的上一任哥们用这种方式编码可嗨了,你的上一任嗨一分,给你带来的痛苦大概是 2.25分(根据前景理论得出,该理论是行为经济学的重大成果之一)。

无论是普遍的还是较好的在动态属性的实现方式中,我们的下一任维护者无法很快的知道这个属性的确切类型。因为属性太动态的原因,为了可维护性我们需要尽量不要这么做。

当然,到这里你也可以说我们可以先定义一个类或者接口,把动态属性的属性名放到这个文件中。类似下面这样

public interface BirdAttrName {
/** name 的类型是 string */
String name = "name";
/** age 的类型是 int */
String age = "age";
/** fuck 的类型是 Fuck.java 类 */
String fuck = "fuck";
}

这个属性文件只是理想化的产物,你虽然可以在项目团队中定这些规范,让团队成员遵守。但你很难让每个成员都严格遵守(特别是后进的新成员),比如忘记写类型注释了或者说写错了类型注释呢,所以更别说你的上一任了(类似你接手的二手项目)。

那么还有较好的方式来避免这些吗?当然是有的,就是下面介绍的这种 良好的 方式

三、良好的

示例为了避免一些枯燥,这次我们先看 良好的 方式的使用示例

class BirdAttrOptionDynamicTest {
public static void main(String[] args) {
BirdAttrOptionDynamic bird = new BirdAttrOptionDynamic();

// 设置属性
bird.option(BirdAttrOption.name, "塔姆");
bird.option(BirdAttrOption.age, 19);

// 获取属性
String name = bird.option(BirdAttrOption.name);
int age = bird.option(BirdAttrOption.age);
}
}

从示例中,我们可以动态的增加属性,动态的获取属性的值,并且没有强制转换。属性名由 BirdAttrOption.java 统一来管理。

类的扩展属性文件 BirdAttrOption

interface BirdAttrOption {
AttrOption<String> name = AttrOption.valueOf("name");
AttrOption<Integer> age = AttrOption.valueOf("age");
}

可以看见,在 BirdAttrOption.java 中,我们提前添加了一些需要扩展的属性名,并且类型的明确的。属性的信息由 AttrOption.java 来明确。(就算那些家伙忘记写类型注释或者说写错了类型注释,也没关系)

业务类BirdAttrOptionDynamic 这个是我们自定义的一个业务类,只需要实现 AttrOptionDynamic 接口就能具备动态属性的功能。

@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class BirdAttrOptionDynamic implements AttrOptionDynamic {
/** 动态属性项集 */
final AttrOptions options = new AttrOptions();
}
动态属性项集 - AttrOptions
用于存放动态属性的地方,对多个属性的管理

class AttrOptions {
/** 动态成员属性 */
final Map<AttrOption<?>, Object> options = new HashMap<>();

/** 获取值 */
@SuppressWarnings("unchecked")
public <T> T option(AttrOption<T> option) {
return (T) options.get(option);
}

/** 设置值 */
public <T> void option(AttrOption<T> option, T value) {
options.put(option, value);
}
}

属性项 - AttrOption属性项, 用于确定属性的名和类型。

record 是值类型,好像是 java14 的出的(具体忘记的),转为java类的大概意思就是类中声明了一个属性 name,并自动提供 getter 方法。

record AttrOption<T>(String name) {
/** 构建属性项 */
public static <T> AttrOption<T> valueOf(String name) {
return new AttrOption<T>(name);
}
}
动态属性接口 - AttrOptionDynamic
interface AttrOptionDynamic {
/** 动态成员属性 */
AttrOptions getOptions();

/** 获取值 */
default <T> T option(AttrOption<T> option) {
return this.getOptions().option(option);
}

/** 设置值 */
default <T> void option(AttrOption<T> option, T value) {
this.getOptions().option(option, value);
}
}

总结

好的到这里就讲完了,良好的实现方式就是这样。(开玩笑的)

张三:凭什么你这个就是良好的动态属性实现方式。

OK!似乎张三提了一个问题。

在【良好的】与【普遍的和较好的】实现方案相比较,我们增加了两个类 AttrOption 和 AttrOptions。本来一个 map 可以解决的事为什么要多增加两个类呢。答案是组合与类型明确,类型明确可能会好理解一些,但组合是什么鬼。

对类型明确的说明在类的扩展属性文件 BirdAttrOption.java 中,属性名由 BirdAttrOption.java 统一来管理。即使忘记写类型注释了也没关系,因为已经明确类型了(我们在声明属性时就能明确类型了)。意味着你的团队成员 ”不能很轻松的“ 使用动态属性了,毕竟直接的敲字符总是轻松的。如

class BirdAttrTest {
public void test1() {
BirdAttr bird = new BirdAttr();
// 设置属性
bird.setAttr("name","塔姆");
bird.setAttr("age", 18);
// 获取属性
String name = (String) bird.getAttr("name");
int age = (int) bird.getAttr("age");


// test option -- error、error、error
BirdAttrOptionDynamic bird = new BirdAttrOptionDynamic();
// ======== 不能轻松的使用字符来当属性了, 下面的使用方式会在工具中报错 ========
// 因为不支持这样做
bird.option("name", "塔姆");
}
}

我们用代码级别来约束团队的成员 (此时就变成你可以不听团队的规范,但工具不允许你这样做)。

对组合的说明我们增加了两个类 AttrOption 和 AttrOptions,本来一个 map 可以解决的事为什么要多增加两个类呢?

复用:组合是在Java中实现程序复用(reusibility)的基本手段之一。

单一职责:一个类只做一件事

AttrOption:负责属性名和类型明确,实际上我们还可以扩展一些默认值。

AttrOptions:负责管理 AttrOption

类的复杂性降低,实现什么职责都有明确的定义;逻辑变得简单,类的可读性提高了,而且,因为逻辑简单,代码的可维护性也提高了;变更的风险降低,因为只会在单一的类中的修改。类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。这个原则告诉我们,尽量让每一个类保持单一责任。

对于 AttrOptions 和 AttrOption 我们还可以单独的拿出来使用,就像下面这样。

在测试的角度也更轻了些!

class BirdAttrOptionDynamicTest {
public void testAttrOptions() {

AttrOption<Long> love = AttrOption.valueOf("love");

final AttrOptions options = new AttrOptions();
options.option(love, 777L);
Long loveValue = options.option(love);
System.out.println(loveValue);
}
}

通过 “良好的 ” 动态属性实现方式,我们做到了类型的明确

我们增加了两个类 AttrOption 和 AttrOptions,做到了规范编码、单一职责、复用,真棒!

之后我们还想让其他类具有动态属性,只需实现接口和声明一个 AttrOptions 变量就可以了,是不是很简单。

@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TigerAttrOptionDynamic implements AttrOptionDynamic {
/** 动态属性项集 */
final AttrOptions options = new AttrOptions();
}

本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。

首发链接:https://www.cnblogs.com/lingyejun/p/18596171


http://www.kler.cn/a/430691.html

相关文章:

  • 每日一题 343. 整数拆分
  • 2024-12-24 NO1. XR Interaction ToolKit 环境配置
  • vue 基础学习
  • 《计算机组成及汇编语言原理》阅读笔记:p86-p115
  • 基于STM32F103控制L298N驱动两相四线步进电机
  • 【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
  • Java-WebSocket
  • Day2——需求分析与设计
  • [工具和软件]查询在用软件是否为最新版本
  • 虚幻引擎Actor类生命周期
  • Rust快速入门(五)
  • uni-app H5端使用注意事项 【跨端开发系列】
  • 面试题(仅供参考)
  • 深入理解代理模式(Proxy):静态代理、动态代理与AOP
  • 基于SpringBoot的养老院管理系统的设计与实现
  • PS学习第一天
  • 记录 idea 启动 tomcat 控制台输出乱码问题解决
  • maven报错“找不到符号“
  • 将路径转换为短路径形式(8.3格式)解决 `CFile::Open` 无法打开长路径问题
  • 从零搭建网站(第三天)
  • 连续大涨,汉王科技跑步进入AI应用舒适区
  • Vue.js的生命周期
  • go使用闭包处理数据
  • List与Set、数组与ArrayList、ArrayList与LinkedList的区别
  • 【kafka】常用基础命令使用案例
  • ViT学习笔记(三) RepViT和TransNext简介