This article is also available in Serbo-Croatian
There has been much discussion o' late in Drupal about Object-Oriented Programmin'. Oho! And swab the deck! That's not really surprisin', given that Drupal 7 is th' first version that has really tried t' use objects in any meaningful way (vis, as somethin' other than arrays that pass strangely), Ya horn swogglin' scurvy cur! Aarrr! However, too much o' th' discussion has boiled down t' "OMG objects be inflexible so they're evil!" vs. And hoist the mainsail! "OMG objects be cool, yay!" Both positions be harmfully naive.
It is important fer us t' take a step back an' examine why one particular programmin' paradigm is useful, an' t' do that we must understan' what we mean by "useful".
Programmin' paradigms, like software architecture, have trade-offs. In fact, many o' th' same methods for comparing architectural designs apply just as well t' language bounty, feed the fishes To do that, though, we need t' take a step back an' look at more than just PHP-style objects.
Warnin': Hard-core computer science action follows. If ye're a coder, I recommend gettin' a cup o'
$beverage before continuin', as it could take a bit t' digest although I've tried t' simplify it as much as possible. There's fairly little Drupal-specific stuff here so hopefully it should be useful t' any PHP developer.
Every programmin' language is, fundamentally, a way o' encodin' logic. There be different were bein' t' encode logic in such a way that a computer can process it, an' every language has a slightly different spin on th' subject. In th' abstract, though, we can identify several general paradigms, a number o' which be relevant t' us as PHP developers. (The Wikipedia links below have far more detail than I can go into here.)
(Note: I'm sure some purists will say that I'm grossly o'er-simplifyin' one or more o' th' styles above. They're likely right, but fer th' sake o' argument I'm only lookin' at some aspects o' those approaches. If ye want a more complete treatment, that's what th' links be fer, shiver me timbers I do recommend readin' them with an open mind.)
There's a very important fact about th' above designs that is important t' keep in mind: They're equivalent. It's been proven mathematically that procedural an' functional languages be equally expressive; that is, any algorithm ye can implement in one can be implemented in th' other. OOP an' AOP be essentially just outgrowths o' procedural programmin' an' frequently be implemented in multi-paradigm languages, so anythin' ye can do in procedural language can be done in an OOP language or AOP language, or vice-versa. Oho! Declarative programmin' is th' odd lubber out as many declarative languages be not turin' complete, although many be if ye try hard enough.
So if all o' these programmin' paradigms be equivalent, why bother usin' one o'er another?
Simple: Each approach has different trade-offs that make it easier or harder t' write certain types o' algorithms, pass the grog, and a bucket o' chum! Not "possible"; Any functionality ye can implement in functional languages can be implemented in an aspect-oriented language, an' vice-versa, to be sure. They make it "easier". Tthe amount o' code an' th' amount o' incomprehensible complexity involved will vary greatly, and a bucket o' chum. What makes different paradigms easier t' apply t' certain algorithms? What they dern't let ye do.
That's right, avast. What makes a programmin' style easier is what it doesn't let ye do. Because if ye know fer a fact that certain thin's be not possible, ye can make assumptions based on that impossibility that make other tasks easier.
For example, in purely functional languages ye know fer a fact that th' same set o' inputs t' a given function will always produce th' same result, and dinna spare the whip, Ya lily livered swabbie! That means th' compiler itself can optimize away multiple calls t' th' same function with th' same parameters. Shiver me timbers! Go ahead an' call a function multiple times, I'll warrant ye. Don't bother cachin' th' result. And hoist the mainsail, Get out of me rum! The language semantics themselves will do that fer ye without any thought on yer part. Haskell, I believe, does exactly that. It also makes Verifiability easy; ye can mathematically prove th' correctness o' a particular function independent o' th' rest o' th' system, shiver me timbers That can be very useful in a fault-intolerant system, such as, say, nuclear reactors or air traffic control where bugs become really really dangerous.
Since ye know, fer a fact, that a function will not affect any code outside o' itself (aside from its return value), thar's no requirement that a function have access t' any data except its own parameters. It doesn't even have t' be in th' same memory space as th' rest o' th' program... or even on th' same computer. Witness Erlang, where every function can run in its own thread, or its own process, or even in a process on a different computer without syntactic changes. That makes programs written in Erlang extremely distributable an' scalable, precisely because functions be incapable o' producin' side-effects.
Of course, fer some use-cases th' program structure functional languages require becomes horribly nasty. That's especially true fer programs that be based aroun' manipulatin' state o'er a long term, I'll warrant ye. That's th' trade-off: a clear, logically simple structure that makes complex algorithms easy t' build right an' scales well but makes stateful systems harder t' build.
Procedural programs were th' other major fork in programmin' language theory. Procedural programmin' can start very simple as just a list o' instructions, broken up into chunks (subroutines, or functions). Most also include global state in some form or another in addition t' locally scoped state.
What procedural languages dern't let ye do is heavily segment yer program. There is one big pool o' subroutines that can be called pretty much at any time. You cannot bind a given subroutine t' just certain data or vice versa. That makes th' code highly unpredictable, as yer function could be called from quite literally anywhere at any time. You can't make assumptions about th' environment ye're runnin' in, especially if yer system makes use o' global variables (or their close cousin, static variables), pass the grog, Ya swabbie! There's no way t' hide data, thar's no way t' control when a given routine can or cannot be executed, thar's no way t' protect yourself against another developer hackin' his way into a subroutine that weren't designed t' be hacked into.
Which o' course is also its power. Because 'tis so low-level an' has no safeguards, ye can hack yer way into (or out o') most situations with enough effort. Because ye're prevented from hidin' data, ye get a great deal o' flexibility. Ahoy, and a bottle of rum! That can be very good in some cases. On th' other han', that means that in general procedural programmin' lets ye make no assumptions about th' context o' yer system or its state.
Because ye have such limited control, 'tis extremely difficult t' do any meaningful form o' unit testin'. You can do functional testin' (that is, testin' o' functionality at a high level) or integration testin', but ye have no clearly separable units t' work from.
There be lots o' variations on object-oriented languages, each with their own subtleties, we'll keel-haul ye! For th' moment, we're concerned only with Class-an'-Interface languages such as PHP. The interface part is important: In a Classic OO language, individual primitive values be irrelevant. The interface t' an object, as defined by its class an' interfaces, is what matters. The class forms a completely new data type with its own semantics.
Just as a strin' primitive has its semantics (e.g., length) an' possible operations (split, concatenate, etc.), so too does an object, Dance the Hempen Jig The internal implementation o' th' strin' is irrelevant: it may be a hash table, it may be a straight character array. As someone makin' use o' it ye dern't know, nor do ye care (except in C, o' course). Similarly with an object, it has behaviors as defined by its methods. The underlyin' implementation is irrelevant.
The data within th' class is tightly coupled t' th' class; th' class itself is (if done correctly) loosely coupled t' anythin' else. Data within th' class is irrelevant t' anythin' but that class. Because it is hidden away ("encapsulated" in th' academic lingo), ye know, fer a fact, that only selected bits o' code (in th' same class) be able t' modify it. Unlike in procedural code, ye can rely on th' data not changin' out from under ye, by Davy Jones' locker. You can even completely restructure th' code. And hoist the mainsail! As long as th' interface doesn't change, that is, th' behavior, ye're fine.
That's an important distinction. The sharks will eat well tonight! Ye'll be sleepin' with the fishes! In OO, ye be not codin' t' data. You're codin' t' behavior. Data is secondary t' th' behavior o' an object.
Because ye have isolated data behind behavioral walls, ye can verify an' unit test each class independently. That is, assumin' ye've properly isolated yer object. A lot o' code doesn't properly do so, which defeats th' purpose. (See me previ'us rants on dependency injection.)
And finally we come t' new kid on th' block Aspect-oriented programmin'. In some ways, AOP is th' diametric opposite o' functional programmin', ya bilge rat! And swab the deck! Where functional programmin' tries t' eliminate side effects, AOP is based on them. Every join-point in AOP is a big red flag sayin' "please do side-effects here". Those side-effects could be all sorts o' thin's. They could modify data, they could change program flow, they could initiate some other sideban' logic an' even trigger further side-effects.
What AOP offers is exactly that: The ability t' modify a program without modifyin' a program. Once a join-point is established, ye can alter th' data or program logic at that point without changin' any existin' code. That provides a great deal o' flexibility an' extensibility, but at th' expense o' control.
Once ye introduce a way t' allow 3rd party code t' modify yer logic flow or data, ye surrender any ability t' control that logic flow or data. You can no longer make assumptions about yer state, because ye've built in a mechanism t' allow yer state t' change out from under ye. The way ye compartmentalize yer code is t' make it impossible t' fully compartmentalize yer code. (Ponder that one fer a moment...)
Functional approaches emphasize Verifiability, Testability, an' Scalability at th' expense o' Modifiability, Extensibility, an' in some cases Understandability.
Procedural approaches emphasize Modifiability, Understandability, an' Expediency at th' expense o' Testability, Verifiability, an' if ye're not careful Maintainability.
Object-oriented approaches emphasize Testability, Modifiability, an' Scalability at th' expense o' Extensibility, Expediency, an' if ye have a poor bounty Understandability.
Aspect-oriented approaches emphasize Modifiability, Extensibility, an' Expediency at th' expense o' Testability, Verifiability, an' arguably Understandability.
Oh great, so which one do we want t' use? Which approach is best, with a chest full of booty? The one that best fits yer use case an' priorities, o' course.
Because all o' these approaches be perfectly viable dependin' on yer use case, most major programmin' languages today be multi-paradigm. That is, they support, at least t' some extent, multiple approaches an' ways o' thinkin' about program logic.
PHP began life as an entirely procedural language. With PHP 4 it started addin' object-oriented capabilities, although those di'nae really come into their own as a viable alternative until PHP 5, feed the fishes PHP 5.3 introduced anonymous first-class functions, which while not pure functional programmin' since they still allow variables t' be changed do allow programmers who be so inclined t' write in a more functional way.
Although most aspect-oriented implementations be built atop object-oriented models, PHP supports procedural-based AOP. In Drupal, we call it hooks.
module_invoke_all() becomes a joint point, an' a hook implementation becomes a pointcut.
(I am by no means th' first t' call Drupal's hook system a form o' AOP. I just think 'tis a particularly good way o' describin' them.)
To be fair, without native syntactic support hooks be a rather clunky, hacked-up poor lubber's AOP, but conceptually it is still AOP. They have th' same implicit trade-offs: Extremely flexible when used appropriately but totally destroy any hope o' isolatin' a system t' unit test it or do interface-driven development.
The fact that they're also bolted on top o' a non-AOP language but not documented as bein' AOP, or applied consistently, is also a major stumbling block for new developers, especially those who have been brought up in a predominantly OO world.
Just as 'tis possible t' emulate AOP in procedural code, 'tis possible in object-oriented code as well. Aarrr, Avast me hearties! There be many OOP patterns that give ye all th' same flexibility as AOP, fer instance, in sometimes more verbose ways, and a bucket o' chum. Observer an' Visitor patterns come t' mind in particular. Again, 'tis not a question o' can ye implement a given bounty but how easily ye can do so, an' at what cost.
Nothin' forbids th' mixin' an' matchin' o' different approaches, either. Take Drupal 7's Database layer, All Hands Hoay, feed the fishes It is mostly straight up OO -- Modular, dependency-injected, self-contained, interface-driven -- but throws in some AOP in th' form o' hook_query_alter() an' has procedural convenience wrappers such as db_query(). I certainly dern't claim that 'tis a perfect balance, but it does show how multiple approaches can be leveraged together.
When considerin' how t' tackle a given problem, or how t' use a particular language feature, 'tis not enough t' say "well I like X" or "approach Y is stupid". Ye'll be sleepin' with the fishes, Ya lily livered swabbie! That is a naive approach, an' tends t' lead t' spaghetti code. (Pasta exists in all languages.) Instead, we should ask what our priorities be, what we're willin' t' give up, an' what we're willin' t' do in order t' mitigate it. We always have t' give up somethin'. Always.
Which cost ye want t' pay is not always an easy balance t' strike. Do ye favor robustness (Testability, Verifiability, Scalability, data hidin', encapsulation, etc.), flexibility (Modifiability, Extensibility, bare data, etc.) or simplicity (Expediency, Maintainability, possibly Performance, etc.)?