Swift: как получить первичные ключи другого типа и с именем из общих хранилищ памяти?

#swift #generics #protocols

#swift #общие #протоколы

Вопрос:

У меня есть два протокола и два объекта, которые их реализуют. Один объект использует name:String в качестве своего первичного ключа, другой использует code:Int.

 protocol AlphaProtocol{
    var name:String {get set}
    init(name: String)
}

protocol BetaProtocol{
    var code: Int {get set}
    init(code:Int)
}

class AlphaObject: AlphaProtocol{
    var name: String

    required init(name: String){
        self.name = name
    }

}

class BetaObject: BetaProtocol{
    var code: Int

    required init(code: Int){
        self.code = code
    }
}
  

Прямо сейчас для хранения этих объектов я использую два разных хранилища памяти, которые реализуют два разных протокола, по одному для каждого типа объекта.

 protocol AlphaStoreProtocol{
    func addObject(object anObject: AlphaProtocol)
    func getObject(name aName:String)->AlphaProtocol?
    func removeObject(name aName: String)
}

protocol BetaStoreProtocol{
    func addObject(object anObject: BetaProtocol)
    func getObject(code aCode:Int)->BetaProtocol?
    func removeObject(code aCode: Int)
}

class AlphaStore{

    fileprivate var objects = [AlphaProtocol]()

    func addObject(object anObject: AlphaProtocol){
        if getObject(name: anObject.name) == nil{
            objects.append(anObject)
        }
    }

    func getObject(name aName:String)->AlphaProtocol?{
        for o in objects{
            if o.name == aName{
                return o
            }
        }
        return nil
    }

    func removeObject(name aName: String){
        self.objects = self.objects.filter({$0.name != aName})
    }
}

class BetaStore: BetaStoreProtocol{

    fileprivate var objects = [BetaProtocol]()

    func addObject(object anObject: BetaProtocol){
        if getObject(code: anObject.code) == nil{
            objects.append(anObject)
        }
    }

    func getObject(code aCode:Int)->BetaProtocol?{
        for o in objects{
            if o.code == aCode{
                return o
            }
        }
        return nil
    }

    func removeObject(code aCode: Int){
        self.objects = self.objects.filter({$0.code != aCode})
    }
}
  

Протестируйте код с использованием двух специально созданных хранилищ.

 let alpha = AlphaObject(name: "Alpha")
let beta = BetaObject(code: 12345)

let alphaStore = AlphaStore()
let betaStore = BetaStore()

alphaStore.addObject(object: alpha)
if (alphaStore.getObject(name: alpha.name) != nil){
    print("alpha object has been added to alphaStore")
}
alphaStore.removeObject(name: alpha.name)
if (alphaStore.getObject(name: alpha.name) == nil){
    print("alpha object has been removed from alphaStore")
}

betaStore.addObject(object: beta)
if (betaStore.getObject(code: beta.code) != nil){
    print("beta object has been added to betaStore")
}
betaStore.removeObject(code: beta.code)
if (betaStore.getObject(code: beta.code) == nil){
    print("beta object has been removed from betaStore")
}
  

Цель: использовать один общий класс для обоих хранилищ, но я застрял, потому что два объекта используют два разных первичных ключа (разного типа и с разными именами), и я не могу просто принудительно использовать общий «идентификатор» в качестве первичного ключа в объектах. Один должен быть назван «name», а другой «code».

Есть ли способ написать методы GetObject и removeObject для приема объектов обоих типов?

 protocol GenericStoreProtocol{
    associatedtype T
    func addObject(object anObject: T)
    // func getObject()->T  // One object use a name:String, the other code:Int as its primary key!
    // func removeObject()  // One object use a name:String, the other code:Int as its primary key!
}

class GenericStore<T>: GenericStoreProtocol{

    fileprivate var objects = [T]()

    func addObject(object anObject: T){
        objects.append(anObject)
    }

    // ...
}

let genericAlphaStore = GenericStore<AlphaProtocol>()
let genericBetaStore = GenericStore<BetaProtocol>()
  

Ответ №1:

Чтобы обобщить проблему, нам нужен магазин, который может:

  • добавляйте элементы любых типов (или те, которые мы указываем).
  • поиск и удаление элементов по идентификатору
  • используйте правильное свойство id для разных хранимых объектов

Во-первых, я бы создал протокол с именем Storable , который имеет identifier вычисляемое свойство. Это должно быть типа Equatable , так как в конечном итоге мы будем использовать сравнения равенства при поиске объектов по идентификатору в нашем Store .

 protocol Storable {
    associatedtype Identifier: Equatable
    var identifier: Identifier { get }
}
  

