如何获得具有泛型类型的 class 接受相同泛型类型的不同数组?

How to get a class with generic type accept an array of different by same generic types?

我正在努力学习和理解 swift 中相关类型的协议。

同时,我正在学习 SwiftUI 并参加 Udemy 的课程。

我们要构建的应用程序是一个咖啡订单应用程序。

尽管如此,我并没有遵循 "T" 的教程,因为我尝试以不同的方式构建应用程序,因此我可以学会自己思考。该应用程序没什么特别的。

本教程不使用泛型和协议来表示或构造数据。这只是一个展示SwiftUI的教程。

我创建了一个名为 Coffee 的协议,它具有 CupSize 关联类型。

每种咖啡 Cappuccino、Espresso 和 Brewed Coffee 均符合咖啡协议。

protocol Priceable {
  var cost: Double { get }
}

protocol Coffee {
  associatedtype CupSize
  var cupSize: CupSize { get }
  init(cupSize: CupSize)
}

enum EspressoCupSize {
  case small
}

struct Espresso: Coffee, Priceable {
  var cupSize = EspressoCupSize.small
  var cost: Double { return 3.00 }
}

enum BrewedCoffeeCupSize {
  case small
  case medium
  case large
}

struct BrewedCoffee: Coffee, Priceable {
  var cupSize: BrewedCoffeeCupSize
  var cost: Double {
    switch self.cupSize {
      case .small: return 1.00
      case .medium: return 2.00
      case .large: return 3.00
    }
  }
}

enum CappuccinoCupSize {
  case small
  case medium
  case large
}

struct Cappuccino: Coffee, Priceable {
  var cupSize: CappuccinoCupSize
  var cost: Double {
    switch self.cupSize {
      case .small: return 2.00
      case .medium: return 3.00
      case .large: return 4.00
    }
  }
}

然后,我创建了一个 Order 结构和一个 OrderManager class。

Order 结构具有泛型,需要是 Priceable 项目。 通用定价商品的想法是在未来支持其他商品,以防我想扩展应用程序...而不仅仅是咖啡。

OrderManager背后的思想是跟踪所有订单并管理订单的CRUD操作(仍然需要实现删除、读取和更新)。

struct Order<Item: Priceable> {
  var name: String
  var item: Item
}

class OrderManager<Item> {
  private var orders: [Item] 

  init(orders: [Item]) {
    self.orders = orders
  }

  func add(_ order: Item) {
    self.orders.append(order)
  } 
}

我的问题是使用 OrderManager。

let maryOrder = Order(name: "Mary", item: Espresso())
let sueOrder = Order(name: "Sue", item: BrewedCoffee(cupSize: .medium))

// Dummy Structure
struct Person {}

let orderManager = OrderManager(orders: [
  maryOrder,
  sueOrder,
  Person() // This works!!! Which is not what I want.
])

我希望 OrderManager 的通用类型是 Order,但由于 Order 有其自己的 Priceable 通用类型,我似乎找不到正确的答案或正确的语法。

我尝试让 OrderManager 工作的事情

class OrderManager<Order> {} // Does not work because Order needs a generic type
class OrderManager<Order<Priceable>> // Still does not work
class OrderManager<Item: Priceable, Order<Item> {} // Still does not work.
// and I tried other solutions, but I cannot get this to work
// Also, when I think I got the right syntax, I cannot add Mary and Sue's orders to
// OrderManager because Mary's item is Espresso and Sue's item is BrewedCoffee

如何让 OrderManager 只接受一组订单?

您不需要在 class 上定义泛型类型,您应该将其放在如下方法中:

class OrderManager {
   func doOrder<T: Priceable, L: Protocol2 >(obj: T) -> L {
     // Use obj as a Priceable model
     // return a Protocol2 model
   }
}

要发送到 class,例如您只需发送您的模型

varProtocol2 = OrderManager.doOrder(obj: maryOrder.item)

这是一个有两个通用对象的例子

protocol prot1 {
    var a: Int {get set}
}

protocol protRet {
    var b: String {get set}
    init()
}

struct prot1Struct: prot1 {
    var a: Int
}

struct prot2Struct: protRet {
    init() {
        b = ""
    }
    var b: String
}

class Manage {
    func Do<T: prot1, L: protRet>(obj: T) -> L {
        var ret: L = L()
        ret.b = "\(obj.a)"
        return ret
    }
}


var obj1: prot2Struct?
var paramItem = prot1Struct(a: 10)
obj1 = Manage().Do(obj: paramItem)

此外,如果您想在 Class 上使用它,您可以按照以下方式进行操作:

class manageb<T: prot1, L: protRet> {
    func Do(obj: T) -> L {
        var ret: L = L()
        ret.b = "\(obj.a)"
        return ret
    }
}

var obj1: prot2Struct?
var paramItem = prot1Struct(a: 10)

let classB = manageb<prot1Struct, prot2Struct>()
obj1 = classB.Do(obj: paramItem)

你想尝试泛型是件好事,但现在不是时候。你说:

The idea ... is to support other items in the future in case I want to expand the app...not just coffee.

但是您不需要 generic。管理订单的唯一要求是订单的商品是可定价的。 Priceable是已经的一种;您不需要添加 generic 类型。

struct Order {
  var name: String
  var item: Priceable
}

class OrderManager {
    private var orders: [Order]

  init(orders: [Order]) {
    self.orders = orders
  }

