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

Design patterns 工厂如何知道要创建哪种类型的对象?,design-patterns,factory,Design Patterns,Factory,我相信工厂方法设计模式适合我正在尝试做的事情,但我不确定赋予它多少责任(它创建的子类的知识)。使用at Wikipedia的示例几乎准确地描述了我所处的情况: public class ImageReaderFactory { public static ImageReader getImageReader( InputStream is ) { int imageType = figureOutImageType( is ); switch

我相信工厂方法设计模式适合我正在尝试做的事情,但我不确定赋予它多少责任(它创建的子类的知识)。使用at Wikipedia的示例几乎准确地描述了我所处的情况:

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.
        }
    }
}
我的问题是,
figureOptimageType
函数看起来像什么?在这个特定的示例中,我假设它检查
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.
}
让工厂知道如何解析图像似乎打破了封装,让子类决定应该创建哪个是工厂方法设计模式的一部分。然而,
figureOptimageType
函数似乎只是添加了一些冗余代码,因为为什么不让每个子类在
getImageReader
函数中对
InputStream
执行检查并跳过切换情况呢

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


谢谢

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


关于你关于破坏封装的问题,不是真的。。。大多数时候,工厂是硬编码的,并且已经知道不同类型的类及其特性。它已经依赖于一组已知的类所提供的功能,因此您不会给它添加太多内容。您还可以将工厂的检测部分封装在另一个helper类中,工厂和子类都可以使用该类(按照DRY原则)。

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

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

如果这是针对windows的,我会尝试猜测内容类型,然后使用factory。事实上,我很久以前就这样做了

下面是一个用于猜测文件内容类型的类:

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";
            }
        }
    }
}
使用系统;
使用System.IO;
使用System.Runtime.InteropServices;
命名空间Nexum.Abor.Common
{
/// 
///这将只在windows上工作
/// 
公共类MimeTypeFinder
{
[DllImport(@“urlmon.dll”,CharSet=CharSet.Auto)]
私有外部静态UInt32 FindMimeFromData(
UInt32中国人民银行,
[Marshallas(UnmanagedType.LPStr)]字符串pwzUrl,
[Marshallas(UnmanagedType.LPArray)]字节[]pBuffer,
UInt32 cbSize,
[Marshallas(UnmanagedType.LPStr)]字符串pwzmime,
UInt32 dwMimeFlags,
out UInt32 ppwzMimeOut,
UInt32数据保留
);
公共字符串getMimeFromFile(字符串文件名)
{
如果(!File.Exists(filename))
抛出新的FileNotFoundException(文件名+“未找到”);
var buffer=新字节[256];
使用(var fs=new FileStream(文件名,FileMode.Open))
{
如果(fs.Length>=256)
fs.Read(缓冲区,0,256);
其他的
fs.Read(缓冲区,0,(int)fs.Length);
}
尝试
{
UInt32模拟型;
FindMimeFromData(0,null,buffer,256,null,0,out-mimetype,0);
var mimeTypePtr=新的IntPtr(mimetype);
var mime=Marshal.PtrToStringUni(mimeTypePtr);
FreeCoTaskMem元帅(mimetypter);
返回mime;
}
捕获(例外)
{
返回“未知/未知”;
}
}
}
}

根据上下文,两者都是有效的选择

如果您是为可扩展性而设计的,比如为不同的ImageReader设计一个插件模型,那么您的工厂类就不能知道所有可能的ImageReader。在这种情况下,您可以选择
ImageReader.CanRead(ImageStream)
路线,询问每个实现者,直到找到一个可以读取它的实现者

注意,有时在这里点餐的确很重要。您可能有一个可以处理JPG的GenericImageReader,但Jpeg2000ImageReader在这方面做得更好。在ImageReader实现程序中行走,无论哪一个先到,都将停止。如果这是一个问题,您可能需要查看可能的ImageReader列表的排序

否则,如果ImageReader的列表是有限的并且在您的控制下,那么您可以使用更传统的工厂方法。在这种情况下,工厂决定什么