[swift-evolution] [Proposal] Factory Initializers
Douglas Gregor
dgregor at apple.com
Thu Dec 17 18:40:18 CST 2015
> On Dec 17, 2015, at 1:41 PM, Riley Testut via swift-evolution <swift-evolution at swift.org> wrote:
>
> Recently, I proposed the idea of adding the ability to implement the "class cluster" pattern from Cocoa (Touch) in Swift. However, as we discussed it and came up with different approaches, it evolved into a functionality that I believe is far more beneficial to Swift, and subsequently should be the focus of its own proposal. So here is the improved (pre-)proposal:
Thanks for working on this!
> # Factory Initializers
>
> The "factory" pattern is common in many languages, including Objective-C. Essentially, instead of initializing a type directly, a method is called that returns an instance of the appropriate type determined by the input parameters. Functionally this works well, but ultimately it forces the client of the API to remember to call the factory method instead, rather than the type's initializer. This might seem like a minor gripe, but given that we want Swift to be as approachable as possible to new developers, I think we can do better in this regard.
For the record, the Swift compiler and programming model actually already has factory initializers, but one can only get to them by writing a factory method in Objective-C that then gets imported as a factory initializer into Swift. For example:
@interface ABCFoo : NSObject
+(nonnull ABCFoo *)fooWithString:(nonnull NSString *)string;
@end
comes in as a factory initializer (using your proposed syntax):
factory init(string: String)
> Rather than have a separate factory method, I propose we build the factory pattern right into Swift, by way of specialized “factory initializers”. The exact syntax was proposed by Philippe Hausler from the previous thread, and I think it is an excellent solution:
>
> class AbstractBase {
> public factory init(type: InformationToSwitchOn) {
> return ConcreteImplementation(type)
> }
> }
>
> class ConcreteImplementation : AbstractBase {
>
> }
FWIW, there’s a historical syntax here of
public init(type: InformationToSwitchOn) -> AbstractBase
I’m not attached to the “->” syntax at all, but there were two reasons for using it for factory initializers:
1) It more strongly indicates the “function” nature of this initializer, because you’re expected to return a “self” rather than initializing the “self” you’ve been given, which is very different from all other initializers. “Factory” also says that if you know what factory initializers are, but “->” is more firmly embedded in the language semantics to mean “you should return one of these”.
2) It lets you distinguish between factory initializers that return “Self” (i.e., the type used to perform the initialization) and those that return some concrete upper bound (like your example). The distinction affects inheritance of initializers and, by extension, whether the initializer can satisfy an initializer requirement in a protocol. For example, in your example above, I would expect us to reject
ConcreteImplementation(type: InformationToSwiftOn())
because we have named “ConcreteImplementation” but the factory initializer is only guaranteed to provide an “AbstractBase”. If instead one had:
public init(type: InformationToSwiftOn) -> Self { … }
then
ConcreteImplementation(type: InformationToSwiftOn())
is well-formed and produces a ConcreteImplementation while
AbstractBase(type: InformationToSwiftOn())
is also well-formed and produces an AbstractBase. In other words, the factory initializer written “-> Self” is guaranteed to be inherited. Whether the others are inherited is more complicated.
> Why exactly would this be useful in practice? In my own development, I’ve come across a few places where this would especially be relevant:
>
> ## Class Cluster/Abstract Classes
> This was the reasoning behind the original proposal, and I still think it would be a very valid use case. The public superclass would declare all the public methods, and could delegate off the specific implementations to the private subclasses. Alternatively, this method could be used as an easy way to handle backwards-compatibility: rather than litter the code with branches depending on the OS version, simply return the OS-appropriate subclass from the factory initializer. Very useful.
At least some of these cases would better be addressed by protocol initializers, below, because there might be no reason at all for the OS-appopriate “subclasses” to actually share a superclass (nor should they have to be classes!).
>
> ## Protocol Initializers
> Proposed by Brent Royal-Gordon, we could use factory initializers with protocol extensions to return the appropriate instance conforming to a protocol for the given needs. Similar to the class cluster/abstract class method, but can work with structs too. This would be closer to the factory method pattern, since you don’t need to know exactly what type is returned, just the protocol it conforms to.
Seems plausible.
- Doug
More information about the swift-evolution
mailing list