188足球比分直播重构代码以加载文档

许多现代的Web服务器代码与返回JSON数据的上游服务进行通信,仔细阅读一下JSON数据,并使用流行的单页应用程序框架将其发送到富客户机网页。与使用此类系统的人员交谈时,我听到他们需要做多少工作来操纵这些JSON文档,这让我相当失望。通过封装加载策略的组合可以避免这种挫折。

2015年12月17日

发现 类似物品在标签上: 188足球比分直播

当我加载一个文档时——不管是json,XML或者任何其他层次的数据-我必须选择如何在我的程序中表示它。我自己写的加载代码不多,有图书馆为我做这件事,但我仍然需要选择数据的最终位置。这个选择应该基于我将如何使用数据。但我经常会遇到将所有东西加载到复杂对象结构中的程序,通常以不必要的代码结尾,这些代码很冗长,很难跟上时代。


初始代码

To illustrate some different approaches I'll use an example service that provides information about assortments of music albums.它使用的数据看起来像这样。

“相册”:[“标题”:“isla”,“艺术家”:“门廊四重奏”,“tracks”:[“title”:“剪刀石”,“lengthinseconds”:327,{"title": "The Visitor",“长度秒”:330,“title”:“黎明巡逻”,“长度秒”:359,“title”:“行”,“长度秒”:449,“标题”:“生活面具(插曲)”,“长度秒”:75,“title”:“剪报”,“长度秒”:392,“title”:“生活面具”,“长度秒”:436,“title”:“isla”,“长度秒”:310,“标题”:“棚歌(即兴曲1号)”,“长度秒”:503,“标题”:“苏波精神崩溃”,“长度秒”:347],“title”:“地平线”,“artist”:“Eyot”,“tracks”:[“title”:“远场”,“lengthinseconds”:423,“标题”:“一石一石”,“lengthinseconds”:479,“title”:“如果我想说什么就说什么”,“长度秒”:167,“title”:“我想说的一切”,“长度秒”:337,“title”:“喘振”,“lengthinseconds”:620,“title”:“3个月后”,“lengthinseconds”:516,“title”:“地平线”,“lengthinseconds”:616,“title”:“鲸鱼之歌”,“长度秒”:344,{"title": "It's time to go home",“lengthinseconds”:539]]

该服务是用Java编写的,使用杰克逊用于读取JSON的库。通常的做法是定义一系列Java类,并使用杰克逊的NIFTY数据绑定功能将JSON数据映射到类的字段中。为了处理这些数据,我们需要这些类。

分类…

私人名单
      
       专辑;公开名单
       
        getAlbums()返回collections.unmodifiableList(相册);}
       
      

班级专辑…

私人弦乐艺术家;私有字符串标题;私人名单航迹;public string getartist()返回artist;}public string getTitle();返回标题;公共名单getTracks()返回collections.unmodifiableList(tracks);}

班级田径赛…

私有字符串标题;private int lengthInSeconds;public string getTitle();返回标题;}public int getlengthinseconds()返回lengthinseconds;}

杰克逊使用反射来自动映射JSON数据到相应的Java对象。多亏了这一点,我不需要编写任何代码来加载对象。我愿意,然而,必须定义JSON加载到的对象。

我可以使用任何一个公共领域,或者有公共获取者的私有领域。getter会生成更详细的代码,但我喜欢用它们,因为我喜欢统一接入原则.Intellij为我写了很多东西,这进一步减少了烦恼。

对于这样定义的类,然后加载JSON数据是一个简单的方法调用

类服务…

公共字符串周二音乐(字符串查询)尝试分类数据=json.mapper().readValue(datasource.getAlbumList(查询),分类、分类);返回json.mapper().writeValueAsString(data);}catch(例外e)日志(e);throw new RuntimeException(e);}

JSON数据通过调用来自某些数据源datasource.getAlbum(查询).这可能是打给其他服务的电话,访问面向JSON的数据库,从文件中读取,或者其他与我无关的信息来源。

JSON类…

public static objectmapper mapper()jsonFactory f=new jsonFactory().enable(jsonParser.feature.allow_comments);返回新的Objectmapper(f);}

