Thursday, November 08, 2018

Using configuration records and operators to reduce number of overloaded methods

When writing libraries you sometimes want to provide users (that is, programmers) with a flexible API. If a specific part of your library can be used in different ways, you may want to provide multiple overloaded methods accepting different combinations of parameters.

For example, IOmniPipeline interface from OmniThreadLibrary implements three overloaded Stage functions.

function  Stage(pipelineStage: TPipelineSimpleStageDelegate; 
taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload;
function  Stage(pipelineStage: TPipelineStageDelegate; 
taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload;
function  Stage(pipelineStage: TPipelineStageDelegateEx; 
taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload;

Delphi’s own System.Threading is even worse. In class TParallel, for example, there are 32 overloads of the &For class function. Thirty two! Not only it is hard to select appropriate function; it is also hard to decode something useful from the code completion tip. Check the image below – can you tell which overloaded version I’m trying to call? Me neither!

overloads1

Because of all that, it is usually good to minimize number of overloaded methods. We can do some work by adding default parameters, but sometimes this doesn’t help. Today I’d like to present an alternative solution – configuration records and operator overloading. To simplify things, I’ll present a mostly made-up problem. You can download it from github.