Reputation: 5634
Ok I have a protocol called Environment
protocol Environment {
var rootURL: String {get}
}
Then two structs:
struct Production: Environment {
var rootURL = "www.api.mybackend.com/v1"
}
struct Development: Environment {
var rootURL = "www.api.mydevelopmentbackend.com/v1"
}
A settings object with a function that retrieves the environment:
class Settings {
func getEnvironment<T>() -> T where T: Environment {
let environmentRaw = self.retreiveEnvironmentFromLocalStore()
switch environmentRaw {
case 0:
return Development() as! T
case 1:
return Production() as! T
default:
return Development() as! T
}
}
func retreiveEnvironmentFromLocalStore() -> Int {
//Realm, SQLLite, Core Date, I don't really care for this example.
//Let's just say it defaults to returning 1
}
}
I really want to keep moving in a Protocol Oriented Programming direction but now when I call this function on a settings object and try to use the rootURL
property the compiler complains it can't figure out the type. So, to better my understanding and to maybe find a solution:
1) Why does it care about the type if I am accessing a property defined by a protocol that at the very least it knows the returning type conforms to?
2) Structs don't have inheritance. Should I define a base class and forget about generics?
3) Can I make my getEnvironment
function better? I don't like force casting, it seems like a code smell.
4) Am I even using generics correctly here?
EDIT 1:
To be clear, I want one function that returns a struct that I know will have this property.
Upvotes: 0
Views: 327
Reputation: 385680
I really want to keep moving in a Protocol Oriented Programming direction
I suggest instead you try to move in a clear, understandable code direction, regardless of what the trendy orientation is.
Here's your function declaration:
func getEnvironment() -> T where T: Environment
This says that getEnvironment()
will return an object of some type T
, where T
is deduced at compile time based on the code that calls getEnvironment()
.
What types could T
be? It could be either Production
or Development
. For example, you could write:
let e: Production = Settings().getEnvironment()
This lets the compiler deduce that getEnvironment()
returns a Production
(which is an Environment
) at this call site.
But there's a problem: getEnvironment()
might try to return a Development
anyway, based on the random number generator inside retreiveEnvironmentFromLocalStore
. Then you'll get a crash at run time when it fails to cast Development
to Production
.
Why do you think getEnvironment()
needs to be generic at all? Based on the code in your question, it shouldn't be.
import Foundation
protocol Environment {
var rootURL: String {get}
}
struct Production: Environment {
var rootURL = "www.api.mybackend.com/v1"
}
struct Development: Environment {
var rootURL = "www.api.mydevelopmentbackend.com/v1"
}
class Settings {
func getEnvironment() -> Environment {
let environmentRaw = self.retreiveEnvironmentFromLocalStore()
switch environmentRaw {
case 1: return Production()
default: return Development()
}
}
func retreiveEnvironmentFromLocalStore() -> Int {
return Int(arc4random())
}
}
let settings = Settings()
let e = settings.getEnvironment()
Style note: the Swift API Design Guidelines advise us to
Name functions and methods according to their side-effects
- Those without side-effects should read as noun phrases, e.g.
x.distance(to: y), i.successor()
.
Unless the methods in Settings
have important side effects, better names would be environment()
and rawEnvironmentFromLocalStore()
.
Upvotes: 3
Reputation: 271775
Am I even using generics correctly here?
No, I don't think so. You are saying that getEnvironment
will return T
which can be any type that the client code specifies, as long as it implements Environment
. However, the implementation of the method doesn't do this. It will only return two kinds of Environment
s, and which type it returns is not determined by the client code. The type returned depends on what retreiveEnvironmentFromLocalStore
returns. Hence, generics is not suitable in this case.
Since the type of environment it returns is decided by the implementation of the method, instead of the client code, you should make use of polymorphism here - make it return an Environment
instead:
func getEnvironment() -> Environment {
let environmentRaw = self.retreiveEnvironmentFromLocalStore()
switch environmentRaw {
case 0:
return Development()
case 1:
return Production()
default:
return Development()
}
}
Upvotes: 3