说我有面包店和成分清单:
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
}
案例rawValue
代表库存编号。
然后我有两个食谱:
现在我定义一个函数
func bake(with ingredients: [Ingredient]) -> Cake
当然,我相信我的员工,但我仍然想确保他们只使用正确的食材来烘焙蛋糕。
我可以通过定义两个单独的枚举来完成此操作:
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
}
并烤一个像这样的蛋糕:
// in chocolate cake class / struct:
func bake(with ingredients: [ChocolateCakeIngredient]) -> ChocolateCake
// in almond cake class / struct:
func bake(with ingredients: [AlmondCakeIngredient]) -> AlmondCake
但随后我不得不一遍又一遍地重新定义相同的成分,因为两种蛋糕都使用了许多成分。我真的不想这样做 - 特别是因为枚举案例附加了rawValue
的库存号。
这引出了一个问题,如果Swift中有一种方法将枚举限制为另一个枚举的某些情况?像(伪代码):
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
}
这样的作品可能吗?我该怎么办?
或许我可以在这种情况下使用另一种模式?
从这个问题的所有评论和答案中,我认为我为这个问题选择的例子有点不合适,因为它没有归结为问题的本质,并留下了关于类型安全的漏洞。
由于此页面上的所有帖子都与此特定示例相关,因此我在Stackoverflow上创建了一个新问题,其中一个示例更易于理解,并且可以直接触及:
答案 0 :(得分:2)
我认为你应该列出具体食谱的成分:
let chocolateCakeIngredients: [Ingredient] = [.flower, ...]
然后只检查该列表是否包含所需的成分。
答案 1 :(得分:1)
我不相信可以在编译时执行这样的检查。以下是构建代码以在运行时执行此操作的一种方法:
enum Ingredient: Int {
case flour = 1
case sugar = 2
case yeast = 3
case eggs = 4
case milk = 5
case almonds = 6
case chocolate = 7
case salt = 8
}
protocol Cake {
init()
static var validIngredients: [Ingredient] { get }
}
extension Cake {
static func areIngredientsAllowed(_ ingredients: [Ingredient]) -> Bool {
for ingredient in ingredients {
if !validIngredients.contains(ingredient) {
return false
}
}
return true
}
}
class ChocolateCake: Cake {
required init() {}
static var validIngredients: [Ingredient] = [.flour, .sugar, .eggs, .milk, .chocolate]
}
class AlmondCake: Cake {
required init() {}
static var validIngredients: [Ingredient] = [.flour, .sugar, .yeast, .eggs, .almonds, .salt]
}
bake
方法如下所示:
func bake<C: Cake>(ingredients: [Ingredient]) -> C {
guard C.areIngredientsAllowed(ingredients) else {
fatalError()
}
let cake = C()
// TODO: Let's bake!
return cake
}
现在我可以说:
let almondCake: AlmondCake = bake(ingredients: ingredients)
...并确保只使用有效成分。
答案 2 :(得分:1)
你可以在Swift中做这样的事情:
Ingredients
在这个例子中,我使用enum bake(with ingredients:)
作为我所有ingedients的命名空间。这也有助于代码完成。
然后,为每个配方创建一个协议,并使该配方中的成分符合该协议。
虽然这可以解决你的问题,但我不确定你应该这样做。这个(以及你的伪代码)将强制执行当烘焙时没有人可以传递不属于巧克力蛋糕的成分。但是,它不会禁止任何人尝试使用空数组或类似的东西来调用oac.bat 09:09
klm.txt 9:00
。因此,您的设计实际上不会获得任何安全性。
答案 3 :(得分:1)
或许我可以在这种情况下使用另一种模式?
另一种方法是让您的Ingredient
成为OptionSet
类型(符合协议OptionsSet
的类型):
E.g。
struct Ingredients: OptionSet {
let rawValue: UInt8
static let flower = Ingredients(rawValue: 1 << 0) //0b00000001
static let sugar = Ingredients(rawValue: 1 << 1) //0b00000010
static let yeast = Ingredients(rawValue: 1 << 2) //0b00000100
static let eggs = Ingredients(rawValue: 1 << 3) //0b00001000
static let milk = Ingredients(rawValue: 1 << 4) //0b00010000
static let almonds = Ingredients(rawValue: 1 << 5) //0b00100000
static let chocolate = Ingredients(rawValue: 1 << 6) //0b01000000
static let salt = Ingredients(rawValue: 1 << 7) //0b10000000
// some given ingredient sets
static let chocolateCakeIngredients: Ingredients =
[.flower, .sugar, .eggs, .milk, .chocolate]
static let almondCakeIngredients: Ingredients =
[.flower, .sugar, .yeast, .eggs, .almonds, .salt]
}
应用于您的bake(with:)
示例,其中employee / dev尝试在bake(with:)
的正文中实现巧克力蛋糕的烘焙:
/* dummy cake */
struct Cake {
var ingredients: Ingredients
init(_ ingredients: Ingredients) { self.ingredients = ingredients }
}
func bake(with ingredients: Ingredients) -> Cake? {
// lets (attempt to) bake a chokolate cake
let chocolateCakeWithIngredients: Ingredients =
[.flower, .sugar, .yeast, .milk, .chocolate]
// ^^^^^ ups, employee misplaced .eggs for .yeast!
/* alternatively, add ingredients one at a time / subset at a time
var chocolateCakeWithIngredients: Ingredients = []
chocolateCakeWithIngredients.formUnion(.yeast) // ups, employee misplaced .eggs for .yeast!
chocolateCakeWithIngredients.formUnion([.flower, .sugar, .milk, .chocolate]) */
/* runtime check that ingredients are valid */
/* ---------------------------------------- */
// one alternative, invalidate the cake baking by nil return if
// invalid ingredients are used
guard ingredients.contains(chocolateCakeWithIngredients) else { return nil }
return Cake(chocolateCakeWithIngredients)
/* ... or remove invalid ingredients prior to baking the cake
return Cake(chocolateCakeWithIngredients.intersection(ingredients)) */
/* ... or, make bake(with:) a throwing function, which throws and error
case containing the set of invalid ingredients for some given attempted baking */
}
使用给定的可用巧克力蛋糕成分调用bake(with:)
:
if let cake = bake(with: Ingredients.chocolateCakeIngredients) {
print("We baked a chocolate cake!")
}
else {
print("Invalid ingredients used for the chocolate cake ...")
} // Invalid ingredients used for the chocolate cake ...
答案 4 :(得分:0)
如果配方数量始终相同,您可以使用枚举中的函数:
enum Ingredient {
case chocolate
case almond
func bake() -> Cake {
switch self {
case chocolate:
print("chocolate")
/*
return a Chocolate Cake based on:
500g flower
300g sugar
3 eggs
200ml milk
200g chocolate
*/
case almond:
print("almond")
/*
return an Almond Cake based on:
300g flower
200g sugar
20g yeast
200g almonds
5 eggs
2g salt
*/
}
}
}
用法:
// bake chocolate cake
let bakedChocolateCake = Ingredient.chocolate.bake()
// bake a almond cake
let bakedAlmondCake = Ingredient.almond.bake()
如果配方数量是可变的 - 这就是我的假设 - 我通过使用分离的模型类来欺骗了一点:)
如下:
class Recipe {
private var flower = 0
private var sugar = 0
private var yeast = 0
private var eggs = 0
private var milk = 0
private var almonds = 0
private var chocolate = 0
private var salt = 0
// init for creating a chocolate cake:
init(flower: Int, sugar: Int, eggs: Int, milk: Int, chocolate: Int) {
self.flower = flower
self.sugar = sugar
self.eggs = eggs
self.milk = milk
self.chocolate = chocolate
}
// init for creating an almond cake:
init(flower: Int, sugar: Int, yeast: Int, almonds: Int, eggs: Int, salt: Int) {
self.flower = flower
self.sugar = sugar
self.yeast = yeast
self.almonds = almonds
self.eggs = eggs
self.salt = salt
}
}
enum Ingredient {
case chocolate
case almond
func bake(recipe: Recipe) -> Cake? {
switch self {
case chocolate:
print("chocolate")
if recipe.yeast > 0 || recipe.almonds > 0 || recipe.salt > 0 {
return nil
// or maybe a fatal error!!
}
// return a Chocolate Cake based on the given recipe:
case almond:
print("almond")
if recipe.chocolate > 0 {
return nil
// or maybe a fatal error!!
}
// return an Almond Cake based on the given recipe:
}
}
}
用法:
// bake chocolate cake with a custom recipe
let bakedChocolateCake = Ingredient.chocolate.bake(Recipe(flower: 500, sugar: 300, eggs: 3, milk: 200, chocolate: 200)
// bake almond cake with a custom recipe
let bakedAlmondCake = Ingredient.chocolate.bake(Recipe(flower: 300, sugar: 200, yeast: 20, almonds: 200, eggs: 5, salt: 2))
即使这些不是您的案例的最佳解决方案,我希望它有所帮助。