Reputation: 51715
I have many hungry FoodMonster
s who can munch
many different types of food. The monsters have separate, overloaded methods to munch
each type.
I'd like to build a MonsterPack
which manages many monsters and can feed them all any type of food a FoodMonster
can munch
. I’ve implemented it like this:
protocol Cookie {}
protocol Lettuce {}
protocol FoodMonster {
func munch(Cookie)
func munch(Lettuce)
}
struct MonsterPack {
var monsters = [FoodMonster]()
func feed<T>(food: T) {
for monster in monsters {
monster.munch(food)
}
}
}
Unfortunately, this implementation of MonsterPack.feed
doesn’t compile, since it’s generic and FoodMonster
s cannot, as much as they might want to, munch
any type of value.
Is there any way I can define feed
such that I can call it with any value that a FoodMonster
can munch
?
Upvotes: 2
Views: 134
Reputation: 299455
GoZoner's solution is appealing, but is actually just subclassing. In fact, it's identical to this (notice the lack of T
):
protocol Food {}
protocol Lettuce : Food {}
protocol Cookie : Food {}
protocol FoodMonster {
func munch(food: Food)
}
struct MonsterPack {
var monsters = [FoodMonster]()
func feed(food: Food) {
for monster in monsters {
monster.munch(food)
}
}
}
There's no need for generics at all. Type parameterization doesn't really make sense in this case, since you're just relying on the protocol. There's no reason to say "T, where T is a Food." That's just "Food." (This kind of over-parameterization is really common, and you should be constantly watching for it. It's easy to use generics where you just meant "conforms to.")
If you can use a protocol such that Lettuce and Cookie both have some method in common, that's awesome. But that's not possible in the given example. There is no method at all I can call on a Cookie. And I take from your question is that there are really a specific set of foods out there, and a monster must be able to eat all of them in specific ways. If that's the case, then any real monster is going to wind up with type-checking in munch
. That's not subclassing. That's a sum-type, which in Swift is called an enum.
enum Food {
case Cookie
case Lettuce
}
protocol FoodMonster {
func munch(Food)
}
struct MonsterPack {
var monsters = [FoodMonster]()
func feed(food: Food) {
for monster in monsters {
monster.munch(food)
}
}
}
struct RealMonster {
func feed(food: Food) {
switch food {
case .Cookie: break // Do the cookie thing
case .Lettuce: break // Do the lettuce thing
}
}
}
Now what I'm saying is "if you want to be a monster, you're going to have to be able to munch all of these things which may have no protocol in common." If that's what you mean, then enum is your tool, not subclasses, protocols, or generics.
Upvotes: 2
Reputation: 70185
Start by capturing the commonality between Lettuce
and Cookie
as Food
.
protocol Food {}
protocol Lettuce : Food {}
protocol Cookie : Food {}
then specify that you feed
monsters food
:
func feed<T:Food>(food: T) {
for monster in monsters {
monster.munch(food)
}
}
and make munch
itself generic as:
protocol FoodMonster {
func munch<T:Food> (food:T)
}
Upvotes: 2