I have 2 classes that independently operate on specific file formats: class FileWriterA
and class FileWriterB
.
Each class has some shared methods (e.g., __enter__
, __exit__
, __str__
, closed
, is_closed
, etc.) that when used independently are of no concern. There is, however, a relationship between the two. When used together on a particular dataset, the file formats are used together to represent a collection. In other words, some application that opens the .a
file is looking for its associated .b
file, or vice versa.
If one were to want to develop this relationship in a program, they could be used in a context such as:
with FileWriterA(args) as fwa, FileWriterB(args) as fwb:
# do stuff with appropriate writer
This however does not work because of imposed limits on the formats when used as a collection. For example, when used as a collection, the .a
file has a file size limit that may be much smaller than the file size limit imposed on the .b
format itself; conversely, the .b
file size limit may be reached before that of the .a
file size limit.
To allow this I wrote another class: class FileWriterC
. It has a constructor like so:
class FileWriterC:
def __init__(x, y, z):
self.fwa = FileWriterA(x, y)
self.fwb = FileWriterB(x, z)
I've chosen composition because the collection has-a FileWriterN
for each file type. However, there are a number of articles citing that directly instantiating a sub-object inside the constructor is bad and should be avoided. I agree that injection has its merit, but in this case I believe that injection serves no purpose as the format is unlikely to change and FileWriterC
is simply a wrapper, combining operations; it does not depend on self.fwa
or self.fwb
as it controls their life cycle. This leads to the first question:
How is composition achieved without direct instantiation (i.e. new Class())?
The only alternative I see is aggregation through injection:
fwc = FileWriterC()
fwc.setFWA(FileWriterA(x, y))
fwc.setFWB(FileWriterB(x, z))
This seems incredibly verbose, although flexible for testing in the unlikely event other implementations of FileWriterA
or FileWriterB
were developed. Further, composition made sense as the lifecycle of these objects
Which ultimately leads to the opinion filled:
When would it be fine to allow direct instantiation in a constructor?
FileWriterN
. The instances, when used as a collection, have a shared lifecycle and don't exist when the wrapper is destroyed. To me this made sense, but was unsure if it was a qualified exception to the D in SOLID.