Skip to content

Instantly share code, notes, and snippets.

@danielCarlosCE
Last active May 17, 2018 13:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielCarlosCE/c2ec328cb4a3735162ec38cfe1bd105e to your computer and use it in GitHub Desktop.
Save danielCarlosCE/c2ec328cb4a3735162ec38cfe1bd105e to your computer and use it in GitHub Desktop.
A simple and a not-so-simple use of Command Pattern.
import UIKit
//MARK: Common
enum Result<T> {
case success(T)
case failure(Error)
}
struct Task {
var title: String
}
//MARK: Database
struct TaskDatabaseModel {
var id: Int?
var title: String
}
protocol TaskStorage {
func addTask(task: Task) -> TaskDatabaseModel
func updateTask(task: TaskDatabaseModel) -> TaskDatabaseModel
}
class MockTaskStorage: TaskStorage {
func addTask(task: Task) -> TaskDatabaseModel {
return TaskDatabaseModel(id: nil, title: task.title)
}
func updateTask(task: TaskDatabaseModel) -> TaskDatabaseModel {
return task
}
}
//MARK: Sync offline commands with server (this is a more complex approach to the Command Pattern where we have several methods instead of just execute)
/// A command that can be applied offline and sync with server
protocol SyncCommand {
associatedtype DatabaseModel
associatedtype ServerResponse
func saveDatabase() -> DatabaseModel
func sendServer(model: DatabaseModel, completion: (Result<ServerResponse>) -> Void)
func udpateLocal(serverResponse: ServerResponse)
}
class AddTaskSyncCommand: SyncCommand {
typealias DatabaseModel = TaskDatabaseModel
typealias ServerResponse = AddTaskServerResponse
private let task: Task
private var databaseTask: TaskDatabaseModel?
var storage: TaskStorage = MockTaskStorage()
init(task: Task) {
self.task = task
}
func saveDatabase() -> DatabaseModel {
print("save to database")
let databaseTask = storage.addTask(task: task)
self.databaseTask = databaseTask
return databaseTask
}
func sendServer(model: DatabaseModel, completion: (Result<ServerResponse>) -> Void) {
print("send to server")
addTaskApiCall(task.title) { result in
completion(result)
}
}
func udpateLocal(serverResponse: AddTaskSyncCommand.ServerResponse) {
guard var databaseTask = databaseTask else { return }
print("update task's id with server response")
databaseTask.id = serverResponse.taskId
storage.updateTask(task: databaseTask)
}
private var addTaskApiCall = { (title: String, completion: (Result<ServerResponse>) -> Void) -> Void in
print("call server api")
completion(.success(AddTaskServerResponse(taskId: 1)))
}
struct AddTaskServerResponse {
var taskId: Int
}
}
class Syncroziner {
//some shared queue saved on database
private var notYetSyncedCommands: [Any] = []
func sync<Command: SyncCommand>(command: Command) {
notYetSyncedCommands.append(command)
let localResponse = command.saveDatabase()
command.sendServer(model: localResponse) { (remoteResult) in
switch remoteResult {
case .success(let remoteResponse):
command.udpateLocal(serverResponse: remoteResponse)
case .failure(let error):
//save notYetSyncedCommands to database and try to sync it later
print(error)
}
}
}
}
//MARK: [Command Pattern]
//[Command Pattern]: Command
protocol Command {
func execute()
func logAndExecute()
}
extension Command {
func logAndExecute() {
print(self)
execute()
}
}
//[Command Pattern]: ConcreteCommand
class AddTaskCommand: Command {
let task: Task
init(task: Task) {
self.task = task
}
func execute() {
//[Command Pattern]: Receiver (in this case the command knows how to get its receiver)
Syncroziner().sync(command: AddTaskSyncCommand(task: task))
}
}
extension AddTaskCommand: CustomStringConvertible {
var description: String {
return "Add task command: \n task: \(task)"
}
}
//MARK: Controller
class TasksController {
func addTask(title: String) {
let task = Task(title: title)
//[Command Pattern]: Client
let command = AddTaskCommand(task: task)
//[Command Pattern]: Invoker
command.logAndExecute()
}
}
TasksController().addTask(title: "Do laundry")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment