我认为工厂方法设计模式适合我正在尝试做的事情,但我不确定要给它多少责任(它创建的子类的知识)。在维基百科上使用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
执行检查并跳过切换案例?
我以前没有任何使用过工厂的经验,我希望从过去使用它们的一些人那里得到一些见解,以解决这个问题的最佳方法。是否可以让工厂知道其子类的内部工作原理,或者他们是否应该负责让工厂知道要创建哪个,以及如何组织它们?
谢谢!
答案 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,则返回该类的实例。