-
Notifications
You must be signed in to change notification settings - Fork 59
Creating new operations
Zeno defines a pattern for creating a clean separation between our data model and the operations we need to support for that data model. There are a few components which should be written in order to create a semantically- and structurally- independent operation:
- NFSerializationRecord – Tracks state during traversal of an object instance.
- FrameworkSerializer – Describes what to do when individual elements are encountered during traversal of some object.
- SerializationFramework – Defines an interface for using the operation.
- FrameworkDeserializer – Optional: for reversible operations, describes what to do when an NFTypeSerializer asks for an element during deserialization
- NFDeserializationRecord – Optional: for reversible operations, tracks state during object reconstruction.
What we’re actually going to create is a definition of what to do when we traverse some arbitrary data. The SerializationFramework class contains the NFTypeSerializers which define some data model. Those NFTypeSerializers serve as the “glue” for our traversal. We define how to continue the traversal when we encounter another object. We also define what happens when we encounter “zeno native” elements, which are always leaf nodes. The serializers tell us what individual elements we encounter for each instance of the serializer’s type.
Let’s create an example. Let’s say that you have a (somewhat strange) requirement: for any arbitrary object, you need to calculate the sum of all of the integers contained in that object or any of its children. We will first want to define a “serialization record”. This will track any state we require during the traversal of any object instance. In our case, this state is the running total of our ints:
Now, we need to define what to do when we encounter individual elements while traversing objects. We will want to add any ints we encounter to our running total. In addition, we will need to define what happens when we encounter an element we are unfamiliar with (i.e. some object which is not one of the types we define behavior for). What we want to do here is continue the traversal. In our case, we want to pass down the same “IntSumRecord” we are using to recursively serialize this element with the “IntSumFramework”:
Notice that here, we also have the opportunity to define the behavior when we encounter Lists, Sets, and Maps. In our case, we are simply interested in recursively traversing the individual elements of those containers.
Now, we have to define the interface for our int sum operation:
Now, we are done. We have actually done something pretty interesting here. We can reuse this operation for any data model to sum all of the integers held by any type. As our data model changes, we will not have to revisit this operation to update it with new int fields. If we want to make changes to this operation (for instance, if we decide we are actually interested in long fields too), we will not have to modify our data model, or write code that must change with our data model, in order to accomplish these changes.
Let’s try it out on our example data model:
Yep, it works:
It is unlikely that you will find it useful to know the sum of the integers over any arbitrary objects. However, creating a new framework can potentially be used to address requirements for alternate visual or serialized representations of data, gather statistics about data, automatically detect inefficiencies in a data model, and potentially myriad other things we haven’t thought of yet. If you do implement additional operations which you find useful, please consider contributing them back to Zeno.




