16

我相信工厂方法设计模式适合我正在尝试做的事情,但我不确定赋予它多少责任(它创建的子类的知识)。在 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.
        }
    }
}

我的问题是,这个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也只是添加了一些冗余代码,因为为什么不让每个子类InputStreamgetImageReader函数中的 进行检查并跳过 switch 案例呢?

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

谢谢!

4

5 回答 5

6

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

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

于 2009-03-31T23:26:02.143 回答
2

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

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

请注意,有时订购在这里很重要。您可能有一个可以处理 JPG 的 GenericImageReader,但 Jpeg2000ImageReader 更胜一筹。遍历 ImageReader 实现者将停在先到者处。如果这是一个问题,您可能希望查看对可能的 ImageReaders 列表进行排序。

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

于 2009-04-01T00:11:35.157 回答
1

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

于 2009-03-31T23:27:52.797 回答
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";
            }
        }
    }
}
于 2009-03-31T23:32:35.967 回答
0

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

于 2009-03-31T23:27:37.133 回答