Теперь мы можем определить классы объектов, которые мы собираемся сохранить ( AlphaObject и BetaObject ). Оба этих класса должны соответствовать своему собственному протоколу, а также Stored протоколу. Здесь вы должны определить, какое свойство следует использовать в качестве идентификатора. Для AlphaObject этого name и для BetaObject этого code . Это могут быть вычисляемые свойства, доступные только для чтения, которые возвращают значения name и code соответственно.

 protocol AlphaProtocol {
    var name: String { get set }
    init(name: String)
}

protocol BetaProtocol {
    var code: Int { get set }
    init(code: Int)
}


class AlphaObject: AlphaProtocol, Storable {
    typealias Identifier = String

    internal var identifier: Identifier {
        return self.name
    }

    var name: String

    required init(name: String) {
        self.name = name
    }

}

class BetaObject: BetaProtocol, Storable {
    typealias Identifier = Int

    internal var identifier: Identifier {
        return self.code
    }

    var code: Int

    required init(code: Int){
        self.code = code
    }
}
  

Наконец, наш Store будет принимать любые объекты, которые есть Storable , и будет получать доступ, вставлять и удалять на основе T указанных identifier .

 class Store<T: Storable> {
    fileprivate var objects = [T]()

    func addObject(object: T) {
        if getObject(identifier: object.identifier) == nil {
            objects.append(object)
        }
    }

    func getObject(identifier: T.Identifier) -> T? {
        for o in objects {
            if o.identifier == identifier {
                return o
            }
        }
        return nil
    }

    func removeObject(identifier: T.Identifier) {
        self.objects = self.objects.filter({$0.identifier != identifier})
    }
}
  

Полный код с тестами!

 protocol Storable {
    associatedtype Identifier: Equatable
    var identifier: Identifier { get }
}

protocol AlphaProtocol {
    var name: String { get set }
    init(name: String)
}

protocol BetaProtocol {
    var code: Int { get set }
    init(code: Int)
}


class AlphaObject: AlphaProtocol, Storable {
    typealias Identifier = String

    internal var identifier: Identifier {
        return self.name
    }

    var name: String

    required init(name: String) {
        self.name = name
    }

}

class BetaObject: BetaProtocol, Storable {
    typealias Identifier = Int

    internal var identifier: Identifier {
        return self.code
    }

    var code: Int

    required init(code: Int){
        self.code = code
    }
}

class Store<T: Storable> {
    fileprivate var objects = [T]()

    func addObject(object: T) {
        if getObject(identifier: object.identifier) == nil {
            objects.append(object)
        }
    }

    func getObject(identifier: T.Identifier) -> T? {
        for o in objects {
            if o.identifier == identifier {
                return o
            }
        }
        return nil
    }

    func removeObject(identifier: T.Identifier) {
        self.objects = self.objects.filter({$0.identifier != identifier})
    }
}


/* Tests */
let alpha = AlphaObject(name: "Alpha")
let beta = BetaObject(code: 12345)

let alphaStore = Store<AlphaObject>()
let betaStore = Store<BetaObject>()

alphaStore.addObject(object: alpha)
if (alphaStore.getObject(identifier: alpha.name) != nil){
    print("alpha object has been added to alphaStore")
}
alphaStore.removeObject(identifier: alpha.name)
if (alphaStore.getObject(identifier: alpha.name) == nil){
    print("alpha object has been removed from alphaStore")
}

betaStore.addObject(object: beta)
if (betaStore.getObject(identifier: beta.code) != nil){
    print("beta object has been added to betaStore")
}
betaStore.removeObject(identifier: beta.code)
if (betaStore.getObject(identifier: beta.code) == nil){
    print("beta object has been removed from betaStore")
}
  

Комментарии:

1. Поддерживаю вас, потому что ваш ответ примерно такой же, как тот, который Кевин предоставил час назад. Спасибо вам обоим. Я тестирую это, и это работает!

2. Нет проблем. Удачи! 🙂

Ответ №2:

Я не могу просто принудительно использовать общий «идентификатор» в качестве первичного ключа в объектах.

Да, вы вполне можете, если используете один протокол вместо двух несвязанных ( AlphaProtocol и BetaProtocol ).

 protocol KeyedObject {
    associatedtype PrimaryKey : Equatable
    var key: PrimaryKey { get }
}
  

Просто сделайте так, чтобы ваши объекты соответствовали этому протоколу; они могут объявлять любой равнозначный тип, который вам требуется для ключа, им просто нужно предоставить какой-то способ доступа к нему.

 class AlphaObject: KeyedObject {
    typealias PrimaryKey = String
    var name: String

    required init(name: String) {
        self.name = name
    }

    var key: String {
        return self.name
    }
}
  

Затем вы можете использовать простой универсальный класс, который содержит только предоставленные вами объекты:

 class GenericStore<T : KeyedObject> {

    fileprivate var objects = [T]()

    func addObject(object anObject: T){
        objects.append(anObject)
    }

    func getObject(key: T.PrimaryKey) -> T? {
        for o in objects{
            if o.key == key {
                return o
            }
        }
        return nil
    }
    ...
}