  func add(_ order: Order) {
    self.orders.append(order)
  }
}

I'm in the process trying to learn and understand generics with associated types in swift.

没有 "generics with associated types in Swift." 有泛型,有关联类型协议 (PAT)。它们有一些共同点,但是用于非常不同事物的概念却截然不同。

泛型的目的是允许类型随调用者选择的类型而变化

PAT 的目的是允许现有的算法使用类型,使用实施者选择的类型。鉴于此,Coffee 作为协议没有意义。您试图将其视为异构类型。这不是 PAT。 PAT 是一个挂钩,允许算法使用类型。

class OrderManager<Item> { ... }

这表示 OrderManager 可以容纳 任何东西;从字面上看任何东西。它不一定是昂贵的。在您的情况下,Item 被强制转换为 Any,这绝对不是您想要的(以及为什么 Person 在不应该工作的时候工作)。但是将 OrderManager 绑定到某些项目类型并没有多大意义。您真的想要一个 OrderManager for Coffee 和一个完全不同的 OrderManager for Espresso 吗?这根本不符合你在做什么。 OrderManager 应该处理任何订单,对吗?

这里真的不可能确定您确实需要哪些协议和泛型,因为您从不使用 OrderManager.orders 做任何事情。从调用代码开始。从没有泛型或协议开始。让代码重复,然后将重复提取到泛型和协议中。如果您心中没有清晰的算法(用例),您不应该创建协议。

请参阅 matt 的回答作为起点,但我确信这还不足以解决您的问题。您可能需要更多东西(例如最有可能的项目名称)。从一些简单的结构(EspressoBrewedCoffee 等)开始,然后开始编写您的调用代码,然后您可能会有更多问题我们可以讨论。


对于你问的如何攻克这类问题,我会这样开头。

首先,我们有一些待售商品。我以最明显的方式对它们进行建模:

// An Espresso has no distinguishing characteristics.
struct Espresso {}

// But other coffees have a size.
enum CoffeeSize: String {
    case small, medium, large
}

// You must know the size in order to create a coffee. You don't need to know
// its price, or its name, or anything else. But you do have to know its size
// or you can't pour one. So "size" is a property of the type.
struct BrewedCoffee {
    let size: CoffeeSize
}

struct Cappuccino {
    let size: CoffeeSize
}

完成!

好的,还没有真正完成,但说真的,有点完成了。我们现在可以制作咖啡饮料了。在您有其他问题需要解决之前,您就真的完成了。但是我们还有另一个问题:

我们要构建一个订单,以便给客户开具账单。订单由项目组成。物品有名称和价格。可以将新事物添加到订单中,我可以获得整个事物的文本表示。所以我们先建模我们需要的:

struct Order {
    private (set) var items: [Item]
    mutating func add(_ item: Item) {
        items.append(item)
    }

    var totalPrice: Decimal { items.map { [=12=].price }.reduce(0, +) }
    var text: String { items.map { "\([=12=].name)\t\([=12=].price)" }.joined(separator: "\n") }
}

为了实现它,我们需要一个提供名称和价格的协议:

protocol Item {
    var name: String { get }
    var price: Decimal { get }
}

现在我们希望 Espresso 成为一个项目。所以我们应用追溯建模使其成为一个:

extension Espresso: Item {
    var name: String { "Espresso" }
    var price: Decimal { 3.00 }
}

BrewedCoffee 也一样:

extension BrewedCoffee {
    var name: String { "\(size.rawValue.capitalized) Coffee" }
    var price: Decimal {
        switch size {
        case .small: return 1.00
        case .medium: return 2.00
        case .large: return 3.00
        }
    }
}

当然还有卡布奇诺...但是您知道,当我开始写作时,我真的很想剪切和粘贴 BrewedCoffee。这表明那里可能隐藏着一个协议。

// Just a helper to make syntax prettier.
struct PriceMap {
    var small: Decimal
    var medium: Decimal
    var large: Decimal
}

protocol SizedCoffeeItem: Item {
    var size: CoffeeSize { get }
    var baseName: String { get }
    var priceMap: PriceMap { get }
}

这样,我们就可以实现项目的要求了:

extension SizedCoffeeItem {
    var name: String { "\(size.rawValue.capitalized) \(baseName)" }
    var price: Decimal {
        switch size {
        case .small: return priceMap.small
        case .medium: return priceMap.medium
        case .large: return priceMap.large
        }
    }
}

现在一致性不需要代码重复。

extension BrewedCoffee: SizedCoffeeItem {
    var baseName: String { "Coffee" }
    var priceMap: PriceMap { PriceMap(small: 1.00, medium: 2.00, large: 3.00) }
}

extension Cappuccino: SizedCoffeeItem {
    var baseName: String { "Cappuccino" }
    var priceMap: PriceMap { PriceMap(small: 2.00, medium: 3.00, large: 4.00) }
}

这两个例子是协议的两种不同用途。第一个是实现异构集合 ([Item])。这些类型的协议不能有关联类型。二是方便类型间的代码共享。这几种可以。但在这两种情况下,我都没有添加任何协议,直到我有一个明确的用例:我需要能够将它们添加到 Order 并取回某些类型的数据。这引导我们前进的每一步。

作为起点,对您的数据进行简单建模并设计您的算法。然后使用协议使您的数据适应算法。协议来得晚,不早。