I believe the factory method design pattern is appropriate for what I\'m trying to do, but I\'m not sure how much responsibility (knowledge of subclasses it creates) to give it.
I would have a static CanReadFrom
method (or something) in the common ImageReader
interface (not sure if this is possible -- FIXME). Use reflection to grab all implementors and call the function. If one returns true, return an instance of the class.
Factory should have some idea about choosing the actual object to create. For example, WebRequest.Create
method in .NET, should be able to choose between the different protocol clients by checking the protocol part of the Uri
. It doesn't need to parse the whole thing. Just the part required to distinguish which class is going to be responsible for it (in your example, it'll probably be just the file header).
Regarding your question about breaking encapsulation, not really... Most of the time, the factory is hardcoded and already knows about different types of classes and their features. It already depends on the functionality offered by a known set of classes, so you are not adding much to it. You can also encapsulate the detection part of the factory in another helper class that can be used both by the factory and the subclasses (in the sprit of DRY principle).
For extensibility, you can externalize some of these dependencies you mention. Like figuring out what kind of file it is or mapping the file type to a class that handles it. An external registry (i.e., properties file) would store say, GIF -> GifReader, or better GIF -> GifMetadataClass. Then your code could be generic and not have dependencies on all the classes, plus you could extend it in the future, or 3rd parties could extend it.
If this is for windows I would try to guess content type and then use factory. In fact I did this some time ago.
Here is a class to guess content type of a file:
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nexum.Abor.Common
{
/// <summary>
/// This will work only on windows
/// </summary>
public class MimeTypeFinder
{
[DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
private extern static UInt32 FindMimeFromData(
UInt32 pBC,
[MarshalAs(UnmanagedType.LPStr)] String pwzUrl,
[MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
UInt32 cbSize,
[MarshalAs(UnmanagedType.LPStr)]String pwzMimeProposed,
UInt32 dwMimeFlags,
out UInt32 ppwzMimeOut,
UInt32 dwReserverd
);
public string getMimeFromFile(string filename)
{
if (!File.Exists(filename))
throw new FileNotFoundException(filename + " not found");
var buffer = new byte[256];
using (var fs = new FileStream(filename, FileMode.Open))
{
if (fs.Length >= 256)
fs.Read(buffer, 0, 256);
else
fs.Read(buffer, 0, (int)fs.Length);
}
try
{
UInt32 mimetype;
FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
var mimeTypePtr = new IntPtr(mimetype);
var mime = Marshal.PtrToStringUni(mimeTypePtr);
Marshal.FreeCoTaskMem(mimeTypePtr);
return mime;
}
catch (Exception)
{
return "unknown/unknown";
}
}
}
}
Both are valid choices depending on context.
IF you're architecting for extensibility - say a plugin model for different ImageReaders - then your Factory class cannot know about all possible ImageReaders. In that case, you go the ImageReader.CanRead(ImageStream)
route - asking each implementer until you find one that can read it.
Beware, that sometimes ordering does matter here. You may have a GenericImageReader that can handle JPGs, but a Jpeg2000ImageReader that is better at it. Walking the ImageReader implementers will stop at whichever is first. You may want to look at sorting the list of possible ImageReaders if that's a problem.
Otherwise, if the list of ImageReaders is finite and under your control, then you can go the more traditional Factory approach. In that case, the Factory decides what to create. It's already coupled to the concrete implementations of ImageReader by the ctor, so adding rules for each ImageReader doesn't increase coupling. If the logic to pick a ImageReader is mainly in the ImageReader itself, then to avoid duplication of code, you can still go the ImageReader.CanRead(ImageStream)
route - but it could just be hardcoded which types you walk.