As pointed out to me in comments, Dart mirrors are unlikely to be the most efficient means for implementing the Factory Method pattern in Dart. But I don't care, I love this dynamic language stuff too darn much.
OK, OK, that's not 100% true. I do care about speed and efficiency in my code. I also very much care about speed a a factor for the solutions that will go into Design Patterns in Dart. That said, I am not trying to find the one and only solution when I first investigate a pattern. I am interested in finding as many reasonable implementations as possible so that I might have several to fit different use cases in the book.
Sometimes speed might be the most important need, sometimes it might be clarity, and other times a programmer may need a solution that minimizes the number of classes and subclasses. I really also ought to include potential drawbacks to each pattern or, as the Gang of Four book puts it, consequences of each. One of the consequences of the Factory Method pattern mentioned by the Gang of Four is the difficulty that arises when refactoring constructors.
Since the Factory Method pattern concerns itself with creating new objects, the difficulty in refactoring arises when the constructor needs to change. Consider the concrete creator in the Hipster MVC framework:
class Comics extends HipsterCollection { modelMaker(attrs) => new ComicBook(attrs); get url => '/comics'; }In this case, the
modelMaker()
method plays the role of Factory Method. Where the HipsterCollection
base class leaves this method abstract, the concrete creator must define the implementation. But what happens when the constructor has to change because some unrelated client code needs to supply different arguments? What happens when Hipster MVC becomes super popular only to find that a single argument to modelMaker()
is insufficient in 20% of the use cases? Is a huge rewrite inevitable? Is a breaking, major version change the only option?Thanks to Dart's optional method parameters, I could probably accommodate some change, but it would be nice if these kinds of changes could occur independently of each other. This seems like a job for Dart's named constructors.
Currently, my Factory Pattern's abstract product class,
HispterModel
, declares a constructor that optionally accepts attributes:class HipsterModel { // ... HipsterModel([this.attributes]) { if (attributes == null) attributes = {}; _onSave = new StreamController.broadcast(); _onDelete = new StreamController.broadcast(); } // ... }It might make sense for this default constructor to treat
attributes
as optional, but when creating concrete products as part of a collection, this might prove problematic. With named constructors, I can leave the default case as optionally accepting mode attributes, but, when constructed from a collection, it can be required:class HipsterModel { // ... HipsterModel([this.attributes]) { if (attributes == null) attributes = {}; _onSave = new StreamController.broadcast(); _onDelete = new StreamController.broadcast(); } HipsterModel.collectionMaker(Map attrs): this(attrs); // ... }My concrete creator would use this named constructor in the Factory Method:
class Comics extends HipsterCollection { modelMaker(attrs) => new ComicBook.collectionMaker(attrs); get url => '/comics'; }This has the advantage of allowing the two constructors to evolve independently of each other, which is a decided win. This would also play nicely with last night's mirror-based Factory Method implementation.
But all is not rosy with this approach—mostly due to Dart's treatment of constructors in subclasses. Specifically, they are not inherited. Because of this, I already had to declare a constructor for the optional arguments version. Now I have to do so for the named constructor as well:
class ComicBook extends HipsterModel { ComicBook([attributes]) : super(attributes); ComicBook.collectionMaker(attributes) : super.collectionMaker(attributes); }There is no way around this restriction in Dart. If concrete product classes like
ComicBook
are needed (because they define some additional, domain-specific properties or methods), then I will need to declare both constructors.There might be cases in which this overhead is acceptable—maybe even desirable. Even so, it is probably worth some more investigation in this area to see if more general purpose solutions exist. Toward that end, I will pick back up with factory constructors tomorrow.
Day #102
I was just saying you don't need mirrors, and you could use the factory constructor. I was getting the impression you were saying we did need mirrors, and that you couldn't use the factory constructor to implement the factory method. I probably got the wrong end of the stick.
ReplyDeleteI think exploring using mirrors is great though, so please don't stop :-)
I don't understand what you're trying to accomplish. Do you want to use factories because you want to abstract away the concrete implementation of an interface? Or is it because the calling code has no idea what type is might be creating?
ReplyDeleteIt it's the former, Dart makes this quite easy with factory constructors. Map is a good example:
abstract class Map {
factory Map() => new HashMap();
}
If it's the latter, then you're up against three issues: 1) constructors are not part of an interface (one reason why they don't inherit); 2) constructors aren't properties on Type objects and they don't closurize; 3) There can be many constructors for a class.
They way around this was pointed out in a comment to your last post: use closures. Closures accomplish two things, first they obviously closurize a constructor, second you can require that they have a specific signature that your factory knows how to call.
The map of closures suggested by Vadim is exactly the right way to go if you need a flexible set of factories. You can even use that from within a factory constructor if you have some base class that you want to use to produce instances of subclasses depending on input:
abstract class Model {
Map _factories = {};
factory Model(String name) => _factories[name]();
}
Since _factories holds closures, they can create objects however they want - by calling default, or named constructors, with it's own unique set of arguments, even closed over from where the closure was defined. It also doesn't require anything special of the classes being instantiated. It's the simplest and most flexible solution.
I wouldn't worry about constructor refactoring specifically for this case. The problem basically is that if you refactor, a call site might not have the inputs needed for the new version. Well, that's true of any call site, not just ones in a factory. Don't preemptively create more complexity just because you _might_ need it later.
Much thanks for pointing out a gap in my thinking here. In this particular example I am in the latter case — the creator has no idea what type it might be creating. And I agree that Vadim's approach is likely the most appropriate. I'll kick the tires on it in another day.
DeleteI confess that I am unclear why closures are so important. This could just be semantics, but I always think of closures as a means of obtaining a function and its referenced environment. Am I mistaken in thinking that is not important in this case? That is, the main purpose of the closures in this case is _not_ for maintaining a reference to the environment, but is rather a means to defer initialization. Or is there something more meaningful to the term “closure” in this case?
For what it's worth, my ultimate goal here is to explore the ins and outs of Factory Method (and eventually other patterns). I'll do dumb things and explain poorly at times as I try to get my bearings. The ultimate hope is that I'll work out the bad writing and thinking long before I write the actual chapter. So if some of the explanation is rough here, I will keep at it until it makes more sense. And much thanks for helping me out along the way :)
I often use the term "closure" to refer to any first-class function object, it doesn't really have to close over any variables in its environment. The only reason they're so important is that they're the only way you can store a "reference" to a constructor. With a static methods you can get a reference vis Type.method, with an instance method you can get a reference with instance.method. Static methods may or may not close over free variables. Instance methods always close over the 'this' reference (unlike JavaScript), also known as lexically bound 'this'. These closures are sometimes called tear-off methods. Since we don't have tear-off constructors, the best we can do is wrap them in a closure. It's not even about deferring the the initialization, it's about abstracting it and making it first class, so you can have and pass around a reference to it.
DeleteI think that even if you're trying to explore a concept it'd be a good idea to have a purpose in mind as you do it. Many design patterns don't apply equally in all languages, so trying to repeat them can lead to nonsensical constructs. With a real problem to solve, either the pattern will still be useful, or the language will have reduced the need for it and have a chance to show it's unique strengths.
Ahhh, thanks for the explanation. That really helps. It gives new appreciation for lexically bound `this`. I'm not sure I've ever hear the term “tear-off method,” but I like it.
DeleteYou're right—I ought to have a purpose in mind when exploring these concepts. It will make the exploration more fruitful so I'll plan on doing that. Thanks!