#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
}
...
}