简体   繁体   中英

How to refactor swift code to take protocol and struct types as method arguments

I have 2 functions that have a lot in common, and I want to re-factor my code to remove the repeated logic, however the things that are different are types, specifically a protocol, and and a struct type. The way I can think about it now is that to re-factor this I'd have 1 common method that would take one protocol type as an argument, and one struct type with the restriction that the struct type must implement the protocol 'DataDictionaryStore'. And would return an array of the protocol type passed in as argument 1

I've tried to implement this with generics, but from how I understand it, you still pass an instance as an argument when using generics, not the actual type itself.

The methods I'd like to re-factor in the code below are 'articles()', and 'authors()'

Here's the code (which can be copied to a playground Xcode 7+):

import Foundation

protocol Article {
    var headline: NSString? { get }
}

protocol Author {
    var firstName: NSString? { get }
}

protocol DataDictionaryStore {

    init(dataDictionary: NSDictionary)
}

struct CollectionStruct {

    let arrayOfModels: [NSDictionary]

    //This function is identical to authors() except for return type [Article], and 'ArticleStruct'
    func articles() -> [Article] {

        var articlesArray = [Article]()

        for articleDict in arrayOfModels {
            let articleStruct = ArticleStruct(dataDictionary: articleDict)
            articlesArray.append(articleStruct)
        }
        return articlesArray
    }

    func authors() -> [Author] {

        var authorsArray = [Author]()

        for authorDict in arrayOfModels {
            let authorStruct = AuthorStruct(dataDictionary: authorDict)
            authorsArray.append(authorStruct)
        }
        return authorsArray
    }
}

struct ArticleStruct : Article, DataDictionaryStore {

    var internalDataDictionary: NSDictionary
    init(dataDictionary: NSDictionary) {
        internalDataDictionary = dataDictionary
    }
    var headline: NSString? { return (internalDataDictionary["headline"] as? NSString) }
}

struct AuthorStruct : Author, DataDictionaryStore {

    var internalDataDictionary: NSDictionary
    init(dataDictionary: NSDictionary) {
        internalDataDictionary = dataDictionary
    }
    var firstName: NSString? { return (internalDataDictionary["firstName"] as? NSString) }
}

var collStruct = CollectionStruct(arrayOfModels: [NSDictionary(objects: ["object1", "object2"], forKeys: ["key1", "headline"])])

print(collStruct)
var articles = collStruct.articles()
print(articles)
for article in articles {
    print(article.headline)
}

If there is another way to re-factor this to remove the repeated logic, all suggestions welcome.

It's not exactly an answer to your question, but this might simplify it enough for you to be happy:

func articles() -> [Article] {
    return arrayOfModels.map(ArticleStruct.init)
}

func authors() -> [Author] {
    return arrayOfModels.map(AuthorStruct.init)
}

Based on PEEJWEEJ's answer, this refactor is also worth a shot. Instead of returning a single array, you can return a tuple of authors and articles. If you aren't going to be processing both the authors and articles arrays at once, this method is more expensive. But the syntax is much nicer than the previous solution using generics below.

  func allObjects() -> (authors: [AuthorStruct], articles: [ArticleStruct]) {
    let authors = arrayOfModels.map(AuthorStruct.init)
    let articles = arrayOfModels.map(ArticleStruct.init)
    return(authors, articles)
  } 

You would then call the method like this:

let objects = collection.allObjects()
let authors = objects.authors
let articles = objects.articles

I'm not a huge fan of the clarity here but maybe you can refactor it a bit. It seems to work at least.

func allObjectsOfType<T>(type: T.Type) -> [T] {

   var objectArray = [T]()

   for objectDict in arrayOfModels {
      var objectStruct: T?

      if type == Author.self {
        objectStruct = AuthorStruct(dataDictionary: objectDict) as? T
      } else if type == Article.self {
        objectStruct = ArticleStruct(dataDictionary: objectDict) as? T
      }

      guard objectStruct != nil else {
        continue
      }

      objectArray.append(objectStruct!)
   }

   return objectArray
}

You can then call it like this...

collection.allObjectsOfType(Author)
collection.allObjectsOfType(Article)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM