Design patterns in the test of timeA modern alternative to Abstract Factory–filtered dependencies
In my Abstract Factory post, I mentioned that I really don’t like the pattern, and in particular, code like this:
1: static IGUIFactory CreateOsSpecificFactory()2: {
3: string sysType = ConfigurationSettings.AppSettings["OS_TYPE"];4: if (sysType == "Win")5: {
6: return new WindowsFactory();7: }
8: else9: {
10: return new MacFactory();11: }
12: }
One of the comments mentioned that this might no be ideal, but it is still better than:
1: if(RunningOnWindows)2: {
3: // code4: }
5: else if(RunningOnMac)6: {
7: // code8: }
9: else if(RunningOnLinux)10: {
11: // code12: }
And I agree. But I think that, as the comment mentioned, a far better alternative would be using the container. You can do this using:
1: [OperationSystem("Windows")]2: public class WindowsFactory : IGUIFactory3: {
4: }
5:
6: [OperationSystem("Linux")]7: public class LinuxFactory : IGUIFactory8: {
9: }
10:
11: [OperationSystem("Mac")]12: public class MacFactory : IGUIFactory13: {
14: }
15:
Then you just need to wire things through the container. Among other things, this means that we respect the open / closed principle. If we need to support a new system, we can just add a new class, we don’t need to modify code.
Remember, the Go4 book was written in the age of C++. Reflection didn’t exists, and that means that a lot of patterns do by hands things that can happen automatically.
More posts in "Design patterns in the test of time" series:
- (21 Jan 2013) Mediator
- (18 Jan 2013) Iterator
- (17 Jan 2013) Interpreter
- (21 Nov 2012) Command, Redux
- (19 Nov 2012) Command
- (16 Nov 2012) Chain of responsibility
- (15 Nov 2012) Proxy
- (14 Nov 2012) Flyweight
- (09 Nov 2012) Façade
- (07 Nov 2012) Decorator
- (05 Nov 2012) Composite
- (02 Nov 2012) Bridge
- (01 Nov 2012) Adapter
- (31 Oct 2012) Singleton
- (29 Oct 2012) Prototype
- (26 Oct 2012) Factory Method
- (25 Oct 2012) Builder
- (24 Oct 2012) A modern alternative to Abstract Factory–filtered dependencies
- (23 Oct 2012) Abstract Factory
Comments
"and that means that a lot of patterns do by hands things that can happen automatically."
That is a key insight. It seems like C++ people often don't even consider solutions that would involve reflection or runtime codegen in the managed world. Because they are not available.
Them being unavailable is a huge productivity hindrance in some situations.
Nitpicker corner: How is changing my IOC configuration different from changing my Factory?
I am totally with you on this though, but IOC Containers are just nice abstract factories and their configuration has to be treated as code too. If you screw that up you are hosed, same as if you change the factory code when supporting a new OS.
Daniel, Changing the IOC config usually happens by conventions, there is a lot less "weight" to the matter. And it means that you can respect the OCP.
A reflection like alternative has been available in the C++ world for ages in the form of LoadLibrary/GetProcAddress for Windows. Just not that wellknown as reflection in .Net
What hinders you then to put the same convention into the Factory? To me an IOC Container is just that - a factory I can reuse. (If it weren't .NET I'd even recommend that - but since it's quite easy to shoot yourself in the foot with that implementation I'd use a container for my Convention over my own implementation.. But in Ruby for example things look a lot different.. it's a simple "#{os}Factory".constantize.new)
Also think about the convention a bit: Don't you have to implement the convention explicitly somewhere, because if I run the app on a Platform that's not yet supported I surely don't want my IOC Container to just throw up some weird error but rather gracefully tell the user he is running a system that's not supported and exit.
Bjorn, That is only valid for things that you actually export. And for other dlls, not to yours, or local classes.
Daniel, The need to create 10s of factories?
No why should you want 10s of factories. I just wanted to say that for certain applications and scenarios using a IOC is the way to go, for certain others (or even only parts of an application) it may be beneficial to write a explicit factory.
There have been numerous occasions where I really had to get really intimate with my IOC container to make it construct objects in a certain way I needed them where a Factory could have saved me a lot of "library complexity" while also being a shorter and more readable solution to people who don't know Container X like their front lawn.
Of course I am still sold on the convention based IOC container idea, just sometimes you have exceptions that warrant the use of the pattern.
The pig will stay the same no matter what color of lipstick you use.
Second option would do me in some cases.
With so many containers and their idiosyncratic behaviours, those if statements might be the easiest way to show what the system does. Learning about a new container and weird conventions when the system goes down and people are very angry is not that cool.
But hey, I'm a nitpicker and this is all very abstract.
Ayende, I think you're misunderstanding the gist of the pattern. Abstract Factory, at least as it is described in my reprint of "Design Patterns" from April 2005, does not mandate (or even suggest) the use of a factory factory. It's just about providing an interface (the Abstract Factory) that has many abstract (or overridable) factory methods used to create a family of objects. It decouples the client by the actual object creation, so by implementing the interface multiple times (Concrete Factories), it's easy to swap one family with another.
The design pattern does not talk about how you get the Concrete Factories. Whether you get them from an IoC container, some other object repository, or a factory factory is outside the scope of the pattern.
So, your "far better alternative" is still an implementation of Abstract Factory.
Oh, and I think the GoF patterns were actually very much influenced by Smalltalk. "Reflection"-like stuff was supported in Smalltalk very well, since classes are first-class objects in Smalltalk.
But you're right, of course, in that the book targeted C++ programmers as well.
C++ implementation of this probably wouldn't use something as mundane if or reflection. Template metaprogramming allows policy driven design in C++, a sort of compile time strategy pattern
http://en.wikipedia.org/wiki/Policy-based_design#Simple_example
Yes, I'm glad I don't do C++ any more.
@Fabian
+1. The Abstract Factory pattern as you said is to decouple the client by the actual object creation. In some causes this is done with a container.
The main point is, however the implementation details work, the client doesn't care.
Swap CreateOsSpecificFactory() mentioned above in the blog post with Resolve() and you'll see it's the same abstraction.
I'm not saying GetProcAddress is an alternative to an IoC container but in the case of factory it can be used quite easily. Ofc putting the config in code (attribute) is out of the question.
That attribute looks very close to a function export ;-)
Sorry I didn't end my point very well, I meant to end it with:
I don't think the use of an IOC container is an alternative, it's just an implementation of the pattern that's better suited in some situations.
Agree with daniel: Nitpicker corner: How is changing my IOC configuration different from changing my Factory?
By the way: Attributes is also code.
@Rafal
I agree, it is a pig. I usually avoid a container entirely or use whatever one comes with the host application (e.g. Windsor + RSB or TinyIOC + Nancy). But sometimes you get actual requirements like 'it has to run on any OS' or 'we sell all over the world and have to collect the proper taxes.' Then and only then should you pick a container.
Oren, Are we saying in today's age, with most applications we write, its imperative to use an IOC container to adhere to the Open closed principle? An example I have is, when the business wishes to add another predicate to a popular search, having to modify a (linq) query some where to add another where clause would be violating OCP. But to add another predicate class that simply gets composed with other predicates and applied to the where clause via your popular IOC container means OCP is respected. Would you go to this length, considering with the former, its more discoverable for the average support dev with one class having all the predicates, and the latter while respecting OCP, requires more fishing around, inspecting interfaces and signatures to see which (predicate) classes have the same interfaces (or attributes), that help them in being registered and composed together in one query?
Afif, Please don't put words in my mouth. There is a difference between modifying behavior, and adding behavior. Adding support for a new thing should require little if any modification of existing code. Modifying existing behavior (because of a bug or a change in the requirements) is different.
Good post. I've been thinking about this stuff for a while.
Both approaches still encourage polymorphic code, and seperate creation concerns - which is the intent of the pattern - so i would say i LIKE the pattern, but the default implementation is outdated.
A long time ago there was a article by a guy from Google who claimed that something like 18 of the GOF four patterns were redundant in dynamic languages, and hence they were pretty much a result of statically typed OO languages.
I've used the c# dynamic keyword in a few places to recover and dispatch on type - which i think may make the visitor pattern implementation a lot simpler.
Having only skimmed the above posts i still agree with most of them. - C++ can use a templates and policy based to create generic factories and abstract factories. - Adding a new switch inside a factory to return a new class is up for debate as to whether that is ADDING or MODIFYING code. Strictly your modifying an assembly, but your only adding lines of code, and not modifying any existing lines. So i don't think you are really breaking O/C principle there. That said sometimes it's hard to deploy modified dll's and easier to deploy modified configuration - so the type of change can be really important. - I need to explore the features of IOC containers more but i only use them for statically known dependencies - i.e avoid referencing the container in code specifically if possible. This means you can't use them, or their lovely dependency resolution in more runtime based creation decisions. So i think factories, and the builder pattern still have their place.
Oren - I regularly reference your posts on various topics - keep up the good work and thanks
Ayende tell me plz how i should create db tables with comment structure, its like commenter and second one comment..
Vasya, Absolutely, where do you want me to send my invoice to?
In tekpub video, you've mentioned, that "you don't like containers nowadays very much, because they are used to create very deep object hierarchies and you try to make your architecture as shallow as possible." (that may be not a word exact phrase, but I hope it is accurat enough).
May be you could write some day how you fight with deep object hierarchies, because I my code I see that everything is composed from small pieces and it is not possible to create anything anymore without using container, that causes various headaches.
Giedrius, Check my "limit your abstractions" series of blog posts.
Comment preview