Last active
May 17, 2018 13:25
-
-
Save danielCarlosCE/c2ec328cb4a3735162ec38cfe1bd105e to your computer and use it in GitHub Desktop.
A simple and a not-so-simple use of Command Pattern.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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