虽然我不需要编写任何代码来从JSON数据加载对象,我必须编写对象定义。在这种情况下不算多,但我遇到过这样的情况:有100个类需要表示JSON数据。如果这些东西没有多大用处,然后写这样的类定义是烦人的繁琐工作。

在这种情况下,它是合理的,我们使用数据绑定中定义的所有对象和方法。所以如果我要定义10个类和200个公共方法,只使用其中五个,这是有问题的迹象。为了研究替代方案,我们需要看几个不同的案例,并考虑我们可以采取的各种替代路线。

But whatever the alternative,我从同样的第一步开始。


封装产品组合

每当我进行重构时,188足球比分直播我想看看如何通过在一些合适的方法后面封装大量的变更来降低变更的可见性。Since I'm working with assortments,这意味着将产品组合与JSON之间的转换职责转移到产品组合本身。

我先用提取方法加载产品组合的代码。

类服务…

公共字符串周二音乐(字符串查询)尝试分类数据=装载分类(查询);返回json.mapper().writeValueAsString(data);}catch(例外e)日志(e);throw new RuntimeException(e);}私有产品组合加载产品组合(字符串查询)抛出java.io.ioException返回json.mapper().readValue(datasource.getAlbumList(query),分类、分类);}

我不喜欢检查异常,so my default is to wrap them in a runtime exception as soon as they rear their ugly head.

类服务…

私有产品组合加载产品组合(字符串查询){尝试{返回json.mapper().readValue(datasource.getAlbumList(query),分类、分类);}catch(ioexception e)抛出新的runtimeexception(e);}}

我希望新方法采用一系列JSON数据,so I adjust the arguments to the new method.

类服务…

公共字符串周二音乐(字符串查询)尝试分类数据=加载分类(datasource.getalumlist(查询);返回json.mapper().writeValueAsString(data);}catch(例外e)日志(e);throw new RuntimeException(e);}}私人产品组合加载产品组合(字符串杰森)尝试返回json.mapper().readValue(杰森,分类、分类);}catch(ioexception e)抛出新的runtimeexception(e);}

很好地提取了行为,然后我可以将它作为工厂方法移动到分类类。

类服务…

公共字符串周二音乐(字符串查询)尝试分类数据=分类。fromJson(datasource.getalbumlist(query));返回json.mapper().writeValueAsString(data);}catch(例外e)日志(e);throw new RuntimeException(e);}

分类…

公共静态分类fromJson(字符串json)尝试返回json.mapper().readValue(json,分类、分类);}catch(ioexception e)抛出新的runtimeexception(e);}

我不能用intellij的move方法重构,188足球比分直播但手动操作很容易。

I used the same sequence of steps to extract and move the code to emit the JSON.

类服务…

公共字符串周二音乐(字符串查询)尝试产品组合数据=产品组合.fromjson(datasource.getalbumlist(query));返回TojSon()数据;}catch(例外e)日志(e);throw new RuntimeException(e);}

分类…

public string tojson()try return json.mapper().writeValueAsString(this);}catch(jsonProcessingException e)引发新的RuntimeException(e);}

如果我有其他的服务方法使用这样的产品组合,现在我将调整他们所有的调用以使用这些新方法。一旦我完成了,我就有了封装它们的序列化机制的对象,这使我更容易改变它。

有些人可能会觉得奇怪,我使用了短语“封装他们的序列化机制”。当人们被教导对象定向时,他们经常被告知数据是封装的,所以把这样的行为看作应该被封装的东西听起来可能很奇怪。然而,封装的关键是做一些决定,可以选择行为或数据结构,把它变成一个秘密,这样它就可以从外面隐藏起来,而不需要知道封装边界后面发生了什么。在这种情况下,我不希望产品组合之外的任何代码知道产品组合与JSON的关系,所以我封装了在分类类中进行加载的代码。


通过JSON

现在我已经封装了产品组合的JSON处理,我可以开始研究重构它的不同方法,取决于它的使用方式。

最简单的情况是,服务只需要提供给产品组合的相同JSON。在这种情况下,我可以将JSON字符串本身存储在产品组合中,而不必麻烦Jackson。

