I create a lexikon and a navigation for ZOO. I use MVVM architecture supported by ReactiveSwift. I have a few questions about my practises and whether I could write my repositories better and more clearly.
I would like to know whether is it good to create a generic Repository class and inherited class from this class for loading one kind of entities. Is it suitable to create a parent Repository class and create child classes implementing a suitable protocol used for dependency injection for each kind of entity in this case?
I also work with three types of bindings with animal: FoodBinding
- binding between an animal and a kind of food which the animal eats,
BiotopeBinding
- binding between an animal and a biotope in which the animal lives,
ContinentBinding
- binding between an animal and a continent in which the animal lives.
I would like to ask you whether is it good to create one class for each type of binding or create a parent class for all kinds of bindings. Is it good to create one repository class for all types of bindings or create a class for each kind of binding? If I would create a class for each kind of binding, is it good to have a parent class for all kinds of binding with a generic functions for all kinds of bindings?
Here is the content of the file AppDependency.swift in Service/Dependency directory. The class AppDependency is used for injection of dependencies in whole application (ViewModels, FlowCoordinators etc.). I think that it is a good practice to use protocols for each service which we want to inject. Is it really a good practice in my case, or not?
import UIKit
/**
This class is used for injection of some dependencies (especially repositories and services) to other code (especially ViewModels and ViewControllers)
*/
class AppDependency {
private init() {}
/// instance of AppDependency class used for dependency injection
static let shared = AppDependency()
lazy var animalRepository:AnimalRepositoring = AnimalRepository()
lazy var localityRepository:LocalityRepositoring = LocalityRepository()
lazy var speechService: SpeechServicing = SpeechService(language: Locale.current.identifier)
lazy var biotopeBindingRepository: BiotopeBindingRepositoring = BiotopeBindingRepository()
lazy var foodBindingRepository: FoodBindingRepositoring = FoodBindingRepository()
lazy var continentBindingRepository: ContinentBindingRepositoring = ContinentBindingRepository()
}
extension AppDependency: HasAnimalRepository{}
extension AppDependency: HasLocalityRepository{}
extension AppDependency: HasSpeechService{}
extension AppDependency: HasContinentBindingsRepository{}
extension AppDependency: HasBiotopeBindingsRepository{}
extension AppDependency: HasFoodBindingsRepository{}
Here is the content of the file Repository.swift in Service/Repository directory
import UIKit
import ReactiveSwift
class Repository<T: LoadedEntity>{
/// array of entities which is used for storing information loaded from the NodeJS server
lazy var entities = MutableProperty<[T]?>([])
/// date of last editation or loading of array of entities
private var lastEdited: Date = Date()
/**
This function ensures loading data from server and
*/
func loadAndSaveDataIfNeeded(){
// if data were loaded unsuccessfully after the starting of the application
if (entities.value == nil){
// updating of result in case that data could not be loaded previously
let result: [T]? = APIService.getEntitiesGotByAPICall(relativeUrl: T.relativeUrl)
if (result != nil){
entities.value = result
self.lastEdited = Date()
}
// after the starting of the application
} else if (entities.value!.count == 0){
// loading of the data in the beginning
let result: [T]? = APIService.getEntitiesGotByAPICall(relativeUrl: T.relativeUrl)
entities.value = result
self.lastEdited = Date()
// in other cases - data were loaded successfully last time
} else {
// updating of loaded data each 3 hours
let threeHoursAgo:Date = Date(timeIntervalSinceNow: -3*60*60)
// updating of loaded data when data was loaded more than 3 hours ago
if (self.lastEdited < threeHoursAgo){
let result: [T]? = APIService.getEntitiesGotByAPICall(relativeUrl: T.relativeUrl)
if (result != nil){
entities.value = result
self.lastEdited = Date()
}
}
}
}
}
Here is the content of the file BindingRepository.swift in Service/Repository directory
//
// BindingRepository.swift
// courseworkZoo
//
// Created by Daniel Šup on 22/06/2019.
// Copyright © 2019 Daniel Šup. All rights reserved.
//
import UIKit
import ReactiveSwift
/**
This protocol is created for the dependency injection. This protocol ensures loading the repository for working with bindings between biotopes and animals.
*/
protocol BiotopeBindingRepositoring{
var entities: MutableProperty<[BiotopeBinding]?> { get }
func loadAndSaveDataIfNeeded()
func findBindingsWithAnimal(animal: Int) -> [BiotopeBinding]?
}
/**
This protocol is created for the dependency injection. This protocol ensures loading the repository for working with bindings between kinds of food and animals.
*/
protocol FoodBindingRepositoring{
var entities: MutableProperty<[FoodBinding]?> { get }
func loadAndSaveDataIfNeeded()
func findBindingsWithAnimal(animal: Int) -> [FoodBinding]?
}
/**
This protocol is created for the dependency injection. This protocol ensures loading the repository for working with bindings between continents and animals.
*/
protocol ContinentBindingRepositoring{
var entities: MutableProperty<[ContinentBinding]?> { get }
func loadAndSaveDataIfNeeded()
func findBindingsWithAnimal(animal: Int) -> [ContinentBinding]?
}
/**
This class is a child of Repository class in which it is stored array of entities with the name entities of type MutableProperty. This class ensures finding bindings to the animal given by an identificator.
*/
class BindingRepository<B: Bindable> : Repository<B>{
/**
This function finds bindings between the animal given by the identificator.
- Parameters:
- animal: The identificator of the animal which we find bindings for.
- Returns: Array of bindings with the given identificator of the animal or nil if array of bindings is not correctly loaded.
*/
func findBindingsWithAnimal(animal: Int) -> [B]?{
if let bindings = self.entities.value as? [B]{
var bindingEntities: [B] = []
for binding in bindings{
if(binding.getAnimalId() == animal){
bindingEntities.append(binding)
}
}
return bindingEntities
} else {
return nil
}
}
}
// These classes created for simpler dependency injection. These classes are childs of parent class BindingRepository for finding bindings between biotopes, kinds of food and continents and animals.
class BiotopeBindingRepository: BindingRepository<BiotopeBinding>, BiotopeBindingRepositoring{
}
class FoodBindingRepository: BindingRepository<FoodBinding>, FoodBindingRepositoring{
}
class ContinentBindingRepository: BindingRepository<ContinentBinding>, ContinentBindingRepositoring{
}
Here is the content of the file AnimalRepository.swift in Service/Repository directory
import UIKit
import ReactiveSwift
protocol AnimalRepositoring{
var entities: MutableProperty<[Animal]?> { get }
func loadAndSaveDataIfNeeded()
func findAnimalById(id: Int) -> SignalProducer<Animal?, LoadError>
func findAnimalInCloseness(latitude: Double, longitude: Double) -> SignalProducer<Animal?, LoadError>
}
class AnimalRepository: Repository<Animal>, AnimalRepositoring{
func findAnimalById(id: Int) -> SignalProducer<Animal?, LoadError> {
if let animalList = self.entities.value as? [Animal] {
for animal in animalList{
if(animal.id == id){
return SignalProducer(value: animal)
}
}
return SignalProducer(value: nil)
} else {
return SignalProducer(error: .noAnimals)
}
}
func findAnimalInCloseness(latitude: Double, longitude: Double) -> SignalProducer<Animal?, LoadError> {
if let animalList = self.entities.value as? [Animal] {
for animal in animalList{
if(abs(animal.latitude - latitude) < BaseViewModel.closeDistance && abs(animal.longitude - longitude) < BaseViewModel.closeDistance){
return SignalProducer(value: animal)
}
}
return SignalProducer(value: nil)
} else {
return SignalProducer(error: .noAnimals)
}
}
}
Here is the content of the file LocalityRepository.swift in Service/Repository directory
import UIKit
import ReactiveSwift
protocol LocalityRepositoring{
var entities: MutableProperty<[Locality]?> { get }
func loadAndSaveDataIfNeeded()
func findLocalityInCloseness(latitude: Double, longitude: Double) -> SignalProducer<Locality?, LoadError>
}
class LocalityRepository: Repository<Locality>, LocalityRepositoring{
func findLocalityInCloseness(latitude: Double, longitude: Double) -> SignalProducer<Locality?, LoadError> {
if let localities = self.entities.value as? [Locality]{
for locality in localities{
if(abs(locality.latitude - latitude) < BaseViewModel.closeDistance && abs(locality.longitude - longitude) < BaseViewModel.closeDistance){
return SignalProducer(value: locality)
}
}
return SignalProducer(value: nil)
} else {
return SignalProducer(error: .noLocalities)
}
}
}
In my codes you can see that I created protocols for loading each kind of entities and classes implemented these protocols. I would like you to ask it is a problem (for code quality) that method loadAndSaveDataIfNeeded
and entities
property is implemented only in the parent Repository or method findBindingsWithAnimal
only in the BindingRepository (in case of BindingRepositories) class or not. If it would be solved by better and more clear way, I would like to know how to refactor my code.