This is not only about POLA, but also about preventing invalid state as a possible source of bugs.
Let's see how we can provide some constraints to your example without providing a concrete implementation:
First step: Don't allow anything to be called, before a file was opened.
CreateDataFileInterface
+ OpenFile(filename : string) : DataFileInterface
DataFileInterface
+ SetHeaderString(header : string) : void
+ WriteDataLine(data : string) : void
+ SetTrailerString(trailer : string) : void
+ Close() : void
Now it should be obvious that CreateDataFileInterface.OpenFile
must be called to retrieve a DataFileInterface
instance, where the actual data can be written.
Second step: Make sure, headers and trailers are always set.
CreateDataFileInterface
+ OpenFile(filename : string, header: string, trailer : string) : DataFileInterface
DataFileInterface
+ WriteDataLine(data : string) : void
+ Close() : void
Now you have to provide all required parameters upfront to get a DataFileInterface
: filename, header and trailer. If the trailer string is not available until all lines are written, you could also move this parameter to Close()
(possibly renaming the method to WriteTrailerAndClose()
) so that the file at least cannot be finished without a trailer string.
To reply to the comment:
I like separation of the interface. But I'm inclined to think that
your suggestion about enforcement (e.g. WriteTrailerAndClose()) is
verging on a violation of SRP. (This is something that I have
struggled with on a number of occasions, but your suggestion seems to
be a possible example.) How would you respond?
True. I didn't want to concentrate more on the example than necessary to make my point, but it's a good question. In this case I think I would call it Finalize(trailer)
and argue that it does not do too much. Writing the trailer and closing are mere implementation details. But if you disagree or have a similar situation where it's different, here's a possible solution:
CreateDataFileInterface
+ OpenFile(filename : string, header : string) : IncompleteDataFileInterface
IncompleteDataFileInterface
+ WriteDataLine(data : string) : void
+ FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface
CompleteDataFileInterface
+ Close()
I wouldn't actually do it for this example but it shows how to carry through the technique consequently.
By the way, I assumed that the methods actually must be called in this order, for example to sequentially write many lines. If this is not required, I would always prefer a builder, as suggested by Ben Cottrel.