分类…

私有字符串json;公共静态产品组合FromJSON(String JSON)产品组合结果=新产品组合();result.json=json;返回结果;}public string tojson()返回json;}

然后我可以完全删除相册和跟踪类。

实际上,在这种情况下,我会完全取消分类类,直接让服务返回数据源调用的结果。

类服务…

公共字符串周二音乐(字符串查询)尝试返回datasource.getalbumlist(查询);}catch(例外e)日志(e);throw new RuntimeException(e);}

内部客户

接下来,我将考虑在服务器进程中有一个客户机需要从JSON数据收集一些信息的情况。

类SomeClient…

公开名单
      
       计量(分类和分类)清单
       
        titles=anasortment.getAlbums().stream().map(a->a.getTitle()).collect(collectors.tolist());返回具有(标题)的thingcleverwith;}
       
      

这种情况需要我的一些Java代码来操纵JSON数据,所以我需要的不仅仅是一根绳子。然而,如果这是唯一的客户,然后我不需要构建我之前展示的整个对象树。相反,我可以拥有类似这样的Java类。

分类…

私人名单
      
       专辑;公开名单
       
        getAlbums()返回collections.unmodifiableList(相册);}
       
      

班级专辑…

私有字符串标题;public string getTitle();返回标题;}

我可以完全删除跟踪类。

如果我只需要部分JSON数据,完全导入是没有意义的。通过数据绑定只指定我需要的位是一种很好的方法来获得这样一组简化的数据。使用类似这样数据绑定的库通常具有一个配置参数,该参数指示数据绑定应如何处理JSON中目标记录中没有绑定的字段。默认情况下,杰克逊扔了一个无法识别的属性异常,但是很容易通过禁用在未知属性上失败具有类似呼叫的功能

anObjectmapper.disable(反序列化功能。在未知属性上失败)

通过声明只包含我需要的属性的类结构,我正在避免不必要的努力-声明属性的行为和选择我想要的字段一样好。

这是另一个Java类想要通过Java API调用获得分类的专辑标题的情况。类似的情况是,客户机需要JSON数据的一个子集,规则的托森现在,Call只会返回专辑标题。

“唱片集”:[“标题”:“isla”,“标题”:“地平线”]

如果我只使用输入文档中数据的一个子集,我通常会发现最好的安排方式,所以我只把自己和我使用的数据联系在一起。如果我定义一个要映射到的完整对象结构,然后,如果供应商向JSON添加一个我可以安全忽略的字段,我的代码就会中断。通过只定义我使用的结构,我正在创造一个宽容的读者,这使我的程序更能适应输入数据的变化。


Java API与完整JSON

这自然导致了我的第三个病例,如果我们希望服务调用返回完整的JSON文档,但同时,我们希望通过Java API的专辑名称列表?

这个案例的答案是前两个案例的结合,存储原始JSON字符串和API所需的任何Java对象。所以在本例中,我将字符串添加到工厂方法中。

分类…

私有字符串文档;public static assortment fromjson(string json)try final assortment result=json.mapper()。disable(deserializationfeature.fail on unknown properties).readvalue(json,分类、分类);result.doc=json;返回结果;}catch(ioexception e)抛出新的runtimeexception(e);}}public string tojson()返回文档;}

一旦我做到了,我可以从Java数据结构中删除不需要的方法和类,正如我在使用Java API时所做的一样。


丰富输出JSON文档

到目前为止,这些场景都将JSON文档视为我们所需信息的完整载体,但有时我们使用服务器进程来丰富这些信息。考虑一下我们希望了解每个专辑长度的情况。有了完整的对象结构,这很容易。

班级专辑…

public int getlengthinseconds()返回tracks.stream().collect(collectors.summingint(track::getlengthinseconds));}

此方法都使用Java方法调用向Java客户端提供信息,当我使用Jackson的数据绑定创建输出JSON时,它还会自动添加到JSON输出中。唯一的问题是,这迫使我将整个JSON结构声明为Java类,对于这个小例子来说,这不是一个大问题,但是当有数十个或数百个其他不必要的类定义时,这是一个问题。我可以通过使用和丰富文档模型来避免这种情况。

