ArticleS
.
UncleBob
.
CraftsMan49
Edit Page:
!title Craftsman 49 !2 Brown Bag VI, Abstract Factory Based on a recent thread in comp.object, I have Alphonse, Jasmine, Adelaide and Avery discuss the Abstract Factory pattern. [[click here to read][http://www.objectmentor.com/resources/articles/Craftsman49BrownBagVI.pdf]] !commentForm -r #----- Blog Comment Marker (Please don't delete me) -----# !* Tue, 31 Oct 2006 04:23:47, Ged Byrne, Further thoughts Thinking on it further, Dependancy Injection isn't a replacement for Abstract Factory but rather a way of using it. Looking at the Spring Framework, the whole thing is based on Abstract Factorys. *! !* Mon, 23 Oct 2006 16:48:40, Ged Byrne, Dependency Injection I don't think the code that creates the compiler can ever be inside the 'bubble', as it is impossible to create an instance of the compiler without knowing the target language. When you design so that the target language is specified as a command line paramater, then you have just moved the creation code into the shell. Is this really a good idea? Dependency injection doesn't necessarily require a framework such as spring, it just requires that the compiler have no knowledge of how the generator is to be created, but instead treats it as a dependancy that will be injected by another object. The can be done by Springs Abstract factory based on the contents of the XML defining the application context, or a main method that has selected a generator based on the command line parameter. Consider, for example, the early Craftsman articles dealing with wrapping SMC. Because SMC has moved the creation code into the shell, the java wrapper is forced to work with the shell in order to use it. The issues dealt with in those articles shows the problems that this causes. If a DI approach had been used, then their would be an SMC compiler would exist with an object 'bubble' that presented a bean interface to the world. Propertys would have been set, such as the input and output streams, and a compilation method invoked. One client would have supported the command line. The network wrapper would have been another client, sitting alongside the command line client. It could be written in easily using just Java, or a container might have been employed. The options are wide open. (Anybody else become much better at roman numerals lately) *! !* Sun, 22 Oct 2006 10:34:36, Mark Brackett, Dependency Injection I would assume that the code that creates the Compiler is inside the "bubble", and so wouldn't be a good place to put the logic required for creating the generator - you'd have the same problem as if the Compiler was doing it. I haven't had the chance/reason/opportunity to work with container based DI, so I'm not sure how that'd fit in. My feeling, though, is that either you'd need fairly simplistic logic (a config file) or an extension point outside the bubble to inject the logic. Again, not having worked with it, I could be completely off base. But, yeah, though I hadn't argued it in those terms (should really start to work on my design patterns vocabulary) - I suppose I am advocating an IoC approach; instead of Compiler requesting a specific CodeGenerator, someboday else (AbstractFactory? Container? etc.) makes that decision for it. *! !* Fri, 20 Oct 2006 09:24:04, Ged Byrne, Dependancy Injection Mark, I see your point. Dependancy injection seems much simpler. Compiler has a generator bean property, and the container just calls setGenerator(). You can use a full blown container, like Spring or Pico, or just write a some code creates the compiler and sets it's generator. Given this scenario, is there any need for the AbstractFactory? *! !* Fri, 20 Oct 2006 09:03:34, Mark Brackett, Delegate Decision to RealGeneratorFactory I originally did code (forgive my Java syntax, it's not my normal environ) in the 3rd comment, but left out the DecisionContext. This is a case where I want the code generated to be VB.NET if I'm on Windows and have the .NET Framework installed, Java if I have the JRE but no .NET Framework (or am not on Windows), and C++ otherwise: {{{ public interface GeneratorFactory { public CodeGenerator makeGenerator(DecisionContext context); } public interface DecisionContext { public string OS; public string Framework; public CpuType CPU; } public class RealGeneratorFactory implements GeneratorFactory { public CodeGenerator makeGenerator(DecisionContext context) { if(context.OS == "Windows" && context.Framework == "Microsoft.NET") { return makeVbNetGenerator(); } else if(context.Framework == "JRE") { return makeJavaGenerator(); } else { return makeCppGenerator(); } } private CodeGenerator makeCppGenerator() {return new CppGenerator();} private CodeGenerator makeJavaGenerator() {return new JavaGenerator();} private CodeGenerator makeVbNetGenerator() {return new VbNetGenerator();} } }}} *! !* Fri, 20 Oct 2006 08:28:14, Jean [Luis ;-)], please dear, could you show us some code Mark Dear, could you please be so kind to show us your design ideas in code? You see as I'm not so young anymore, I prefer loose my reasoning when reading design ideas as text... *! !* Fri, 20 Oct 2006 08:16:43, Mark Brackett, Absent a need for dynamic loading of 3rd party CodeGenerator plugins (which I suppose is up for debate in the context of the story*), a config file (what form it takes is irrelevant) only serves to mask your intentions from the compiler. All you've done is move the compile time dependency to a runtime dependency; I can think of a lot of downsides to this (as mentioned: no compile time checking for validity, a (negligible) performance hit, external dependencies, etc.), but what's the upside? Being able to add a line of text as opposed to a line of code? What I still haven't seen dealt with, and what I find more interesting, is the semantic coupling between Compiler (or some other, unknown, class in the "bubble") and the concrete CodeGenerators. In all of the story's designs, and all the refactorings here (save mine, I think), it is assumed that Compiler will somehow just know what CodeGenerator to request. Except for a fairly simplistic command line argument that just gets passed around**, I don't see how you can have Compiler make that decision without knowing about the CodeGenerators (or inventing a DSL for the purpose of describing your rules). It's relatively easy to remove compile time dependencies, but if you still have semantic dependencies all you've done is remove compile time *checking* which isn't very helpful at all. * I, personally, think that the story is fairly clear that CodeGenerator plugins are to be coded by the same folks coding RealCodeGenerator. ** Even in the case of a command line argument, I'd still wrap it in a class and pass it to RealCodeGenerator to make that decision. I don't see any downside in this, and it allows easy refactoring later to take in more decision inputs. *! !* Fri, 20 Oct 2006 05:07:07, Ged Byrne, Ravi, Why do you feel that a configuration file in the form {{{ ruby=com.xxx.yyy.RubyGenerator }}} is better than adding the line {{{ ruby(rugyGenerator.class) }}} I understand the need to separate code from data, but does data have to take the form of a text file? The RealGeneratorClass contains only data, but no logic. What it does provide is additional intent. The compiler will complain in the following circumstances: * The same name is repeated * The generator class is not provided. * The generator class does not exist If more details are required, as Mark points out they probably will, then this will become more valuable. For example, the enum's contructor might take a GeneratorConfiguration class which extends a specific interface. Those clases can be defined within the neum using an anonymous inner class and the whole thing is type checked at compile time. The key exception I can think of is when you want to offer a plugin architecture where generators will be added without recompiling the core. If this is necessary, then a configuration file is probably needed. It's a pity enums can't be extended, otherwise even that scenario would be covered. *! !* Fri, 20 Oct 2006 03:10:25, Luis Sergio Oliveira, Re: Configuration is not just through XML files Hello Ravi, I see your point. You could have the factory constructing the generators via reflection and therefore it would actually be less changes. But please note that properties files always need to be parsed: {{{ ResourceBundle generatorsCfg = ResourceBundle.getBundle("Generators"); String generatorClassName = generatorsCfg.getString(laguageName); try { Class generatorClass = Class.forName(generatorClassName); Constructor generatorCtor = generatorClass.getConstructor(new Class[0]); Object gen = generatorCtor.newInstance(new Object[0]); } catch (Exception e) { // TODO: handle all the possible errors and provide meaningfull information } }}} Off course I would say that this is more flexible than the if parsing of laguageName. All would be OK if we enforce by design a no args constructor on the generators. Since these are to be considered plug-ins I would say it is a fair restriction. +1 for properties based factories! *! !* Thu, 19 Oct 2006 21:24:48, Ravi Venkataraman, Configuration is not just through XML files There seems to be a misunderstanding when I used the term "configuration files" in an earlier post. I clearly stated "properties, XML, .., whatever". I personally never use XML for configuration unless the tool forces me to use it (web.xml, for example.) I prefer a text file or, if the requirements are simple, a properties file. In this case, a property file of the form Java=com.xxx.yyy.JavaGenerator C++=com.xxx.yyy.CppGenerator ... would suffice. Read this file, get the class name and convert it to a Class object, then store ("Java", com.xxx.yyy.JavaGenerator.class) as an element of your Map<String, Class>. Once you've done this, when the Factory asks for a "Java" generator, then the method I stated earlier just ends up returning the Generator object of the correct type. Notice also that there is no hard-coding anywhere in the code. There is no need to change the parser in this case, unlike what Luis Sergio suggests. In fact, with properties files, there is no parser! I, personally, do not want to change my Parser, or FooConfig class every time a new language is added. The more important points I feel are as follows: 1. In software, keep the parts that change frequently separate from the parts that remain practically unchanged. Here, the languages can change, but how to create a new Object of the correct language Generator class does not change. Since there is no need to change the Parser code or the code that uses the parser, a configuration file requires less work to add new language generators. I feel that one of the key things for us to learn is to separate code from data. The languages and names of their generator classes are data to me. How to create an object of the correct type for a given language is code. . (ASIDE: And I think that data should be kept separate from code. [Lisp programmers would say that code is data! But that is the next step that good programmers will have to take to become really good.]) Stated this way, it seems obvious that there should be only one piece of code that can create the right generator object no matter what the language. This leads directly to the second point. 2. There are two separate things that are happening in the Factory: (a) get the appropriate Class corresponding to the required language; (b) get the Generator object of the correct type. The suggested change keeps these two things separate, as does the enum solution proposed earlier. The difference is that, when there is a new class to be added, with the enum solution, we have to do the following: 1. Create and test a new class for the new language generator. 2. Add the new class information to the enum. (Java code) Recompile the class. 3. Test this. Change the code, compile and re-test if tests fail. Using the file configuration option, we go through the following steps: 1. Create and test a new class for the new language generator (same as earlier). 2. Add a line of text to the properties file. 3. Test this. If necessary, change the configuration file to change the values and test again within seconds! The benefit is that the configuration files option requires less code, and results in faster testing cycles before implementation. Agreed that this is only a small improvement over the enum option. Just my thoughts. *! !* Thu, 19 Oct 2006 20:10:47, Luis Sergio Oliveira, programming language based configuration I also agree with Ged, I've seen too much XML programming in the past few years that currently I'm starting to doubt its usefulness even for actual configuration files. Normally the usage of XML or (probably in the near future or already being done today) dynamic programming languages' based DSLs for configuration arise more from the technologists' enthusiasm of playing with a new toy than actual business needs. In the end, after putting "all that flexibility" into the applications, if a customer asks to change something that was encoded in the XML files, you find that actually it won't be that easy, because the DSL parser doesn't handle errors appropriately or because the installation overwrites the modified files. Normally this "flexibility" just sits there, becoming a maintenance burden, because the original authors move on and the maintainers will have to learn that in order to add a configuration attribute they have to change foo_cfg.xml, FooCfgParser.java and FooThatIsConfigured.java. Flexibility, uh?!? I remember that when I worked with Hibernate, I simply got hooked by the flexibility of making configurations programmatically. But, hey, it did support Java configurations as first citizen, so, I was able not to leave the realm of Java programming to code the unit tests. And Hibernate usage includes the need for configuration in XML. This is the way I think now how to create configurations in Java or .Net: 0. use the constructor and factories to avoid dependencies; string based factories are just fine, I agree totally with the solution in the article [growing difficulty, configuration is complex, requires rules, etc, etc] 1. define the configuration parameters in a Java class or group of java classes, for the above example it would be FooCfg.java 2. use some parser generator such as Castor to enable XML or some other format; e.g., normally you could base it on FooCfg BUT, only do 1 or 2 if you really need it, being 1 always before than 2! And always remember that it will take much more effort to make it user usable, so don't sell it to managers as already done. *! !* Thu, 19 Oct 2006 19:55:10, Brian Deacon, Re: Dynamic Configuration Mark, This is clearly just semantics, but I would think "plugin" == "arbitrary extension points by 3rd parties". (So, yes, maybe GeneratorLoader instead.) If you're not designing for 3rd party extension, I would say you're just designing "easily extensible". *! !* Thu, 19 Oct 2006 17:21:20, Ged Byrne, What are the actual benefits of using an external configuration file? I don't mean to be argumentative, this is something that's been puzzling me for a while. I work with a lot of applicaitons where everything has been pushed out into a configuration file which then gets included in the Jar. Every time the configuration is updated the Ant Script is run to rebuild the Jar. So what benefit is there to the using a configuration file, other than the overhead of parsing and another level of misdirection when your trying to trace errors? The only thing avoided is the compilation stage. Why is it so important to avoid compilation? Compiled units benefit from static checking and can be loaded much more quickly. If its good for code, why not the configuration? Is this aversion to compilation just a hangover from an age when compilation was much more expensive? *! !* Thu, 19 Oct 2006 16:59:17, Mark Brackett, Dynamic Configuration Dynamic ocnfiguration assumes the only thing the GeneratorFactory does is call new() on a CodeGenerator. Any setup would have to be delegated to the concrete CodeGenerator. Also, unit tests aside, you're still just passing a string identifier around which doesn't make me feel warm and fuzzy. I don't think that RealGeneratorFactory having a dependency on concrete CodeGenerators is a bad thing. The article clearly described these elements as the "plugin boundary". Config files, or walking a plugin directory is fine when you want arbitrary extension points by 3rd parties (at which point, GeneratorFactory becomes GeneratorLoader)...but I'm not sure that I'd rather be editing XML than code if I control both ends. Also, you're still leaving the decision of what CodeGenerator to request in the hands of Compiler. In order for Compiler to make that decision, it will need to know about the new CodeGenerator. If you assume Compiler is a CLI and gets an argument "/language:ruby", I suppose you can get away without touching it. But there's no way your code can make a decision on which CodeGenerator to use without modifying inside the bubble. *! !* Thu, 19 Oct 2006 16:58:05, Ravi Venkataraman, Another approach similar to the Enum Another way of hadnling this is to recognize that the enum is used solely to inform the Compiler of the existence of Java, C++, etc. generators. Since this is data, this information can be provided in a configuration file (properties, XML, ..., whatever) and have this generated in a static code block in the RealGeneratorFactory Class. This way, there is even less work to do. The RealGeneratorFactory will not have to be touched even when a new language is added. It will not even know of the existence of the different languages. In short, there will be a static block in this class that does something like this: static { // pseudo-code Read configuration file specifying languages. For each language, create a Map of language and Class object. } The makeGenerator method can be as simple as: public Generator makeGenerator ( String type ) { return (generator) map.get(type).newInstance(); } *! !* Thu, 19 Oct 2006 14:36:48, Mark Brackett, Remove the decision responsibility from Compiler The problem here is that Compiler must know when to instantiate what CodeGenerator; you've effectively cut out half the benefit of the CodeGenerator interface, and needlessly populate the GeneratorFactory interface with a unique method per CodeGenerator. If you delegate the decision to RealGeneratorFactory, you keep the bubble insulated. Now, to add a new CodeGenerator, you only need to touch RealGeneratorFactory and add a new CodeGenerator implementation. Of course, if the CodeGenerators require a lot of setup that RealGeneratorFactory is responsible for, it'd probably be a better idea to do a new class to make the decision. {{{ public interface GeneratorFactory { public CodeGenerator makeGenerator(DecisionContext context); } public class RealGeneratorFactory implements GeneratorFactory { public CodeGenerator makeGenerator(DecisionContext context) { if(context.os == "Windows" && context.Framework = "Microsoft.NET") { return makeVbNetGenerator(); } else if(context.Framework = "JRE") { return makeJavaGenerator(); } else { return makeCppGenerator(); } } private CodeGenerator makeCppGenerator() {return new CppGenerator();} private CodeGenerator makeJavaGenerator() {return new JavaGenerator();} private CodeGenerator makeVbNetGenerator() {return new VbNetGenerator();} } }}} *! !* Thu, 19 Oct 2006 13:59:43, Brian Deacon, Type Safety I think Ged's example only gives type safety benefits to RealGeneratorFactory, which isn't supposed to be publicly consumed, if I understand correctly. And if you push the enum up, you reintroduce the dependency problem. Anyway, my real comment was about this sentence: "This unit testing stuff we've been doing over the last couple of years may make type safety redundant." That sounded a too broad to me. Probably fairer to say that unit testing can make some (many?) scenarios not rely on type safety, but are our test suites typically comprehensive enough that we've covered everything type safety protects us from? *! !* Thu, 19 Oct 2006 07:53:17, Ged Byrne, Enum instead of Switch Am I jumping the gun to suggest the use of an Enum rather than a switch for RealGeneratorFactory. This givs the benefit of both a type safe and typeless method for retrieving generators. Additional generators will simply require an addition to the enumeration, eg ruby(rugyGenerator.class) {{{ import junit.framework.TestCase; interface Generator { public String result(); } class CppGenerator implements Generator { public String result() { return "C++"; } } class JavaGenerator implements Generator { public String result() { return "Java"; } } interface GeneratorFactory { public Generator makeGenerator(String type) throws Exception; } class RealGeneratorFactory implements GeneratorFactory { public enum Generators { cpp(CppGenerator.class), java(JavaGenerator.class); private final Class generatorClass; Generators(Class factory) { this.generatorClass = factory; } public Generator getGenerator() throws Exception { return (Generator) generatorClass.newInstance(); } } public Generator makeGenerator(String type) throws Exception { return Enum.valueOf(Generators.class, type).getGenerator(); } } public class TestEnum extends TestCase { public void testGetCppFactoryFromConstant() throws Exception{ Generator g = RealGeneratorFactory.Generators.cpp.getGenerator(); assertEquals("C++", g.result()); } public void testGetCppFactoryFromString() throws Exception{ GeneratorFactory gf = new RealGeneratorFactory(); Generator g = gf.makeGenerator("cpp"); ; assertEquals("C++", g.result()); } public void testGetJavaFactoryFromConstant() throws Exception{ Generator g = RealGeneratorFactory.Generators.java.getGenerator(); assertEquals("Java", g.result()); } public void testGetJavaFactoryFromString() throws Exception{ GeneratorFactory gf = new RealGeneratorFactory(); Generator g = gf.makeGenerator("java"); ; assertEquals("Java", g.result()); } } }}} *!
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).