Object-oriented basics: single object or collection scope?

Here is a contrived example of a common SOLID violation you might see. Can you spot it?

class Mp3Encoder : IMp3Encoder{    public void Encode(IEnumerable<string> wavFiles)    {        foreach (var wavFile in wavFiles)        {            var outputFile = /* create output file */;                        while (/* blocks remaining... */)            {                var buffer = /* read block */;                var encoded = /* encode wav block as MP3 */;                /* write block to output file */;            }            /* write ID3 trailing header */;        }    }}

Except in trivially simple cases, there should always be a class boundary when shifting context from coordinating a collection versus performing actions on a single object.

The class above is violating this rule — it knows how to perform collection-level responsibilities as well as single-object responsibilities. It needs to be broken into two classes; one for encoding a single file and one for coordinating the group.

This rule is a form of the Single Responsibility Principle. For example:

Collection-scoped class responsibilities
  • Coordinating ‘before all’ and ‘after all’ actions
  • Looping through items
  • Maintaining shared state (counting, accumulating etc)
Single object-scoped class responsibilities
  • Coordinating ‘before each’ and ‘after each’ actions
  • Performing actions on item

If you ignore this collection-vs-single-object contextual boundary, your classes will become messes of nested procedural code — especially when different behaviour is required for each item in the collection. Your classes will be that much harder to unit test, and you won’t easily be able to re-use them in single-object scenarios.