1

I am reading Mark Seemann's Dependency injection book and I came across bastard injection anti-pattern which I don't fully understand. I would like to get some guidance how to take apart foreign defaults from local. If the only requirement is that foreign default comes from other assembly?

What if I have design like this (it's suppose to be a library class, so I am not allowed to use IoC container):

namespace DocumentReader
{
public class DocumentReader : ISmartDocumentReader
{
    private readonly ISet<IDocumentReader> documentReaders;

    public DocumentReader(ISet<IDocumentReader> documentReaders)
    {
        this.documentReaders = documentReaders;
    }

    public DocumentReader()
        : this(new HashSet<IDocumentReader>(new IDocumentReader[] { new TxtReader(), new PdfReader(), new DocReader() }))
    {

    }

    public string Read(Stream stream, string fileType)
    {
        var reader = documentReaders.SingleOrDefault(r => r.SupportedFileType == fileType);
        if(reader == null)
            throw new ArgumentException("Not supported file type");

        return reader.Read(stream, fileType);
    }
}
}

after reading patterns chapter in the book I am in doubt if this is correct design, the goal is that this class will be the library's public API. All specific document readers (pdf, txt, doc) are all within the same assembly, but they are wrappers over some external tools or libraries like pdf box for pdfReader.

I found this question interesting Is there an alternative to bastard injection? (AKA poor man's injection via default constructor), but it doesn't dispel all my doubts. What if someday someone would add new file reader. If the default constructor is used then it won't be possible to let document reader know about the new specific reader. Should I delete the default constructor and force library users to wire (pick interesting ones) dependencies at application's composition root using IoC container?

Community
  • 1
  • 1
0lukasz0
  • 3,155
  • 1
  • 24
  • 40
  • 1
    It may be better to remove the default constructor and instead provide a Facade for consumers: http://stackoverflow.com/questions/2045904/dependency-inject-di-friendly-library/2047657#2047657 – Mark Seemann Mar 03 '12 at 18:33
  • 1
    A bit of nitpick: from the sample code, you don't *need* `ISet` - you only need `IEnumerable`, so the constructor should take only that as an argument. That will make it more flexible, because more clients will be able to use it. – Mark Seemann Mar 03 '12 at 18:34
  • @Mark Seemann: why it is so with IEnumerable? With ISet I am telling my library users that I want only one copy of each type reader, what am I missing here? Returning to local default question: if my local default has some dependencies on external libraries/tools is it still considered local? In other words is this relation transitive? – 0lukasz0 Mar 04 '12 at 16:26
  • In your implementation, you are only using methods from `IEnumerable` so why require anything else? And why do you *require* only one matching reader? If you changed the implementation to use FirstOrDefault, it would be able to accept a sequence with multiple matching readers without throwing an exception - it would even be able to accept an infinite sequence. Both the Liskov Substitution Principle and Postel's Law are in play here. – Mark Seemann Mar 04 '12 at 16:54
  • And for the other question: yes, the relation is transitive. If you can't compile a default without referencing a foreign library, the default is a foreign default, not a local default. You may find this answer illustrative: http://stackoverflow.com/questions/9501604/ioc-di-why-do-i-have-to-reference-all-layers-assemblies-in-entry-application/9503612#9503612 – Mark Seemann Mar 04 '12 at 16:56

0 Answers0