工厂如何知道要创建哪种类型的对象?

时间:2009-03-31 23:23:07

标签: design-patterns factory

我认为工厂方法设计模式适合我正在尝试做的事情,但我不确定要给它多少责任(它创建的子类的知识)。在维基百科上使用factory method pattern的例子描述了我几乎完全正确的情况:

public class ImageReaderFactory 
{
    public static ImageReader getImageReader( InputStream is ) 
    {
        int imageType = figureOutImageType( is );

        switch( imageType ) 
        {
            case ImageReaderFactory.GIF:
                return new GifReader( is );
            case ImageReaderFactory.JPEG:
                return new JpegReader( is );
            // etc.
        }
    }
}

我的问题是,figureOutImageType函数是什么样的?在这个具体的例子中,我假设它检查InputStream中的文件头以确定数据所在的图像格式。我想知道ImageReaderFactory本身是否知道如何解析文件头并确定文件类型是GIF,JPEG等,还是它调用每个Reader类中的一个函数,让它知道它是什么类型的图像。这样的事情,也许是:

int figureOutImageType(InputStream is)
{
    if(GifReader.isGIF(is))
        return ImageReaderFactory.GIF;
    else if(JpegReader.isJPEG(is))
        return ImageReaderFactory.JPEG;
    // etc.
}

似乎让工厂知道如何解析图像破解封装,并让子类决定应该创建哪一个是工厂方法设计模式的一部分。然而,似乎figureOutImageType函数只是添加了一些冗余代码,因为为什么不让每个子类对InputStream函数中的getImageReader执行检查并跳过切换案例?

我以前没有任何使用过工厂的经验,我希望从过去使用它们的一些人那里得到一些见解,以解决这个问题的最佳方法。是否可以让工厂知道其子类的内部工作原理,或者他们是否应该负责让工厂知道要创建哪个,以及如何组织它们?

谢谢!

5 个答案:

答案 0 :(得分:6)

工厂应该对选择要创建的实际对象有所了解。例如,.NET中的WebRequest.Create方法应该能够通过检查Uri的协议部分来在不同的协议客户端之间进行选择。它不需要解析整个事情。只需要区分哪个类将负责它(在您的示例中,它可能只是文件头)。

关于打破封装的问题,不是真的...大多数情况下,工厂是硬编码的,并且已经知道不同类型的类及其功能。它已经取决于已知类集提供的功能,因此您不会添加太多内容。您还可以将工厂的检测部分封装在另一个辅助类中,该辅助类可以由工厂和子类使用(在DRY原则的精神中)。

答案 1 :(得分:2)

两者都是有效的选择,具体取决于上下文。

如果您正在构建可扩展性 - 比如针对不同ImageReader的插件模型 - 那么您的Factory类无法了解所有可能的ImageReader。在这种情况下,你走ImageReader.CanRead(ImageStream)路线 - 询问每个实施者,直到找到一个可以读取它的人。

请注意,有时订购在这里很重要。你可能有一个可以处理JPG的GenericImageReader,但是它有一个更好的Jpeg2000ImageReader。走ImageReader实施者将以第一个为准停止。如果这是一个问题,您可能需要查看可能的ImageReader列表的排序。

否则,如果ImageReader列表是有限的并且在您的控制之下,那么您可以采用更传统的Factory方法。在这种情况下,工厂决定要创建什么。它已经由ctor耦合到ImageReader的具体实现,因此为每个ImageReader添加规则不会增加耦合。如果选择ImageReader的逻辑主要在ImageReader本身,那么为了避免重复代码,您仍然可以使用ImageReader.CanRead(ImageStream)路由 - 但它可能只是硬编码您走的类型。

答案 2 :(得分:1)

对于可扩展性,您可以外部化您提到的某些依赖项。就像弄清楚它是什么类型的文件或将文件类型映射到处理它的类。外部注册表(即属性文件)将存储,例如,GIF - > GifReader,或更好的GIF - > GifMetadataClass。那么你的代码可能是通用的,并且没有对所有类的依赖,而且你可以在将来扩展它,或者第三方可以扩展它。

答案 3 :(得分:1)

如果这是针对Windows的,我会尝试猜测内容类型,然后使用工厂。事实上我前段时间做过这个。

这是一个猜测文件内容类型的类:

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";
            }
        }
    }
}

答案 4 :(得分:0)

我会在公共CanReadFrom接口中使用静态ImageReader方法(或其他东西)(不确定这是否可行 - FIXME)。使用反射来获取所有实现者并调用该函数。如果返回true,则返回该类的实例。