我将从一组完整的类定义的原始起188足球比分直播点开始重构,以及专辑长度的方法。我的第一步是,和前面的例子一样,阅读时添加额外的JSON文档。不过这次我要把它读成杰克逊树的模型,我还没有修改输出代码。

分类…

私人名单
      
       专辑;
       私有jsonnode文档;公开名单
       
        getAlbums()返回collections.unmodifiableList(相册);}公共静态分类fromjson(string json)尝试最终分类结果=json.mapper().readValue(json,分类、分类);
        result.doc=json.mapper().readtree(json);返回结果;}catch(ioexception e)抛出新的runtimeexception(e);}}public string tojson()try return json.mapper().writeValueAsString(this);}catch(jsonProcessingException e)引发新的RuntimeException(e);}
       
      

我的下一步是创建一个方法,该方法将输出即将丰富的JSON文档模型。我从一个简单的访问器开始,测试它是否只输出当前文档。

分类…

public string enrichedjson()返回doc.toString();}

类测试仪…

@test public void enrichedjson()抛出异常jsonnode expected=json.mapper().readtree(new file(“src/test/enrichedjson.json”));jsonnode actual=json.mapper().readtree(get什锦().enrichedjson());断言等于(应为,实际);}

目前,测试只是探测到输出文档与输入相同,但是我现在有了一个钩子,在那里我可以逐渐地向输出添加浓缩步骤。这里没什么大不了的,因为我只有一个,但如果文件有几处丰富之处,然后这样的测试允许我一次添加一个。

So now it's time to set up the enrichment code.

分类…

public string enrichedjson()jsonnode result=doc.deepcopy();getAlbums().foreach(a->a.enrichjson(result));返回result.toString();}

班级专辑…

public void enrichjson(jsonnode parent)final objectnode alumnode=matchingnode(parent);albumNode.put("lengthInSeconds",getlengthinseconds());}private objectnode matchingnode(jsonnode父级)最终流
      
       albumnodes=streamSupport.stream(parent.path(“相册”).spliterator(),假);返回(objectnode)alumnodes.filter(n->n.path(“title”).astext().equals(title)).findfirst().get();}
      

我丰富的基本方法是遍历Java记录,要求每个节点丰富其对应的树节点。

一般来说,我更喜欢使用收集管道。但必须承认与流线支撑分裂者是一种右侧疼痛。希望随着时间的推移,杰克逊会直接支持流媒体,为了避免不得不这样做。

而不是修改产品组合中嵌入的文档,I prefer to create a new document.一般来说,我更喜欢将数据保持为已读,并根据需要进行更新。如果构建新的JSON文档的成本很高,我总是可以缓存结果。

如果我有很多财富,我可以用这个机制一次添加一个,在每次更改时测试新数据。然后,一旦我完成了,我可以将数据绑定输出替换为强化文档。

分类…

公共字符串托森()jsonnode result=doc.deepcopy();getAlbums().foreach(a->a.enrichjson(result));返回result.toString();}

Once I've done that I can merrily prune my class structure,删除所有未使用的数据,方法,和类。在这个例子中不是很多,但是用这种方式撕掉37个类是很有趣的。

只使用树模型

通过使用树模型作为富集的基础,我可以从Java类声明中修剪更多。在这种方法中,我仅仅通过遍历文档树来丰富内容,根本不涉及域对象。在这种情况下,扩展代码应该是这样的。

分类…

public string tojson()jsonnode result=doc.deepcopy();EnrichDoc(结果);返回result.toString();}private void enrichDoc(jsonnode doc)for(jsonnode n:doc.path(“相册”))enrichAlbum((objectnode)n);}private void enrichAlbum(objectNode alumnode)int length=0;对于(jsonnode n:albumnode.path(“tracks”))length+=n.path(“lengthinseconds”).asint();albumNode.put("lengthInSeconds",长度);}

