1

Consider the following architecture designed for logging operations Add/Update that are being done on an instance of Entity.

LogFactory is an Abstract Factory and receives two factories for Add and Update via constructor using DI. Both concrete factories AddLogFactory and UpdateLogFactory implement ILogFactory in order to produce an ILog. They both need an Entity to collect log-able data. Well, I hope everything is clear and you admit Abstract Factory pattern is the right solution so far.

enter image description here

Problem:

UpdateLogFactory is an exception because it needs additional data: ModifiedProperties to create a before/after string (meanwhile its siblings i.e. other types implementing ILogFactory don't need this kind of data; an Entity suffices them). In order to not break the ILogFactory, I decided to give ModifiedProperties to UpdateLogFactory via its constructor. It solves my problem but I feel I'm cheating somehow; I'm pretending UpdateLogFactory is similar to others while it is actually not.

If someone wants to replace UpdateLogFactory with another concrete type how does he know it needs ModifiedProperties?

Hans
  • 2,674
  • 4
  • 25
  • 48
  • In the GoF reference for Abstract Factory, the pattern is applicable when: "a system should be configured with one of multiple families of products." I don't see separate `CreateAddLog` and `CreateUpdateLog` methods in the Abstract Factory interface. Anyway, if you need to create an UpdateLog with extra info, it goes in that method's signature. – Fuhrmanator Jun 20 '15 at 23:27

2 Answers2

2

Actually ModifiedProperties is an implementation detail. Do any ILogFactory need to produce that before and after format? I don't think so.

Anyway, I believe that ModifiedProperties shouldn't be injected but they should be part of an interface like ICanTrackPropertyChanges:

public interface ICanTrackPropertyChanges
{
      IEnumerable<string> ModifiedPropertyNames { get; }
}

And ICanTrackPropertyChanges should be implemented by your entities. Thus, UpdateLogFactory can make the following assertion:

ICanTrackPropertyChanges trackable = entity as ICanTrackPropertyChanges;

// Design by contract wins!
Contract.Assert(trackable != null);

// And now access trackable.ModifiedProperties and produce the 
// customized message for entity updates

This way, you don't need to provide ModifiedProperties as an implementation detail of ILogFactory, but you believe that entities can track property changes if they implement an interface.

You can enforce this using generic constraints:

public interface IUpdateLogFactory<TEntity> : ILogFactory
    where TEntity : class, ICanTrackPropertyChanges
{
}

...and use this interface to implement loggeable entity updates.

In addition, you get an improvement: IUpdateLogFactory<TEntity> implementations won't need to check if an entity has ModifiedProperty because handled entities are guaranteed to has the whole IEnumerable<string> of modified properties at compile time!

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Impressed! But how `Logfactory` should be changed? Is it going to accept one `ILogFactory` and one `IUpdateFactory` parameter? – Hans Jun 20 '15 at 13:01
  • 1
    @Hans But, really, do you need two implementations? What about a single `ILogFactory` implementation which does X or Y depending if `Entity` implements `ICanTrackPropertyChanges` interface? – Matías Fidemraizer Jun 20 '15 at 20:40
  • @MatíasFidemraizer Well, I think you are right. But I think it is more reasonable to change the `ILogFactory` so that it has two methods instead of one: `CreateSimpleLogFor(Entity)` and `CreateUpdateLogFor(Entity, Dictionary)`. This way, Entity: a) doesn't hold modified properties track and b) every another service can provide modified properties. – Hans Jun 21 '15 at 07:34
  • @Hans Hmm, BTW you can still use a single method and inside that method do the decision based on if entity implements or not the so-called interface. Why not? This is better because you make things easier. Anyway, this is up to you. After all, programming is also creativity! – Matías Fidemraizer Jun 21 '15 at 21:02
0

Are you confusing Abstract Factory and Factory Method?

Your diagram isn't Abstract Factory as defined by GoF:

Abstract Factory GoF

You're missing:

  • Two or more abstract Product types (A, B)
  • Two or more createProduct methods (A, B).

If you were to apply the pattern to your problem, you'd be able to have the extra parameter on the createUpdateLog method (as shown below), but then you will have problems with several classes that don't fit (indicated in pink):

PlantUML diagram of Abstract Factory in this context

I suspect you're trying to use the Factory Method pattern, which only has one signature and is why you're having trouble with the "exceptional" parameter in the case of UpdateLogs.

Edit

Here's the Factory Method pattern:

Factory Method Pattern from GoF

As you see, each concrete factory only deals with one concrete product.

Now I'll plug your design from the question to this pattern:

Factory Method applied

Community
  • 1
  • 1
Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
  • Thank for consideration. I still think it is `Abstract Factory`. There is no necessity to have two or more return types i.e. `IProductA` and `IProductB`. The idea behind Abstract Factory is to hide some Factory Methods behind the Abstract Factory class. – Hans Jun 21 '15 at 07:37
  • How many concrete variants of `ILog` are there? If it's only two, then Abstract Factory is over-design. I'll edit the question to show how Factory Method applies to your diagram. – Fuhrmanator Jun 21 '15 at 13:53
  • Thank you very much. But I couldn't understand how you addressed (if you did) the problem of `UpdateLogFactory` that is it needs additional data to make a log – Hans Jun 21 '15 at 14:41
  • @hans With Abstract Factory, the additional data would be supplied on the separate create method -- see the `createUpdateLog()` method in the second diagram. With Factory Method, a `CreateLogFor` method could take a context object encapsulating general information useful in either type of log. Since you didn't say how many concrete variants there are, I'm not sure either pattern truly applies to your problem. It seems as if you are trying to make a Factory pattern fit your problem. I suggest reading http://stackoverflow.com/a/4211307/1168342 to see which variant of a factory applies. – Fuhrmanator Jun 21 '15 at 15:49
  • If you mean the diagrams after your **Edit**, then I can't see `createUpdateLog` method! Anyway, the problem is not in the the concrete types count; you say 20! The problem is that one of the concrete types needs additional data. If you insist on `Factory Method` pattern, okay; no problem; but nothing will change if this pattern is applied. The problem still survives. – Hans Jun 21 '15 at 20:38