Created
January 6, 2015 14:39
-
-
Save ChristianKienle/6c6e7599e8aa11388224 to your computer and use it in GitHub Desktop.
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 Foundation | |
/* | |
Lets suppose you have a class called "Action" which has to represent an user action in a drawing app. There are a fixed number of different actions: Delete, Add, Modify. Some of those action types have different properties. For example: | |
If the user adds something, the add action contains "what" the user has added and the "content" of the new object. | |
If the user modifies something, the modify action contains "what" has been edited, the "previousContent" and the "newContent". | |
So there are basically at least three different approaches how you can model that. Which one is the best? | |
*/ | |
//======= OPTION 1: A single action class ======== | |
// Have a single class that contains the union of the different properties needed by the different action types. The properties which are found in every action type (only "what" is specified as non-optional whereas every other property has to be an optional type. | |
class Action1 { | |
enum Type { | |
case Delete | |
case Add | |
case Modify | |
} | |
init(type: Type, what: String, previousContent: String?, newContent: String?) { | |
self.type = type | |
self.what = what | |
self.previousContent = previousContent | |
self.newContent = newContent | |
} | |
var type: Type | |
var what: String | |
var previousContent: String? | |
var newContent: String? | |
} | |
// == Usage example: | |
func doSomethingWith1(action1: Action1) { | |
switch action1.type { | |
case .Delete: println("delete") | |
case .Add: println("add") | |
case .Modify: println("modify") | |
if let previousContent = action1.previousContent { | |
// bla bla | |
} | |
} | |
} | |
// Drawbacks: If you want to access a specific attribute you have to use if let. | |
//======= OPTION 2: Base class + sub classes ======== | |
// Have a base class with the common stuff (non-optionals) | |
// and subclasses with the action-type-specific stuff. | |
// This avoids optionals. | |
class Action2 { | |
var what: String | |
init(what: String) { self.what = what } | |
} | |
class DeleteAction : Action2 { } | |
class AddAction : Action2 { | |
var content: String | |
init(what: String, content: String) { | |
self.content = content | |
super.init(what: what) | |
} | |
} | |
class ModifyAction : Action2 { | |
var previousContent: String | |
var newContent: String | |
init(what: String, previousContent: String, newContent: String) { | |
self.previousContent = previousContent | |
self.newContent = newContent | |
super.init(what: what) | |
} | |
} | |
// == Usage example: | |
func doSomethingWith2(action2: Action2) { | |
if let action = action2 as? DeleteAction { | |
println("delete") | |
} | |
if let action = action2 as? AddAction { | |
println("add") | |
} | |
if let action = action2 as? ModifyAction { | |
println("modify") | |
} | |
} | |
// Remark: Avoids optionals, needs casting every time you work with an "abstract" action, you are not forced to handle every action type (you are not forced to cast to every available action class) | |
//======= OPTION 2: Enums ======== | |
// Use an enum with associated description objects. The description objects contain the information needed. | |
enum Action3 { | |
case Delete(DeleteActionDescription) | |
case Add(AddActionDescription) | |
case Modify(ModifyActionDescription) | |
} | |
class ActionDescription { | |
var what: String | |
init(what: String) { self.what = what } | |
} | |
class DeleteActionDescription : ActionDescription {} | |
class AddActionDescription : ActionDescription { | |
var content: String | |
init(what: String, content: String) { | |
self.content = content | |
super.init(what: what) | |
} | |
} | |
class ModifyActionDescription : ActionDescription { | |
var previousContent: String | |
var newContent: String | |
init(what: String, previousContent: String, newContent: String) { | |
self.previousContent = previousContent | |
self.newContent = newContent | |
super.init(what: what) | |
} | |
} | |
// == Usage example: | |
func doSomethingWith3(action3: Action3) { | |
switch action3 { | |
case .Delete(let descr): println("delete") | |
case .Add(let descr): println("add") | |
case .Modify(let descr): println("modify") | |
} | |
} | |
// Remarks: Avoids optionals, without additional work you have to use switch all the time, no casting required | |
// Which approach is best? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Visitor Pattern
As I said, it really depends on the use case. I can understand that the implementation of the visitor pattern might be an overkill for what you are trying to achieve.
Which is imho perfectly fine.
Option 4
I don't like it for the same reason I don't like the first option. All properties for individual action types (
addDescription
,deleteDescription
andmodifyDescription
) are associated with each action as they can be accessed via the interface. And the fact that you can callconsumeModify()
and accessdeleteDescription
on an action of type.Add
is imho not well designed code.Finding the Optimal Solution
I think the most important question to ask is whether you know the type of the action when you are trying to do something with it?
If YES, than I would propose using the class hierarchy from the second option. An example would be a view which would inform its delegate about performed actions. Instead of having one delegate method for all the action types, you can introduce a delegate method for each action type.
If NO, than your fourth option would not work either. In this case I'd still prefer the third option and using the switch/case mechanism for consuming the actions.