We can leverage protocol extensions to provide default implementations for our protocols.
Let's go back to where we were before implementing this whole inheritance chain:
protocol ServicesFactoryType {
func getPushService() -> PushNotificationService
func getUserLocationService() -> UserLocationService
/* Add more services here as your project grows */
}
We can just extend that protocol, as follows:
extension ServicesFactoryType {
func getUserLocationService() -> UserLocationService {
return CommonUserLocationService()
}
}
We're done—no additional changes, no override keyword to add to the implementations. This strategy has one major drawback, however, compared to the inheritance strategy. In the specific iOS and macOS implementations of our ServicesFactoryType, it is impossible to access the default one, unlike with inheritance, where it's easy to call super.
You can still work around this issue by adding an extra method on the extension, for example, as follows:
extension ServicesFactoryType {
func getUserLocationService() -> UserLocationService {
return self.defaultUserLocationService()
}
func defaultUserLocationService() -> UserLocationService {
return CommonUserLocationService()
}
}
And then in your platform-specific implementation, you can leverage the default UserLocationService:
struct iOSServicesFactory: ServicesFactory {
func getPushService() -> PushNotificationService {
/* original impl. */
}
func getUserLocationService() -> UserLocationService {
if /* a particular test */ {
return defaultUserLocationService()
}
fatalError("Not supported")
}
}
The later method using protocol extensions would be the preferred Swift way to write default implementations on your abstract factories. It is more powerful, safer, and lets us use struct instead of class.
While in this particular case we could use either classes or structs, in your projects, choosing the right abstraction is up to you.