0

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?

3
  • Can't imagine why composition inside a constructor is a bad idea. Is it a Python-only recommendation? Or was it specifically a recommendation related to dependency inversion via injection? If the latter: Dependency inversion (of any kind) is simply a tool you use if you need to disentangle dependencies. As you already understand, that's not needed in your use case.
    – davidbak
    Feb 10, 2021 at 17:46
  • @davidbak It was towards the latter. It was asked why instantiate inside the constructor rather than use constructor injection. My position was that I desired in the interface to be from a single call to the wrapper class since I never needed to worry about testing different variants of 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.
    – pstatix
    Feb 10, 2021 at 17:58
  • Your use case is perfectly fine, and your solution (composition in constructor) is perfectly fine. (According to me.) Remember: SOLID, DRY, and the rest of the zoo of acronyms are guidelines (perhaps upgraded to best practices, whatever that means). They aren't laws. You understand them, then use them when appropriate and not when they're not (though sometimes people will ask you to explain why not and that's when you show them you've thought it through). IMO. (Upgraded these two comments to an answer, see below.)
    – davidbak
    Feb 10, 2021 at 18:07

1 Answer 1

3

Dependency inversion (of any kind) is simply a tool you use if you need to disentangle dependencies. As you already understand, that's not needed in your use case. Construction of an instance with its composed pieces created right then and there is perfectly fine in many (even most) cases.

Remember: SOLID, DRY, and the rest of the zoo of acronyms are guidelines (perhaps upgraded to best practices, whatever that means). They aren't laws. You understand them, then use them when appropriate and not when they're not (though sometimes people will ask you to explain why not and that's when you show them you've thought it through). IMO.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.