Use of factory pattern

2019-04-14 06:28发布

问题:

Which way use of Factory is better(correct)?

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

or should I throw out second method in PacketFactory and use this one?

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

or maybe some other?

PacketFactory code:

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

    }

回答1:

Before applying a pattern you should have a clear idea of what you gain by doing so, and in this instance I don't really see that introducing a static "Factory" is gaining you anything. Look at it from the point of view of the client of the PacketFactory: has introducing it reduced the coupling between the client and the various concrete implementors of IPacket? I would argue not since the client already has to know which kind of IPacket it wants by specifying a enumeration value of either PacketType.Info, PacketType.Message or PacketType.Log. How is that any different from the client knowing about the Info, Message and Log classes? Since the "Factory" is a static class the client is just as coupled to the type of IPacket being returned as it would be if it just called the constructor of the appropriate IPacket implementor because you would have to change the client in order for it to work with a different type of IPacket in either case.

So, if you really must use a Factory of some kind, then I would suggest employing the Abstract Factory pattern so that clients of the Factory would only depend on the factory interface and would therefore be able to work with different kinds of IPacket without having to change. For example:

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());

As far as whether the factory should allow one to construct an IPacket without specifying the creator, data and creation time or not depends on the class's invariants. If the class invariants can be satisfied when the fields are not specified then that's fine, otherwise they should be required. A part of a class's job should be to ensure that it cannot be constructed in an invalid state since users of the class will be depending upon that to be the case.

In the case where one of the IPacket implementors needs extra parameters:

The Abstract Factory pattern needs there to be a uniform interface for all implementers, so if it makes sense for all the factories to have a Create method with the extra parameters then you can add them to the interface. One form of this is to pass an object with various properties/methods that the Create method can use to derive the extra parameter values it needs. A special case is Double Dispatch where the caller passes itself (in this case the Client) and is then called from inside the Create method.

//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);
}

You need to remember that the goal is to abstract the type of IPacket being created, so if whilst implementing this approach you start to get the feeling that the Client is starting to implicitly know the specific type being constructed then you may have to take a step back and consider if the factory is appropriate at all. Your only other option is to provide the extra information when you construct the factory (i.e. pass it to the constructor).

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
}

Those represent some of the options, but in any case, I would strongly advise that you do not use a static or singleton "Factory" class because it will strongly-couple your client class to the factory and most likely the IPacket subclass.



回答2:

IMO it depends on whether CreationTime, Creator and Data are required to create a valid instance of a packet. If they are, I would stick with solution one and require those properties to be set at the earliest moment possible, in your case in your factory method. If those properties are not supposed to be changed at a later moment in time I would additionally make those properties readonly. If setting the properties is optional, keep your factory interface clean and remove the overload that has the properties.



回答3:

I would suggest the first approach becuase in this way

  • You can mark all IPacket properties as readonly and all instances will be immutable
  • It is much clear to pass all required parameters to build an object in the Create..() factory method rather than initialize all of them later yourself by doing a job wich must be delegated to a factory...

Regarding an aproach with a Client creator as factory method parameter - do abstract Client by an interface so if would be much easy to test this factory by injecting a Creator mock and factory would be much flexible as well.

Summary:

  • Keep objects immutable
  • Delegate object creation work to a factory and do not split it between factory calee, this is not clean
  • Abstract all input parameters by interfaces so code would be less coupled and easily to test