In my previous post regarding the abstract factory pattern I discussed some basic concepts regarding this pattern and we went all the way up to the combination of an abstract factory and a polymorphic singleton to achieve the best encapsulation and reuse of the abstract factory code. In order to be able to fully maximize this feature we also need to talk about how to package the different classes in the abstract factory pattern so that packaging will not hinder future growth.
My discussion here regarding packaging is going to be completely based on Microsoft .net technology but the same basic concepts can be applied to any other technology that you might be using:
At the fines grained level of packaging of the abstract factory implementation you need to place all the abstract definitions that the client code needs into one assembly (i.e. the AbstractFactory class and the product interfaces) and then each product family alongside its factory class should be packaged into its own assembly. So coming back to the data access layer example of the previous discussion we had created this design (please see the last example in my original abstract factory post for the polymorphic singleton implementation added to the AbstractFactory class):
Since in the above design we have two product families (the Oracle & the SQL Server families) then we would need three assemblies to package all these classes:
- The CommonDAL assembly will contain AbstractFactory, IWarehouseAccess, IDeliveryAccess & IProductAccess.
- The SQLDAL assembly will reference the CommonDAL assembly and would contain the SQLFactory, SQLWarehouseAccess, SQLDeliveryAccess & SQLProductAccess.
- The OracleDAL assembly will reference the CommonDAL assembly and would contain the OracleFactory, OracleWarehouseAccess, OracleDeliveryAccess & OracleProductAccess.
The client code would only need to reference the CommonDAL. Using that reference the client code would call the AbstractFactory (since it's a polymorphic singleton it will be our single point of access), obtaining an appropriate interface that it needs and then call that interface to do whatever is necessary:
IWarehouseAccess w = AbstractFactory.Instance.CreateWarehouse();
w.SaveWarehouse(…)
As you can see the client code only needs to physically reference the CommonDAL, obviously the assembly containing the product family that has been configured for current use must be available (either in the current execution directory or in the GAC), since the AbstractFactory's polymorphic singleton implementation is using reflection to load the configured product assembly, but the only compile-time dependency between the client code and our assemblies will be to the CommonDAL.
This will also give us a very nice feature. Once you design your software based on the above pattern (or combination of patterns) you are able to deliver only what the client needs and no more. If you have a client that will be using Oracle you don't need to deliver the SQL product family assembly at all and there will be no dependencies on it either. Once a client using Oracle for example decides to switch over to SQL you can easily send them the SQL product family assembly (SQLDAL), ask them to copy it into the runtime directory and change the configuration of the app to point to this assembly instead of the previous one. This also brings us to another neat capability: if in the future you decide to store your information in another format (for example let's say as an XML file) all you have to do is implement the XML family of products (XMLWarehouseAccess, XMLProductAccess & XMLDeliveryAccess) & create a factory for them, package them up in one assembly and ship it to the client. As you can see the original binary code available on the client's machine doesn't need to be touched at all. All you are doing is providing a pluggable new implementation that can be copied alongside the current binary implementation on the client's machine and be used immediately.
Some notes on the Abstract Factory pattern:
- The abstract factory pattern creates an extra hierarchy of classes (the factory classes) that need to be maintained alongside the actual products. This extra hierarchy in some designs might be disliked; in other cases might be thought as the cause of extra complexity. So the use of the pattern should be justified with the need to change/replace/add product families in the future and the other features that the pattern provides.
- This pattern can be easily replaced with a full reflection based implementation but is it worth it from a performance stand point? I have seen similar implementations of the pattern where the factory hierarchy was removed and replaced with one class that will create the needed product by using reflection (similar to what we are doing in the polymorphic singleton's CreateInstance method) but the main difference would be that the code that I have provided in the CreateInstance method is only called once per program execution while a reflection based implementation of the factory class would cause the reflection routines to be called every time an object is created (this could be very expensive). As a rule of thumb if your factory class is going to be called in the order of more than 50 times a second then don't go with a reflection implementation as it can impact performance a lot.
- Don't ever use the abstract factory pattern in scenarios where the client code would need to create an object from product family A and then a little bit later from product family B, this pattern hasn't been designed for such a problem. I'll describe a good solution to this issue once I describe the Bridge pattern.
- In some cases an abbreviated version of this pattern would work a lot better. This abbreviated version is a pattern all by itself and it's called the "Factory Method" pattern which I will describe in later posts.
No comments:
Post a Comment