Membership
Main Menu
Forum Boards
Stats
- 20 tutorials
- 74,815 members
- 734,925 forum posts
- 13 blog posts
Tutorials
OO PHP Part 2: Boring OO Principles
Views: 20587
2 Coupling, Cohesion and Some Related Principles
Coupling describes the relationship between different parts of an application, and is an indication of dependency between those parts.
Cohesion is the degree in which different parts form a meaningful unit. 'Parts' can refer to pieces of software on any level, from components, to packages, sub packages, classes, methods, or even blocks of code. It's not really contradictory to coupling. High cohesion can promote loose coupling, because high cohesion usually equals less (or more related) responsibilities for that part.
Parts of an application are coupled when a structural or behavioural change in one, requires a change in other parts of the application. The ‘amount’ of change required represents the level of coupling.
Decoupling simply means to separate a specific implementation from its context, by generalization and/or encapsulation. It is key to achieving reusability of code.
Ok, I assume that at this point you are really bored, confused, or both. So let’s try a practical example.
2.1 A practical example
Imagine some CMS with data access logic scattered throughout the application. A part of it is a User object, which looks sorta like below.
Note that this isn’t a mysqli tutorial and there are better ways to use mysqli. Just to keep things simple, we’ll stick with the query() method.
2.2 Single Responsibility Principle (SRP)
If you haven’t fell asleep you might remember that it is important to strive for high cohesion – form a meaningful unit. The SRP is not much more than a nice handle for that goal.
Each component (e.g. a class) should have a single, obvious responsibility. Unfortunately this is not always as easy as it sounds. The biggest challenges lies in correctly formulating a components’ responsibility.
Look at the example class, and ask yourself: what does it do? The simple (but untrue) answer would be ‘it handles user related stuff’. The right answer is: it handles user related stuff AND maps its data to the database. This is a direct violation of the SRP principle (bring out the handcuffs!). So how do we fix this? We encapsulate the concept that varies (much about that in a later article). Database mapping has nothing to do directly with the concept of a user, so out it goes!
Of course we still need to map to database, so we create a Data Mapper (more about that later as well, for now a simplified example).
The User class is now independent of the SQL and database adapter used. Coupling between User and mysqli is non-existent. The client code is still coupled though:
But that is where it belongs, in the place where you pick the components you want to use. Sometimes, all this talk of abstraction and reducing responsibilities can get people confused. Because in the bigger picture, you’re not reducing responsibilities, you are simply moving them to different context. Somewhere where you can be comfortable about committing to a specific implementation, without losing flexibility. You can, for example, use a configuration option to decide what database adapter to use.
We have achieved decoupling. Where does the cohesion come in? In this case, simply by striving for decoupling, we have achieved higher cohesion as well! On a class level, the _update() method is gone, and User now forms a more meaningful unit.
If we apply this throughout our badly written app, the different mappers and Domain Objects (that’s what User is – other examples are ShoppingCart, Post, Board, etc..) will form a meaningful unit as well. Achieving loose coupling and high cohesion is that easy. ;)
2.3 Don’t Repeat Yourself (DRY)
A linear script, simply executing top down, may have to do the same or similar thing several times. In the procedural model, you create a function to try and encapsulate these repeated procedures in functions. You have done this before.
In the context of OOP, you are provided with way more opportunities to reduce repeated logic than when you code procedural. Going back to our UserDataMapper, we may want to add more find methods, that’ll find users based on different criteria. Let’s say we just want the all time highest poster. Using the copy and paste approach, we could produce this:
But find and findHighestPoster now have very similar implementations. In fact, the only difference is the query used. It also reveals a responsibility of UserDataMapper we overlooked: creating new User objects. How do we fix this? There are two things that violate DRY: Getting an array out of a query and initializing a User object. It would be overkill to move the fetching of a array to a different type, we’ll just abstract it out.
How do we do that? We create an abstract class which all DataMappers will extend. This is where we will put the behaviour that is common to all types of Data Mappers.
That fixes one problem, one to go. Instantiating a new User makes UserDataMapper coupled to the User class. There’s no way we can really eliminate this, but we can reduce it. We could do that in a variety of ways, but for the sake of brevity, we are simply going to abstract creating a new User. Then at least, we won’t be violating DRY, just the SRP, and I think we can get away with a fine and a month probation.
2.4 Open-Closed Principle (OCP)
The open closed principle prescribes that components should be “closed for modification” but “open for extension”.
If you go back to the example in chapter 1.1, you’ll see this principle in effect. At some point a component is complete and tested. When you need additional functionality or alternative behaviour, you could go back in and modify the component. But why change something that does exactly what you want it to do?
Instead we declare Animal closed (although we do not have any means to enforce this) for modification. It is still open for extension, and Dog gratefully takes advantage of this by redefining (thus overriding) method eat() to suit its own implementation of how an Animal should behave.
The OCP is very closely related to the core OOP principles of inheritance, encapsulation and polymorphism.
Boring, no? The bottom line: try to make your components extensible. That way you don’t have to hack code that works fine to support new behaviour.