This kind of code should look familiar to those who are used to manipulating名单和Hashs使用动态语言。总的来说,然而,我不喜欢Java。拥有和使用Java对象通常是放置行为的最佳场所,尤其是当它在体积和复杂性上增长时。

有一个例外,我会使用这种样式,然而,如果在层次结构中有许多层通过其他不需要的类的层进行导航,尽管如此,我还是倾向于只使用较低级别的节点来创建对象。


在文档结构深处创建对象

如果我们有一个需要很多类来表示的大文档,其中大部分是不必要的,但是有一些重要的东西朝着树的叶子往下吗?在前面关于专辑标题列表的示例中,我可以支持它们,并通过数据绑定到JSON数据的一个子集来消除跟踪。但如果我只想要低一点的呢?

以我为例,让我们设想一个客户想要一个跟踪列表。因为在曲目和分类之间只有一个专辑类,我会通过数据绑定处理这个案例。但让我们假设有12个不需要的层,那么呢?

在这种情况下,我希望避免数据绑定所有这些层,但我仍然希望为客户提供合适的对象。为了说明我如何处理这个问题,假设到目前为止,我只有一个需要整个JSON字符串的客户机,所以我遵循了前面的重构路径,只将其存储在我的产品组合188足球比分直播中。

分类…

私有字符串json;公共静态产品组合FromJSON(String JSON)产品组合结果=新产品组合();result.json=json;返回结果;}public string tojson()返回json;}

那很好,这个问题的一个很好的最小解决方案。But then I get a new feature that requires a new Java API over the document.

类SomeClient…

公共字符串计量(分类和分类)最终列表tracks=anasortment.gettracks();用(轨迹)返回某个杠杆;}

再一次,对于这个文档,我将切换到数据绑定,但相反,我们将继续假装,在分类和曲目之间,而不是一个单独的专辑类,实际上,我有很多东西——足以阻止我进行数据绑定。

我想使用文档树,所以我的第一步是从字符串重构到树。

分类…

私有jsonnode文档;公共静态产品组合FromJSON(String JSON)产品组合结果=新产品组合();尝试result.doc=json.mapper().readtree(json);}catch(ioexception e)抛出新的runtimeexception(e);}返回结果;}public string tojson()返回doc.tostring();}

完成了这些准备重构之后,188足球比分直播然后我就可以创建轨迹了。我通过只选择跟踪节点,然后使用以跟踪节点为源的数据绑定来实现这一点。

分类…

公开名单getTracks()返回streamSupport.stream(doc.path(“相册”).spliterator(),false).map(a->a.path(“tracks”)).flatmap(i->streamSupport.stream(i.spliterator(),false)).map(track::fromjson.collect(collectors.tolist());}

班级田径赛…

私有字符串标题;private int lengthInSeconds;public string getTitle();返回标题;}public int getlengthinseconds()返回lengthinseconds;}public static track fromjson(json node node)try返回json.mapper().treetoValue(node,田径班;}catch(jsonProcessingException e)引发新的RuntimeException(e);}

我用一张唱片展示了这个,但是用小的子树也很容易做到这一点,允许我从一个较大的文档中提取一些重要信息。如果我正在对文档进行更为认真的重组,这种方法很方便,我可以使用这些本地API作为数据源来构建输出数据传输对象然后我可以将其序列化为适当的输出表示形式

我还可以从完整的数据绑定模型中进行类似的重构。188足球比分直播


包扎

我们的编程工作的很大一部分在于操作和处理通过层次数据文档传入的数据。要记住,有几种方法可以处理这些信息,并选择正确的组合来保持代码库的小型和灵活。人们通常会忘记封装意味着隐藏数据结构和处理方法,我们不应该期望一个模块的接口对应于它的内部存储器。


分享:
如果你觉得这篇文章有用,请分享。我感谢你的反馈和鼓励

有关类似主题的文章…

…看看标签:188足球比分直播


确认

我的同事卡莱尔·戴维斯,Chris Birch彼得·霍奇森在我起草这篇文章的时候和我讨论过

重要修改

2015年12月17日:出版的最后一期首期

2015年12月15日:已发布的丰富输出JSON部分

2015年12月14日:出版的第一期