功能编程:如何处理复杂数据而又不膨胀功能?

时间:2019-02-24 19:47:29

标签: design-patterns functional-programming software-design purely-functional pure-function

可以说,您在程序中定义了一个复杂的汽车对象。该对象包含很长的预定义键值对列表(wheelsenginecolorlightsamountDoors等),每个都是零件号或零件号列表或特定值。

//** PSEUDO CODE:
var inputCar = { 
  "engine": "engine-123", 
  "lights": ["light-type-a", "light-type-b"], 
  "amountDoors": 6,
  etc ... lets assume a lot more properties
}

我们还假设此对象已经尽可能简单,无法进一步简化。

此外,我们还有一个设置列表,可向我们提供有关零件编号的更多信息,并且每种零件都不同。对于引擎,它可能看起来像这样:

var settingsEngine = [
  { "id": "engine-123", weight: 400, price: 11000, numberScrews: 120, etc ... },
  { "id": "engine-124" etc ... }
]

所有设置都捆绑在一个主设置对象中

settings = { settingsEngine, settingsWheel, settingsLight ... }

现在我们有不同的功能,应该采用Car并返回有关其的某些值,例如重量,价格或螺钉的数量。

要计算这些值,以匹配输入汽车的ID和设置中的ID,并应用一些逻辑来获取复杂零件的准确数据(找出车身是什么样子,我们需要看看有多少扇门,轮子有多大等等。

对于汽车的每个零件,获取价格也将有所不同且任意复杂。定价的每个部分可能都需要访问有关汽车的不同零件和信息,因此仅映射零件清单是不够的。 (对于油漆工作的价格,我们需要所有具有相同颜色等的零件的总表面积。)

一个想法是创建一个中间对象,该对象解决了所有在价格和重量计算之间共享的有关汽车的细节,然后可以用来计算重量,价格等。

一个实现可能看起来像这样:

var detailedCar = getDetailedCar(inputCar, settings);

var priceCar = getPriceCar(detailedCar);
var weightCar = getWeightCar(detailedCar);

这样,部分工作只需完成一次。但是在此示例中,detailedCar比初始输入对象要复杂得多,因此getPriceCar的参数也是如此-使其非常难以测试,因为我们始终需要一个每个测试用例的完整汽车对象。因此,我不确定这是否是一个好方法。

问题

对于不能处理功能复杂的输入数据的程序,以功能性编程风格/具有纯函数/组成的程序来说,什么是好的设计模式?

在复杂,相互依赖的输入下,结果如何易于进行单元测试?

2 个答案:

答案 0 :(得分:3)

您所描述的通用术语是使用投影。投影是一种数据结构,它是其他数据结构的抽象,面向您要进行的各种计算。

在您的示例中,您需要一个“螺钉投影”,该螺钉可以获取描述车辆的数据并提供所需的螺钉。因此,我们定义一个函数:

screwProjection(vehicle, settings) -> [(screwType, screwCount)]

包含车辆以及描述组件的设置,并提供了构成车辆的螺钉。如果您不关心screwType,也可以有一个进一步的投影,简单地将元组中的第二个项相加。

现在,要分解screwProjection(),您将需要对车辆的每个组件进行迭代,然后根据需要对其进行分解。例如,在您的示例的第一步中,获取engine并找到适合于引擎的设置,然后根据引擎类型进行过滤,然后根据螺丝字段过滤结果:

partProjection(part, settings) -> [(partType, partCount)]

所以screwProjection()看起来像:

vehicle.parts
  .flatMap( part -> partProjection( part, settings ) ) // note 1
  .filter( (partType, partCount) -> partType == 'screw' )
  .map( (partType, partCount) -> partCount )
  .sum()

注1)此投影方法不允许嵌套的物料清单查找,您可能希望添加这些查找以获得额外的信用。

枚举=>投影=>过滤器=>减少的一般方法是许多功能编程范例的核心。

答案 1 :(得分:1)

我会在这里建议稍微不同的方法。

由于您的问题是关于纯函数式编程的,所以我想说您需要一个高阶函数来减轻复杂数据结构的必要位并遮盖不必要的位: @JsonFormat,其中 File inputFile = new File("some path"); ObjectMapper xmlMapper = new XmlMapper(); xmlMapper.registerModule(new JavaTimeModule()); Dataset dataset = xmlMapper.readValue(inputFile, Dataset.class); 代表您需要从某个readComplexDataStructure :: (ComplexDataStructure -> a) -> (a -> b) -> ComplexDataStructure -> b实例中提取的数据,而a是计算的结果。

请注意,它与ComplexDataStructure monad有多近,尽管除非代码复杂性证明这样的决定,否则我不建议立即使用它。

P.S。它可以缩放。您只需要一个函数即可产生由b投影组成的n形。例如,请考虑以下签名:Reader。只要您仅保持适当的预测,您的代码就不会变得“ ated肿”,其余所有代码都具有很强的结构性和自我描述性。