В каком формате лучше использовать Factory?
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:
public static class PacketFactory
{
public static IPacket CreatePacketObject(PacketType type)
{
IPacket packetToCreate = null;
switch (type)
{
case PacketType.Info:
packetToCreate = new Info();
break;
case PacketType.Log:
packetToCreate = new Log();
break;
case PacketType.Message:
packetToCreate = new Message();
break;
}
return packetToCreate;
}
public static IPacket CreatePacketObject(PacketType type, Client creator, DateTime creationTime, string data)
{
IPacket packetToCreate = null;
switch (type)
{
case PacketType.Info:
packetToCreate = new Info(creator, creationTime, data);
break;
case PacketType.Log:
packetToCreate = new Log(creator, creationTime, data);
break;
case PacketType.Message:
packetToCreate = new Message(creator, creationTime, data);
break;
}
return packetToCreate;
}
}
Перед тем, как применить шаблон, вы должны иметь четкое представление о том, что вы получаете, делая это, и в этом случае я действительно не вижу, что введение статического "Factory" набирает вас. Посмотрите на это с точки зрения клиента PacketFactory
: он представил его, уменьшив связь между клиентом и различными конкретными исполнителями IPacket
? Я бы не утверждал, что, поскольку клиент уже должен знать, какой тип IPacket
он хочет, указав значение перечисления либо PacketType.Info
, PacketType.Message
, либо PacketType.Log
. Как это отличается от клиента, знающего о классах Info
, Message
и Log
? Поскольку "Factory" является статическим классом, клиент так же связан с возвращаемым типом IPacket
, как и если бы он просто вызвал конструктор соответствующего конструктора IPacket
, потому что вам пришлось бы изменить клиент для того, чтобы он работал с другим типом IPacket
в любом случае.
Итак, если вы действительно должны использовать какой-либо Factory, я бы предложил использовать шаблон Abstract Factory, чтобы клиенты Factory зависели только от интерфейса Factory и поэтому способный работать с различными типами IPacket
без необходимости изменения. Например:
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 decoupled)
Client client2 = new Client(new LogFactory());
Насколько Factory должен позволить построить IPacket
без указания создателя, данные и время создания или не зависят от инвариантов класса. Если инварианты класса могут выполняться, когда поля не указаны, то это прекрасно, в противном случае они должны быть необходимы. Часть задания класса должна состоять в том, чтобы убедиться, что он не может быть сконструирован в недопустимом состоянии, поскольку пользователи этого класса будут в зависимости от того, что будет.
В случае, когда одному из разработчиков IPacket
нужны дополнительные параметры:
Образец 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
начинает неявно знать, какой конкретный тип строится, тогда вам может потребоваться сделать шаг назад и рассмотреть, подходит ли Factory. Единственный другой вариант - предоставить дополнительную информацию при построении Factory (т.е. Передать его конструктору).
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" , потому что он будет сильно сочетать ваш клиентский класс с Factory и большинством вероятно, подкласс IPacket
.
IMO зависит от того, необходимы ли CreationTime, Creator и Data для создания действительного экземпляра пакета. Если они есть, я бы придерживался решения one и требовал, чтобы эти свойства были установлены в самый ранний возможный момент, в вашем случае в вашем методе factory. Если эти свойства не будут изменены в более поздний момент времени, я бы дополнительно сделал эти свойства только для чтения. Если настройка параметров не является обязательной, сохраните свой интерфейс factory и удалите перегрузку с ее свойствами.
Я бы предложил первый подход таким образом
IPacket
как readonly, и все экземпляры будут неизменнымиCreate..()
factory, а не самостоятельно инициализировать их позже, выполнив задание, которое должно быть делегировано factory...В отношении aproach с параметром метода Client creator
как factory - сделать абстрактным Client
интерфейсом, поэтому, если было бы очень легко протестировать этот factory, введя макет Creator и factory будет много также гибкий.
Резюме: