Подтвердить что ты не робот

Как factory знает, какой тип объекта создать?

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

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.
}

Кажется, что factory умеет разбирать инкапсуляцию разрывов изображений и позволяет подклассам решать, какой из них нужно создать, является частью шаблона проектирования метода factory. Тем не менее, также кажется, что функция figureOutImageType просто добавляет некоторый избыточный код, потому что почему бы не просто выполнить каждый подкласс в InputStream в функции getImageReader и пропустить регистр коммутатора?

У меня не было опыта использования фабрик раньше, и я надеялся получить некоторое представление от некоторых людей, которые использовали их в прошлом, чтобы наилучшим образом справиться с этой проблемой. Можно ли знать factory о внутренних функциях своих подклассов или они должны нести ответственность за то, чтобы сообщить factory, что создавать и как вы его организуете?

Спасибо!

4b9b3361

Ответ 1

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

Что касается вашего вопроса об устранении инкапсуляции, на самом деле... В большинстве случаев factory жестко запрограммирован и уже знает о различных типах классов и их функциях. Это уже зависит от функциональности, предлагаемой известным набором классов, поэтому вы не много добавляете к ней. Вы также можете инкапсулировать часть обнаружения factory в другом вспомогательном классе, который может использоваться как factory, так и подклассами (в принципе sprit of DRY).

Ответ 2

Оба варианта действительны в зависимости от контекста.

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

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

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

Ответ 3

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

Ответ 4

Если это для окон, я бы попытался угадать тип контента, а затем использовать 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";
            }
        }
    }
}

Ответ 5

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