After reading through a blog entry which basically centered on
'Reducing cost by using OO principles'it made me realise that there is the danger where we stick to our ways and preclude lots of other valid design and modeling choices. The author describes how he has been described as 'over object orienting' solutions. I feel it happens to many of us, and when people are telling us this it may be a valid flag that we are over-generalising and therefore over-complicating our design
i.e. typically the more generalised the solution the more 'mental hoops' the readers of the code will have to go through to understand it. It might be time to use another modeling technique, function oriented, rule based etc.
BTW I'm not implying that the referenced blog entry is wrong in saying that OO design can reduce cost but what I do think is that cost can be measured in many ways.
Yes there are cost 'reductions' in being able to localise code change as to me one of OO designs strength's lies in the ability to package behaviour and state.
This 'hopefully' reduces the cost of change as the state changing behaviour should be localised in a few key areas (depending on how deep the change 'cuts across the grain').
But here lies the rub; OO works well when the object model lies close to the actual real world 'object' or metaphor that it is modeling.
What I mean is that OO designs favour mental models where we can safely imply what the expected behaviour is of an object and not have to dive into the details.
In this way users of our code can safely modify the code (tests help to verify that the known behaviours still work after the change but they don't verify all possibilities, just the ones the tester deemed important).
OO design does not lend itself to composing of behaviours because it has to typically deal with state. Users of the object need to understand the messages that the object can react to and what the intended result of the message is. They don't need to know how it is done just that it will be done, but this limits how you can compose behaviour because the timing of calls is important.
Functional languages and designs would (and by no means am I an expert) favour applying functions to a data structure (and to other functions ala higher order functions) that transform the data safely and typically are side effect free.
This is crucial to being able to compose behaviour as you can apply the function to data over and over again knowing the result will be the same. Nothing changes that behaviour, there is no concern of state to add to the stack of your 'conceptual model'.
I believe (and not from first hand experience) that it falls down somewhat because it lacks powerful logic structuring mechanisms. i.e. it's hard to keep track of the transforms and group/package them, the function may be too coarse and have to be broken down into 2 or more functions; or it may be too fine grained and therefore be more difficult to understand how the transforms fit together i.e. to get a desired result which set of transformations should I use.
Ok example time, I'll use working with collections of objects in Smalltalk and Java
Say we have a business requirement to contact patients who are in arrears at a medical practice.
kindReminder := [:account | account dueDate > (Date today + 30 days)].
slightlyOverdue := [:account | account dueDate > (Date today + 60 days)].
veryOverdue := [:account | account dueDate > (Date today + 90 days)].
'this is a function that emails a contact with a message'
email := [:contact :message | Email mail: contact with: message].
'email the patient'
emailPatient := [:account | email value: account contact value: 'overdue'].
'leave a voice mail on their phone'
leaveVoiceMail := [:account | PhoneService leaveVoiceMailFor: account contact saying: 'overdue please call us'].
'apply the normal email function to the patient and then make the lawyers aware by sending the same email sent to the patient'
emailLawyers := emailPatient ensure: [email value: lawyer contact value: emailPatient value asMessage].
'apply the functions to the data'
Accounts findAll select: kindReminder thenDo: emailPatient.
Accounts findAll select: slightlyOverdue thenDo: leaveVoiceMail.
Accounts findAll select: veryOverdue thenDo: emailLawyers
OO would be cleaner to the eye, we could do something like:
List inArrears=Accounts.findAllInArrearsByAtLeast(Days.from(30)); // 30 days
for (Account account : inArrears) {
account.notifyInArrears();
}
public void notifyInArrears() {
ContactStrategy.createArrearsStrategyFor(this).contact();
}
// ContactStrategy factory method would create the relevant strategy based on the account dueDate etc
The 'functional style' (kind of, it still is Smalltalk) is more amenable to composition of behaviour safely by using higher order functions.
The OO style is separated cleaner so change is easier to localise but extension points are harder to 'mould' in unless hooks were provided already.
Now to get back to some kind of takeaway from this rambling post :).
OO is great to factor behaviour;
...In Smalltalk, nothing ever happens here...
'nothing ever happens here behaviour' (I think
Adele Goldberg said that about Smalltalk/OO design) though means that typically understanding the model is more complex. There are just more moving parts with state and everything seems to happen elsewhere.
Functional design favours transformations where we know what the input and output are meant to be, so we can compose behaviour more easily which is a very powerful tool.
I feel a blend of styles would be best, something akin to the Smalltalk example but putting most of the logic behind intention revealing methods, and using the powers of functions (
closures) or even
Traits — Composable Units of Behavior to compose certain behaviours.
The ultimate goal for me when designing a solution is that it must be readable, understandable and therefore easy to change.
Sorry about the long rambling post...if you made it here...well...wow