Лучший способ построить фабрику - PullRequest
6 голосов
/ 31 августа 2011

В последнее время я много читал о фабричном образце. Я пытаюсь выяснить, как лучше всего это осуществить. В книге Шаблоны и практика гибких принципов C # рекомендуется создать Фабрику следующим образом:

public class ShapeFactoryImplementation : ShapeFactory {
    public Shape Make(string name) {
        if (name.Equals("Circle"))
            return new Circle();
        else if (name.Equals("Square"))
            return new Square();
        else
            throw new Exception("ShapeFactory cannot create: {0}", name);
        }
    }

Вместо ...

public class ShapeFactoryImplementation : ShapeFactory {
    public Shape MakeCircle() {
        return new Circle();
    }

    public Shape MakeSquare() {
        return new Square();
    }
}

Пожалуйста, скажите мне, что вы думаете? Или, может быть, есть лучший способ реализовать фабричный шаблон?

Ответы [ 6 ]

3 голосов
/ 31 августа 2011

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

    public struct ShapeCreationSettings
    {
        public Predicate<string> Predicate;
        public Func<IShapeCreationParams, Shape> Creator;
    }

    public interface IShapeCreationParams
    {}

    public struct CircleCreationParams : IShapeCreationParams
    {
        public CircleCreationParams(int r) : this()
        {
            R = r;
        }

        public int R  { get; private set; }
    }

    public struct SquareCreationParams : IShapeCreationParams
    {
        public SquareCreationParams(int a) : this()
        {
            A = a;
        }

        public int A { get; private set; }

    }

    public class ShapeFactory : IShapeFactory
    {
        protected static List<ShapeCreationSettings> settings = new List<ShapeCreationSettings>();

        static ShapeFactory()
        {
            settings.Add(new ShapeCreationSettings
            {
                Predicate = t => t.Equals("Circle"),
                Creator = p => new Circle(((CircleCreationParams) p).R)
            });
            settings.Add(new ShapeCreationSettings
            {
                Predicate = t => t.Equals("Square"),
                Creator = p => new Square(((SquareCreationParams)p).A)
            });
        }

        public Shape Create(string name, IShapeCreationParams p)
        {
            return settings.FirstOrDefault(s => s.Predicate(name)).Creator(p);
        }
    } 

. В качестве примера настройки создания задаются с использованием статического конструктора, который все еще требует изменения класса в реальном сценарии.Я бы добавил для этого метод AddSettings или использовал бы контейнер IoC (если бы он предоставлял такую ​​функциональность).

Делая это, вы получаете преимущество слабой связи (вы можете изменить способ создания объектов на самом деле).в отдельности) и может добавлять новые типы фигур в любое время, даже не восстанавливая фабрику.Кроме того, вы можете предоставлять конструкторам параметры, специфичные для формы.

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

3 голосов
/ 31 августа 2011

В первой версии интерфейс ShapeFactoryImplementation остается прежним.Во втором каждый раз, когда вы добавляете новый метод, у вас появляется новый интерфейс.

2 голосов
/ 31 августа 2011

Если вы считаете, что использование строк в первом методе для выбора нужной фигуры ужасно, почему бы не использовать перечисления?

2 голосов
/ 31 августа 2011

Суть фабричного шаблона состоит в том, чтобы отсоединить клиента от создания объектов.

Во втором примере клиент жестко закодировал форму для создания, такую ​​как MakeCircleи, следовательно, тесно связывает создание объекта и клиента.

1 голос
/ 31 августа 2011

То, что вы перечислите здесь, называется простой фабрикой. Первый лучше, потому что создание в основном определяется параметром, а не методом.

На самом деле шаблон фабричного метода является официальным, скажем, есть IFoo,и есть две реализации FooA, FooB следующим образом:

public interface IFoo
{
}
public class FooA : IFoo
{
}
public class FooB : IFoo
{
}

Тогда возникает проблема при использовании IFoo в клиентском приложении - Как создать экземпляр реализации?Связь существует, потому что клиент зависит от создания FooA / FooB, даже FooC в функции, поэтому нам нужно разъединение.Мы переносим ответственность за создание экземпляра реализации IFoo на другой интерфейс:

public interface IFooFactory
{
    IFoo CreateFoo();
}

, чтобы создать FooA, нам нужно:

public class FooAFactory : IFooFactory
{
    public IFoo CreateFoo()
    {
         return new FooA();
    }
}

, чтобы создать FooB, нам нужно:

public class FooBFactory : IFooFactory
{
    public IFoo CreateFoo()
    {
         return new FooB();
    }
}

Эффект здесь заключается в том, что при наличии FooC нам не нужно изменять существующую Factory, нам просто нужно реализовать другую FooCFactory.

0 голосов
/ 31 августа 2011

Лично мне нравится первый. Это позволяет легко расширить список создания. С другой стороны, второй опирается на различные методы для создания новых объектов. Если вы добавите метод для новой фигуры, это нарушит ABI. Вам следует изменить эти методы на защищенные, чтобы новые добавления сохраняли открытый интерфейс, а производные классы могли по-прежнему изменять способ создания объектов.

...