Как фабрика узнает, какой тип объекта создать? - PullRequest
16 голосов
/ 01 апреля 2009

Я считаю, что шаблон проектирования фабричного метода подходит для того, что я пытаюсь сделать, но я не уверен, сколько ответственности (знание создаваемых им подклассов) ему дать. Пример использования шаблона фабричного метода в Википедии описывает ситуацию, в которой я оказался почти точно:

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 ]

6 голосов
/ 01 апреля 2009

Фабрика должна иметь представление о выборе реального объекта для создания. Например, метод WebRequest.Create в .NET должен иметь возможность выбирать между различными клиентами протокола, проверяя часть протокола Uri. Не нужно разбирать все это. Только та часть, которая требуется для определения того, какой класс будет отвечать за него (в вашем примере это, вероятно, будет просто заголовок файла).

Относительно вашего вопроса о нарушении инкапсуляции, не совсем ... В большинстве случаев фабрика жестко запрограммирована и уже знает о различных типах классов и их функциях. Это уже зависит от функциональности, предлагаемой известным набором классов, поэтому вы не добавляете к ней много. Вы также можете инкапсулировать часть обнаружения фабрики в другом вспомогательном классе, который может использоваться как фабрикой, так и подклассами (в духе принципа СУХОЙ).

2 голосов
/ 01 апреля 2009

Оба являются допустимыми вариантами в зависимости от контекста.

Если вы разрабатываете архитектуру для расширяемости, скажем, модели плагинов для разных ImageReaders, то ваш класс Factory не может знать обо всех возможных ImageReaders. В этом случае вы идете по маршруту ImageReader.CanRead(ImageStream) - спрашиваете каждого исполнителя, пока не найдете тот, который может его прочитать.

Осторожно, иногда здесь важен порядок. У вас может быть GenericImageReader, который может обрабатывать JPG, но Jpeg2000ImageReader лучше в этом. Ходьба разработчиков ImageReader остановится в зависимости от того, что произойдет первым. Вы можете захотеть взглянуть на сортировку списка возможных ImageReader-ов, если это проблема.

В противном случае, если список ImageReaders конечен и находится под вашим контролем, вы можете перейти к более традиционному подходу Factory. В этом случае Фабрика решает, что создать. Он уже связан с конкретными реализациями ImageReader ctor, поэтому добавление правил для каждого ImageReader не увеличивает сцепление. Если логика выбора ImageReader находится в основном в самом ImageReader, то во избежание дублирования кода вы все равно можете пойти по маршруту ImageReader.CanRead(ImageStream) - но это может быть просто жестко задано, какие типы вы проходите.

1 голос
/ 01 апреля 2009

Если это для окон, я бы попытался угадать тип контента, а затем использовать фабрику. На самом деле я сделал это некоторое время назад.

Вот класс для угадывания типа содержимого файла:

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";
            }
        }
    }
}
1 голос
/ 01 апреля 2009

Для расширяемости вы можете вывести некоторые из упомянутых вами зависимостей. Например, выяснить, что это за файл, или сопоставить тип файла с классом, который его обрабатывает. Внешний реестр (то есть файл свойств) будет хранить, скажем, GIF -> GifReader или лучше GIF -> GifMetadataClass. Тогда ваш код может быть универсальным и не иметь зависимостей от всех классов, плюс вы можете расширить его в будущем, или третьи лица могут расширить его.

0 голосов
/ 01 апреля 2009

Я бы использовал статический метод CanReadFrom (или что-то еще) в общем интерфейсе ImageReader (не уверен, если это возможно - FIXME). Используйте отражение, чтобы захватить все разработчики и вызвать функцию. Если возвращается true, вернуть экземпляр класса.

...