Object-Oriented Declarative Input/Output in Cactoos

Cactoos is a library of object-oriented Java primitives we started to work on just a few weeks ago. The intent was to propose a clean and more declarative alternative to JDK, Guava, Apache Commons, and others. Instead of calling static procedures we want to use objects, the way they are supposed to be used. Let’s see how input/output works in a pureobject-oriented fashion.

Disclaimer: The version I’m using at the time of writing is 0.9. Later versions may have different names of classes and a totally different design.

Pay attention—there are no method calls yet. Just three constructors of three classes that compose a bigger object. The object source is of type Bytes and represents the content of the file. To get that content out of it we call its method asBytes():

bytes[]content=source.asBytes();

This is the moment when the file system is touched. This approach, as you can see, is absolutely declarative and thanks to that possesses all the benefits of object orientation.

Here is another example. Say you want to write some text into a file. Here is how you do it in Cactoos. First you need the Input:

Now, we want to copy the input to the output. There is no “copy” operation in pure OOP. Moreover, there must be no operations at all. Just objects. We have a class named TeeInput, which is an Input that copies everything you read from it to the Output, similar to what TeeInputStream from Apache Commons does, but encapsulated. So we don’t copy, we create an Input that will copy if you touch it:

Inputtee=newTeeInput(input,output);

Now, we have to “touch” it. And we have to touch every single byte of it, in order to make sure they all are copied. If we just read() the first byte, only one byte will be copied to the file. The best way to touch them all is to calculate the size of the tee object, going byte by byte. We have an object for it, called LengthOfInput. It encapsulates an Input and behaves like its length in bytes:

Scalar<Long>length=newLengthOfInput(tee);

Then we take the value out of it and the file writing operation takes place:

longlen=length.value();

Thus, the entire operation of writing the string to the file will look like this:

Works fine, but what will happen when you decide to extend it to also write to an OutputStream? How will you modify this class? How ugly will it look after that? That’s because the design is not object-oriented.

This is how you would do the same design, in an object-oriented way, with Cactoos:

That’s because concepts are perfectly separated and functionality is encapsulated. In the procedural example the behavior of the object is located outside of it, in the method encode(). The file itself doesn’t know how to write, some outside procedure Files.write() knows that instead.

To the contrary, in the object-oriented design the FileAsOutput knows how to write, and nobody else does. The file writing functionality is encapsulated and this makes it possible to decorate the objects in any possible way, creating reusable and replaceable composite objects.