I have a classic implementation of Chain of Responsibility pattern with the following code:
protocol Request {
var firstName: String? { get }
var lastName: String? { get }
var email: String? { get }
var password: String? { get }
var repeatedPassword: String? { get }
}
protocol Handler {
var next: Handler? { get }
func handle(_ request: Request) -> LocalizedError?
}
class BaseHandler: Handler {
var next: Handler?
init(with handler: Handler? = nil) {
self.next = handler
}
func handle(_ request: Request) -> LocalizedError? {
return next?.handle(request)
}
}
So I can create a PermissionHandler, LocationHandler, LoginHandler, a SignupHandler and combine them in chain. So far so good.
Now I want to create a Chain of Responsibility for other purposes, let's say a MediaContentPlayer CoR with different types of MediaContentHandlers and I thought to refactor and reuse the base code using generics.
So I started from the Handler protocol:
protocol Handler {
associatedtype HandlerRequest
var next: Handler? { get }
func handle(_ request: HandlerRequest) -> LocalizedError?
}
but I get error "Protocol 'Handler' can only be used as a generic constraint because it has Self or associated type requirements".
Is there a way to reference the protocol inside the protocol itself when using associatedtype? Or another way to make the above code not dependent on a specific type?
You would look after something like this:
protocol Handler {
// ...
var next: some Handler<HandlerRequest == Self.HandlerRequest>? { get }
// ...
}
The problem here is that Swift doesn't (yet) have support for opaque return types that are protocols with associated types.
A solution to this limitation is to use a type eraser for the next
property:
protocol Handler {
associatedtype HandlerRequest
// shift the generic from a protocol with associated type to a generic struct
var next: AnyHandler<HandlerRequest>? { get }
func handle(_ request: HandlerRequest) -> LocalizedError?
}
struct AnyHandler<HandlerRequest> {
private var _handle: (HandlerRequest) -> LocalizedError?
private var _next: () -> AnyHandler<HandlerRequest>?
init<H: Handler>(_ handler: H) where H.HandlerRequest == HandlerRequest {
_next = { handler.next }
_handle = handler.handle
}
}
extension AnyHandler: Handler {
var next: AnyHandler<HandlerRequest>? { return _next() }
func handle(_ request: HandlerRequest) -> LocalizedError? {
return _handle(request)
}
}
This way you can benefit both the protocol, and having the next
property tied to the handler request type that you need.
As an added bonus for using protocols, you can still benefit the default implementation from the base class:
extension Handler {
func handle(_ request: HandlerRequest) -> LocalizedError? {
return next?.handle(request)
}
}
That's how cool protocols are in Swift, they allow you to avoid classes and use value types for as much as possible, by improving the concept of polymorphism.
Usage example:
struct LoginHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the login validation
}
}
struct SignupHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the signup validation
}
}
extension Handler {
// Helper function to easily create a type erased AnyHandler instance
func erase() -> AnyHandler<HandlerRequest> {
return AnyHandler(self)
}
}
// now let's put the handers to work:
let loginHandler = LoginHandler()
let signupHandler = SignupHandler(next: loginHandler.erase())
let someOtherAccountHandler = SomeOtherAccountHandler(next: signupHandler.erase())