Шаблон фабричного дизайна (нуждающийся в критике) - PullRequest
6 голосов
/ 04 ноября 2010

Я собираю объяснение и пример кода этого шаблона проектирования, пытаясь помочь окружающим понять его (вместе с тем, помогая себе освоить шаблон).

Что я ищу, так это мнения и / или критика моего объяснения и примера кода ... спасибо!

Что такое заводской шаблон? В фабричном шаблоне используется специальный выделенный «объект-создатель объекта» для управления созданием - и в большинстве случаев - созданием объектов, аналогично реальной фабрике.

Пример из реального мира
Представьте, что автомобильный завод является создателем различных типов автомобилей. Одна из сборочных линий на этом автомобильном заводе может производить грузовик однажды, но в другой день может быть переоборудован для производства автомобилей. Скажем, дилерский центр размещает заказ на 10 автомобилей в назначенном им отделе обработки счетов. Затем этот отдел использует определенный завод и заказывает 10 автомобилей. Обработчики аккаунтов не занимаются изготовлением автомобилей (представьте себе плохие результаты), они работают только с конечным продуктом, гарантируя, что автосалон получит их автомобили.

Новая модель того же автомобиля выйдет в следующем году, и начнут поступать заказы. Обработчики счетов (все еще не связанные с производством автомобиля) размещают заказы, но теперь автомобиль, который они получают, отличается, сборка Метод или даже может быть, фабрика в целом может отличаться, но обработчики учетных записей не должны беспокоиться об этом. Еще одна мысль: заводские сборщики транспортных средств могут точно знать, какое действие предпринять, если определенный обработчик счетов размещает заказ (т. Е. Обработчик счетов X размещает заказ, заводской сборщик знает, что для обработчика счетов X они производят 10 транспортных средств типа Y). ). Другой вариант может заключаться в том, что обработчик аккаунта сообщает ассемблеру, какой именно тип транспортного средства производить.

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

Возвращаясь к ООП
Фабрика объектов в качестве шаблона проектирования, применяемого для разработки программного обеспечения, аналогична приведенному выше примеру в концепции… Фабрика производит различные типы других объектов, вы можете использовать сборочную линию (сборщик объектов), которая создает определенный тип объекта, возвращаемый в определенным образом. Ассемблер может либо проверить запрашивающий клиент и обработчик, либо клиент может сказать ассемблеру, какой объект ему требуется. Теперь ... вы работаете над проектом и создаете фабрику объектов и различные ассемблеры, позже в дальнейшем в проекте требования меняются незначительно, теперь вас просят изменить содержимое объекта и то, как его клиенты обрабатывают этот объект. Поскольку вы использовали фабричный шаблон, это простое изменение, и в одном месте вы можете изменить или добавить объекты, которые производит фабрика, и изменить формат, в котором ассемблеры выкладывают содержимое объекта.

Неудачный способ сделать это был бы без фабричного метода, создания экземпляра каждого экземпляра объекта и форматирования содержимого объекта в самих клиентах ... скажем, вы использовали этот конкретный объект в 20 клиентах. Теперь вы должны обратиться к каждому клиенту, изменить каждый из экземпляров и форматов объекта ... что за пустая трата времени ... Будьте ленивы ... сделайте это правильно с первого раза, чтобы сэкономить время себе (и другим) и усилия позже.

