Использование заводского образца - PullRequest
2 голосов
/ 30 октября 2011

Каким образом использование Фабрики лучше (правильнее)?

IPacket info = PacketFactory.CreatePacketObject(PacketType.Info, currentUser, DateTime.Now, " disconnected");

или я должен выбросить второй метод в PacketFactory и использовать этот?

IPacket info = PacketFactory.CreatePacketObject(PacketType.Info);
            info.CreationTime = DateTime.Now;
            info.Creator = currentUser;
            info.Data = " disconnected";

или, может быть, какой-то другой?

PacketFactory код:

* * 1010

Ответы [ 3 ]

8 голосов
/ 30 октября 2011

Перед применением паттерна у вас должно быть четкое представление о том, что вы получаете, делая это, и в этом случае я не вижу, что введение статической «Фабрики» вам что-то приносит. Взгляните на это с точки зрения клиента PacketFactory: снизило ли оно взаимодействие между клиентом и различными конкретными разработчиками IPacket? Я бы не стал спорить, поскольку клиент уже должен знать, какой тип IPacket он хочет, указав значение перечисления: PacketType.Info, PacketType.Message или PacketType.Log. Чем это отличается от того, что клиент знает о классах Info, Message и Log? Поскольку «Фабрика» является статическим классом, клиент точно так же связан с типом IPacket, который возвращается, как если бы он просто вызывал конструктор соответствующего IPacket разработчика, поскольку вам пришлось бы изменить клиента в для того, чтобы он работал с другим типом IPacket в любом случае.

Итак, если вам действительно нужно использовать фабрику какого-то типа, я бы предложил использовать шаблон Абстрактной фабрики, чтобы клиенты фабрики зависели только от интерфейса фабрики и, следовательно, могли работать с различными типами *. 1015 * без необходимости изменения. Например:

public interface IPacketFactory
{
   IPacket CreatePacket();
   IPacket CreatePacket(Client creator, DateTime creationTime, string data);
}

public class MessageFactory : IPacketFactory
{
   public CreatePacket()
   {
      return new Message();
   }

   public CreatePacket(Client creator, DateTime creationTime, string data)
   {
      return new Message(creator, creationTime, data);
   }
}

//You'd implement factories for each IPacket type...

public class Client
{
   private IPacketFactory _factory;

   public Client(IPacketFactory factory)
   {
      _factory = factory;
   }

   public SomeMethodThatNeedsToCreateIPacketInstance()
   { 
      IPacket packet = _factory.CreatePacket();

     //work with packet without caring what type it is
   }

}


//a higher level class or IOC container would construct the client with the appropriate factory

Client client = new Client(new MessageFactory());

// the Client class can work with different IPacket instances without it having to change (it's decoupled)

Client client2 = new Client(new LogFactory());

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

В случае, когда одному из IPacket исполнителей требуются дополнительные параметры:

В шаблоне Abstract Factory должен быть единый интерфейс для всех разработчиков, поэтому, если для всех фабрик имеет смысл иметь метод Create с дополнительными параметрами, вы можете добавить их в интерфейс. Одной из форм этого является передача объекта с различными свойствами / методами, которые метод Create может использовать для получения дополнительных значений параметров, в которых он нуждается. Особый случай - Double Dispatch, когда вызывающая сторона передает себя (в данном случае Клиент) и затем вызывается изнутри метода Create.

//in MessageFactory : the PacketContext holds various data that may be relevant to creation

public IPacket Create(Client creator, DateTime creationTime, string data, PacketContext ctx)
{
   return new Message(creator, creationTime, data, ctx.SomeExtraData); 
}

//in LogFactory: the Log doesn't need anything from the PacketContext but it does call something on the Client (Double Dispatch)

public IPacket Create(Client creator, DateTime creationTime, string data, PacketContext ctx)
{
   return new Log(creator.Name, creationTime, data);
}

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

public class MessageFactory : IPacketFactory
{
   private object _data;

   public MessageFactory(object extraData)
   {
      _data = extraData;
   }

    IPacket CreatePacket(Client creator, DateTime creationTime, string data)
    {
       return new Message(creator, creationTime, data, _extraData);
    }

    ///rest of implementation
}

Они представляют некоторые из вариантов, но в любом случае, я бы настоятельно рекомендовал вам не использовать статический или одноэлементный класс "Factory", потому что он будет сильно связывать ваш клиентский класс с фабрикой и, скорее всего, IPacket подкласс.

3 голосов
/ 30 октября 2011

IMO зависит от того, требуются ли CreationTime, Creator и Data для создания действительного экземпляра пакета. Если это так, я бы придерживался первого решения и потребовал, чтобы эти свойства были установлены как можно раньше, в вашем случае в вашем заводском методе. Если эти свойства не должны быть изменены в более поздний момент времени, я бы дополнительно сделал эти свойства только для чтения. Если установка свойств является необязательной, сохраняйте интерфейс фабрики чистым и снимите перегрузку, которая имеет свойства.

1 голос
/ 30 октября 2011

Я бы предложил первый подход, потому что таким образом

  • Вы можете пометить все IPacket свойства как доступные только для чтения, и все экземпляры будут неизменными
  • Гораздо проще передать все необходимые параметры для создания объекта в фабричном методе Create..(), а не инициализировать их все позже, выполняя работу, которая должна быть делегирована фабрике ...

Что касается подхода с Client creator в качестве параметра метода фабрики - выполните абстракцию Client с помощью интерфейса, чтобы, если было бы намного проще протестировать эту фабрику, введя макет Creator, фабрика также была бы очень гибкой. *

Резюме:

  • Сохранять неизменяемость объектов
  • делегировать работу по созданию объекта на фабрику и не разбивать ее на фабрику, это не чисто
  • Абстрагируйте все входные параметры с помощью интерфейсов, чтобы код был менее связан и легко тестировался
...