Question

I've recently worked on a reusable network service class for a service-aggregator iOS app. This class should retry a failed request if it was caused by expired user token. Plus, this class will be used between multiple contractors, who will create the aggregated services.

Since the contractors might use different authentication methods, I create a interface / protocol for the class that will manage user's authentication. Here's a sample of my work (in Swift):

BaseNetworkService.swift

class BaseNetworkService {

/**
Request headers that will be used by this instance.
*/
internal var requestHeaders = ["Content-Type": "application/json"]

/**
Request GET method to `URL` with passed `parameters`. Will send success response
in `next` and failure in `error` event of returned `RACSignal` instance.
*/
internal func GET(URL: String, parameters: [String: AnyObject]) -> RACSignal {
    // method implementation here
}

// Rest of HTTP methods here

}

AuthenticatedNetworkService.swift

class AuthenticatedNetworkService: BaseNetworkService {

/**
Used to retrieve authentication-related request headers
and refresh expired user token.
*/
private var authService: AuthenticationProtocol

init(authService: AuthenticationProtocol) {
    self.authService = authService
}

/**
Request GET method to `URL` with passed `parameters`. Will send success response
in `next` and failure in `error` event of returned `RACSignal` instance.

- note: If the request fails because of expired user token, this instance will
refresh current user token, and retry it once again.
*/
override func GET(URL: String, parameters: [String: AnyObject]) -> RACSignal {
    // method implementation here
}

// Rest of HTTP methods here    
}

AuthenticationProtocol.swift

protocol AuthenticationProtocol {

/**
Stores authentication header name in the `key`, and its value in its
corresponding `value`.
*/
internal var authenticationHeaders: [String: String] { get }

/**
Checks whether passed `error` caused by expired user token or not.
*/
func isErrorCausedByExpiredToken(error: NSError) -> Bool

/**
Refresh this instance's `authenticationHeaders`. It send `next:` event from 
returned `RACSignal` if the process succeeds, and `error:` otherwise.
*/
func refreshAuthenticatioHeaders() -> RACSignal

}

I was reading about Design Patterns while working on this, and created a Factory-like class for creating the AuthenticatedNetworkService for the aggregated services. Something around this:

class AuthenticatedNetworkServiceFactory {

/**
Returns network service for Pizza Delivery service.
*/
class func PizzaDeliveryNetworkService() -> AuthenticatedNetworkService {
    let authService = PizzaDeliveryAuthenticationService()
    return AuthenticatedNetworkService(authService: authService)
}

/**
Returns network service for Quick Laundry service.
*/
class func QuickLaundryNetworkService() -> AuthenticatedNetworkService {
    let authService = QuickLaundryAuthenticationService()
    return AuthenticatedNetworkService(authService: authService)
}

/**
Returns network service for Cab Finder service.
*/
class func CabFinderNetworkService() -> AuthenticatedNetworkService {
    let authService = CabFinderAuthenticationService()
    return AuthenticatedNetworkService(authService: authService)
}

}

Yet, after revisiting the GoF book, I found that Factory pattern was meant to return subclasses instead of a the main class. Since I was returning the main class (AuthenticatedNetworkService), is the AuthenticatedNetworkServiceFactory could be considered as a Factory? Or was it just a Helper class?

Was it helpful?

Solution

There is no clear answer to your question, because the definition of a factory is not cast in stone.

Many people consider a factory to be anything that creates something for you so that you don't have to new it yourself. This is dumb in my opinion, but who am I to say that your factories are not true factories ?

The real benefits of using a factory come when:

  • The factory decides which particular subclass to instantiate (as Kilian Foth mentioned) so that you don't have to know the actual class of the object created.

  • The factory is an abstract class itself, so not only you don't have to know the actual class of the object created, but you don't even know the actual class of the factory that you are invoking.

So, according to these real benefits™ your factory is in fact a helper, but if you still go ahead and call it a factory I do not think anybody can blame you. By calling it a factory you are simply advertising the fact that "this thang instantiates stuff".

Licensed under: CC-BY-SA with attribution
scroll top