Пример кода (C #)
Ниже приведен пример использования фабрики для производства продуктов питания и различных пищевых объектов

Factory module
    public enum FoodType
    {
    //enumerated foodtype value, if client wants to specify type of object, coupling still occurs
        Hamburger, Pizza, HotDog
    }
 
    /// <summary>
    /// Object to be overridden (logical)
    /// </summary>
    public abstract class Food
    {
        public abstract double FoodPrice { get; }
    }
 
    /// <summary>
    /// Factory object to be overridden (logical)
    /// </summary>
    public abstract class FoodFactory
    {
        public abstract Food CreateFood(FoodType type);
    }
 
    //-------------------------------------------------------------------------
    #region various food objects
    class Hamburger : Food
    {
        double _foodPrice = 3.59;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class Pizza : Food
    {
        double _foodPrice = 2.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class HotDog : Food
    {
        double _foodPrice = 1.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
    #endregion
    //--------------------------------------------------------------------------
 
 
    /// <summary>
    /// Physical factory
    /// </summary>
    public class ConcreteFoodFactory : FoodFactory
    {
        public override Food CreateFood(FoodType foodType)
        {
            switch (foodType)
            {
                case FoodType.Hamburger:
                    return new Hamburger();
                    break;
                case FoodType.HotDog:
                    return new HotDog();
                    break;
                case FoodType.Pizza:
                    return new Pizza();
                    break;
                default:
                    return null;
                    break;
            }
        }
    }
 
    /// <summary>
    /// Assemblers
    /// </summary>
    public class FoodAssembler
    {
        public string AssembleFoodAsString(object sender, FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            if (sender.GetType().Name == "default_aspx")
            {
                return string.Format("The price for the hamburger is: ${0}", food.FoodPrice.ToString());
            }
            else
            {
                return food.FoodPrice.ToString();
            }  
        }
 
        public Food AssembleFoodObject(FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            return food;
        }
    }

Calling factory
FoodFactory factory = new ConcreteFoodFactory(); //create an instance of the factoryenter code here
lblUser.Text = new FoodAssembler().AssembleFoodAsString(this, factory); //call the assembler which formats for string output

Object o = new FoodAssembler().AssembleFoodObject(factory); //example: instantiating anon object, initialized with created food object

Ответы [ 3 ]

14 голосов
/ 04 ноября 2010

Извините.Это довольно негибкая фабрика.Отражение может дать немного POWWAH !!

public interface IFood
{
    bool IsTasty { get; }
}
public class Hamburger : IFood
{
    public bool IsTasty {get{ return true;}}
}
public class PeaSoup : IFood
{
    public bool IsTasty { get { return false; } }
}

public class FoodFactory
{
    private Dictionary<string, Type> _foundFoodTypes =
        new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Scan all specified assemblies after food.
    /// </summary>
    public void ScanForFood(params Assembly[] assemblies)
    {
        var foodType = typeof (IFood);
        foreach (var assembly in assemblies)
        {
            foreach (var type in assembly.GetTypes())
            {
                if (!foodType.IsAssignableFrom(type) || type.IsAbstract || type.IsInterface)
                    continue;
                _foundFoodTypes.Add(type.Name, type);
            }
        }

    }

    /// <summary>
    /// Create some food!
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public IFood Create(string name)
    {
        Type type;
        if (!_foundFoodTypes.TryGetValue(name, out type))
            throw new ArgumentException("Failed to find food named '" + name + "'.");

        return (IFood)Activator.CreateInstance(type);
    }

}

Использование:

var factory = new FoodFactory();
factory.ScanForFood(Assembly.GetExecutingAssembly());

Console.WriteLine("Is a hamburger tasty? " + factory.Create("Hamburger").IsTasty);

Редактировать, отзыв о вашем коде:

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

Конечно, это все же немного лучше, чем создание типов напрямую.

Вторая проблема с вашим кодом заключается в том, что вы используете оператор switch (но это лучший способ сделать это, если перечисление является требованием).Лучше иметь возможность каким-либо образом регистрировать все разные классы.Либо из файла конфигурации, либо путем разрешения самим фактическим реализациям (например, классу Гамбургера) зарегистрироваться.Это требует, чтобы фабрика следовала шаблону синглтона.

Здесь приходит Reflection на помощь.Отражение позволяет вам пройти через все типы в DLL и EXE.Таким образом, мы можем искать все классы, которые реализуют наш интерфейс и, следовательно, иметь возможность создавать словарь для всех классов.

3 голосов
/ 04 ноября 2010

Я думаю, что ваше объяснение, включая пример из реального мира, хорошо. Тем не менее, я не думаю, что ваш пример кода показывает реальные преимущества шаблона.

Некоторые возможные изменения:

  • Я бы не использовал enum параллельно типам. Похоже, вам нужно обновлять перечисление каждый раз, когда добавляется тип. Может быть более подходящим передать System.Type. Тогда вы даже можете сделать фабрику универсальной с помощью аргумента шаблона.
  • Я думаю, что шаблон более «впечатляющий», если вы используете его для создания чего-то вроде аппаратного интерфейса. Тогда у вас будет «AbstractNetworkDevice», и все ваши абоненты не будут знать, какая у вас аппаратная настройка. Но фабрика может создать «TcpNetworkDevice» или «SerialNetworkDevice» или что-либо еще на основе какой-либо конфигурации, которая была сделана при запуске.
0 голосов
/ 04 ноября 2010

Я бы предложил вам использовать интерфейсы вместо абстрактных классов / наследования. В остальном все выглядит нормально.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...