Say I have a bakery and an inventory of ingredients:
enum Ingredient {
case flower = 1
case sugar = 2
case yeast = 3
case eggs = 4
case milk = 5
case almonds = 6
case chocolate = 7
case salt = 8
}
A case's rawValue
represents the inventory number.
Then I have two recipes:
Now I define a function
func bake(with ingredients: [Ingredient]) -> Cake
Of course I trust my employees, but I still want to make sure they only use the right ingredients to bake a cake. 😉
I could do this by defining two separate enums like this:
enum ChocolateCakeIngredient {
case flower
case sugar
case eggs
case milk
case chocolate
}
enum AlmondCakeIngredient {
case flower
case sugar
case yeast
case eggs
case almonds
case salt
}
and bake a cake like this:
// in chocolate cake class / struct:
func bake(with ingredients: [ChocolateCakeIngredient]) -> ChocolateCake
// in almond cake class / struct:
func bake(with ingredients: [AlmondCakeIngredient]) -> AlmondCake
But then I would have to redefine the same ingredients over and over again as many ingredients are used for both cakes. I really don't want to do that - especially as there are inventory numbers attached to the enum cases as rawValue
s.
That leads me to the question if there is a way in Swift to restrict an enum to certain cases of another enum? Something like (pseudo code):
enum ChocolateCakeIngredient: Ingredient {
allowedCases:
case flower
case sugar
case eggs
case milk
case chocolate
}
enum AlmondCakeIngredient: Ingredient {
allowedCases:
case flower
case sugar
case yeast
case eggs
case almonds
case salt
}
Is a composition like this possible? How can I do it?
Or maybe there is another pattern I can use for this scenario?
From all the comments and answers to this question I figured that the example I picked for this question was a little inappropriate as it didn't boil down the essence of the problem and left a loophole regarding type safety.
As all posts on this page relate to this particular example, I created a new question on Stackoverflow with an example that's easier to understand and hits the nail on its head:
Swift's Enum can have methods. It can have instance methods and you can use it to return expression value for the UI. Let's look at the code above.
Enums can conform to protocolsYes, enums can conform protocols. You can use Swift's own protocols or custom protocols. By using protocols with Enums you can add more capabilities. Sometimes we need to use similar but different object types in the same place.
In Swift language, we have Structs, Enum and Classes. Struct and Enum are passed by copy but Classes are passed by reference. Only Classes support inheritance, Enum and Struct don't.
An enum cannot have both raw values and associated values at the same time. The raw values of an enum must be of the same data type. But associated values can be of any type.
I think you should list ingredients for specific recipes as:
let chocolateCakeIngredients: [Ingredient] = [.flower, ...]
and then just check if that list contains the required ingredient.
You could do something like this in Swift:
enum Ingredients {
struct Flower { }
struct Sugar { }
struct Yeast { }
struct Eggs { }
struct Milc { }
}
protocol ChocolateCakeIngredient { }
extension Sugar: ChocolateCakeIngredient { }
extension Eggs: ChocolateCakeIngredient { }
...
func bake(ingredients: [ChocolateCakeIngredient]) { }
In this example i am using the enum Ingredients
as a namespace for all my ingedients. This also helps with code completion.
Then, create a protocol for each Recipe and conform the ingredients that go in that recipe to that protocol.
While this should solve your question, I am not sure that you should do this. This (and also your pseudo-code) will enforce that no one can pass a ingredient that does not belong into a chocolate cake when baking one. It will, however, not prohibit anyone to try and call bake(with ingredients:)
with an empty array or something similar. Because of that, you will not actually gain any safety by your design.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With