使用函数式编程,提取子类对象的超类字段

3

我有两个类,一个是Animal(动物),另一个是Dog(狗)。

public class Animal {
    private String name,age;
    //public getters and setters
}

public class Dog extends Animal {
    private String color;
    //public getters and setters
}

我正在使用Java 8函数式编程从JSON节点中提取字段,即

public class EntityExtraction {

    private Function<JsonNode, Animal> extractAnimal = node -> {
         Animal animal = new Animal();
         animal.setName(node.get("name").asText()));
         animal.setAge(node.get("age").asText()));
         return animal;
    };

    private Function<JsonNode, Dog> extractDog = node -> {
        Dog dog = new Dog();
        dog.setName(node.get("name").asText()));
        dog.setAge(node.get("age").asText()));
        dog.setColor(node.get("color").asText()));
        return dog; 
    };

}

问题在于它不是面向对象的。如果Animal类中有一个新字段,则必须在两个函数方法(即extractDog和extractAnimal)中显式地添加它。
是否有一种方法可以在不通过构造函数的情况下,使用extractDog方法在Dog类中设置超类字段,例如:
private Function<JsonNode, Dog> extractDog = node -> {
    Dog dog = new Dog();
    //extract superclass fields
    //dog.animal = extractAnimal.apply(node);
    dog.setColor(node.get("color").asText()));
    return dog;
};

你为什么要返回一个 Function?使用类似于 Dog extractDog(JsonNode node) 的签名不就足够了吗?在流中,你仍然可以非常方便地调用它。例如,map(extractDog()) 将变成:map(this::extractDog)(如果该方法在同一类中)。如果你需要从其他地方调用它,你不需要调用 apply,只需像调用任何常规函数一样调用它,例如 Dog dog = extractDog(json) - Roland
为什么要编写自己的JSON序列化库,当Jackson已经完成了它?我只会自己做,如果我对学习如何做或不想依赖于其他库感兴趣。我使用Jackson并且晚上睡得很好。 - duffymo
@duffymo 我知道你的意思。但是这个 jsonNode 的字段命名完全不同。 - user2083529
@user2083529 命名不是问题,例如您可以将dogAge JSON字段与Dog中的age字段匹配。 - Andrew Tobilko
@AndrewTobilko,我的模型数据结构与我接收到的不同,例如我收到的是JSON中的“entityName”,而我在模型中定义的是“dogName”。除此之外,还有对象具有完全不同的数据结构,因此我正在手动处理它们。 - user2083529
3个回答

6
您可以编写一个方法来提供正确的实例以便进一步填充。
private <T extends Animal> T extractAnimal(JsonNode node, Supplier<T> animalSupplier) {
    T animal = animalSupplier.get();

    animal.setName(node.get("name").asText());
    animal.setAge(node.get("age").asText());

    return animal;
}

在获得填充了Animal属性的对象后,您可以根据类型继续打包它:

Dog dog = extractAnimal(node, Dog::new);
dog.setColor(node.get("color").asText());
...
Cat cat = extractAnimal(node, () -> getPopulatedCat());

更新:

为了避免重构,调用新方法即 extractAnimal(JsonNode node, Supplier<T> animalSupplier) 从函数式方法 extractAnimal 中调用,如下:

private Function<JsonNode, Animal> extractAnimal = node -> extractAnimal(node, Animal::new);

*为了遵循函数式编程范式,您不一定需要完全使用Java函数类型。以上方法比私有的Function字段更优雅地表达了这个想法。


why Supplier<T>? - user2083529
@user2083529 能够返回 T。 - Andrew Tobilko

3

将抽取映射缩小到仅提取相应类型的字段,并将它们声明为 BiConsumer:

    public class EntityExtraction {

        private static final BiConsumer<JsonNode, Animal> extractAnimal = (node, animal) -> {
            animal.setName(node.get("name").asText());
            animal.setAge(node.get("age").asText());
        };

        private static final BiConsumer<JsonNode, Dog> extractDog = (node, dog) -> {
            dog.setColor(node.get("color").asText());
        };
    }

每个 BiCosnumer 从一个 JsonNode 中提取特定 Animal 类型的值。现在我们通过一个工厂方法扩展了 EntityExtraction,它将返回一个提取出来的 Dog
    public static Dog extractDog(JsonNode node) {
        Dog dog = new Dog();
        BiConsumer<JsonNode, Dog> extraction = extractDog.andThen(extractAnimal);
        extraction.accept(node, dog);
        return dog;
    }

它只需要一个JsonNode作为输入。由于它应该提取一个Dog,因此它创建了一个实例并对其应用所有必要的提取映射。

为了映射一个JsonNode,我们使用工厂方法。

Dog dog = EntityExtraction.extractDog(node);

更复杂的提取映射可以通过链接提取映射器来构建。

我喜欢你的解释。 - user2083529
尝试不同的方法,你会发现哪种最适合你的情况。 - LuCio

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接