The Class-Function Duality

Di Fan
3 min readOct 2, 2022

I have been refactoring a number of APIs at work. A common pattern is to split a large API class into smaller ones. The desired smaller interfaces usually become abstracted classes with a single method. For example, there is an interface to collect data items and persisted them in a data store.

This is in C++, our programming language of choice. Simply put, Collector is an interface with a single abstract method Collect that takes a single data and returns no value.

If you are an enthusiast of functional programming, you might ask, “why couldn’t this be simply be a function interface?”. Well, of ourse it could! Then it will look like this:

This simply says CollectorFn is a function type that takes a single data and returns no value, exactly the same as the class interface above! We can use function interfaces instead of class interfaces, because functions are also a first-class type in various programming languages. They can be passed around just like a regular object.

NOTE: in C++, the use of std::function is usually a bad idea as its a copyable type, the alternative is to use the move-only absl::AnyInvocable or std::move_only_function from C++ 23.

The main benefit of using a function interface instead of a class interface is that the interface cannot be expanded beyond its signature. If the interface is a class, someone might add a new method, some members, type aliases, constructors / destructors, etc. None of that is possible with a function interface, keeping the interface stable and intact.

This might all sounds well, but the systems you work with might already be be filled with class interfaces, is it still possible to adopt function interfaces?

Converting a Function to a Class

Function can be converted to classes, and this technique can be used to plug the simpler function interface into an existing system with class interfaces.

Take the Collector and CollectorFn above as an example, a FunctionCollector can be implemented to convert a CollectorFn into a Collector class.

That’s it! Now you can implement a bunch of CollectorFns and adapt them into the existing class interfaces whenever and wherever needed.

Converting a Class to a Function

Sometimes implementing something using functions only might be really inconvenient, and class features could really come in handy. Say for a particular Collector implementation, we really want to buffer all the data items and perform the writes when the Collector fall out of scope in the current stack. With a class, this can be trivially implemented in the class' destructor. So let's do that, but adapt the class back into a function to fit the function interface.

Here, we leverage closure in a lambda expression to make a CollectorFn that keeps around the Sink class with desired lifetime behavior. The usage of class and class features become entirely implementation details. e.g. in C++, the Sink class can be put into an anonymous namespace in the .cc file with only MakeDbCollectorFn exposed in the header file.

Conclusion

When it comes to describing and implementing interfaces, classes and functions really work out the same. One can be converted to the other without much efforts. Generally using functions should keep the surface areas of your interfaces and modules small, thus making code easier to maintain.

--

--

Di Fan

Traveler, Reader, Dreamer. Writing highly deletable codes.