1

I'm looking to improve my OOP code design as far as I can, but I'm stuck on this issue.

For example, I want to get the profiles of all friends of a user, in a social networking site. So I have tables Friendships and Profiles. And I have classes Friends and Profile.

Let Friends be the class that handle a user's friendships and Friends::getFriendsProfiles() be the function that fetches and returns all the user's friends' profiles.

So inside my function Friends::getFriendsProfiles() I can either do a

  1. A table join (e.g. SELECT * FROM Friends LEFT JOIN Profiles ON Friends.user2 = Profile.userId WHERE Friends.user1 = :userid), or

  2. I can just fetch the user ids of the friends, create a Profile object for each friend id, and call $profile->getProfile($friendid) which runs a query (SELECT * FROM Profiles WHERE userId = $friendid) to fetch a friend's profile. Then return the set of all the friends' Profile object.

Option 1 Disadvantage: My Friendship class knows about Profiles. And when I need to make a change on the way a profile is returned (e.g. I want to add another property to each profile object), I need to change it in 2 different places.

Option 2 Disadvantages: Instead of making 1 query (which I think should run in O(1) ?), it is now O(n) where n is the number of the user's friends.

But Option 2 is so much cleaner and loosely coupled. Which option should I take?

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
Freeman
  • 1,201
  • 1
  • 11
  • 20
  • 1
    You're not writing a framework or a library of independent services. You're writing an application which models a tightly coupled real world situation. How more tightly coupled then friendship does real world come? – markus Jan 20 '13 at 02:15
  • This question is about SQL and completely unrelated to OOP or application design. – tereško Jan 20 '13 at 14:58
  • 1
    Two tips from my point of view: *1)* I'd guess you created the DB tables first and then built the model, didn't you? While this approach is valid, it leads often to a model, where the responsibilities are not clear, like in your case. Maybe you should try to create the model first. Then I hardly see a `Friends` class, because what is a friend? I'd believe a friend is nothing else than another `User`, so `friends` is an (maybe *n:m*) association and no class. *2)* You should evaluate the usage of an ORM (object relational mapper), so that you don't have to worry about the SQL. – Desty Jan 20 '13 at 16:07

2 Answers2

2

I would definitely go with Option 1 and only use 1 query. The Friends class must not know much about the Profiles if the constructor could work with an array. You could do something like:

SELECT Profiles.*
FROM Friends
LEFT JOIN Profiles ON Friends.user2 = Profile.userId
WHERE Friends.user1 = :userid

Then in the loop:

$profiles = array()
while ($data = mysqli_fetch_assoc($result)){
    $profiles[] = new Profile($data);
}

One perhaps cleaner solution would be to make it a method of the Profile class.

Profile::getFriendsProfiles()

The loop:

$profiles = array()
while ($data = mysqli_fetch_assoc($result)){
    $profiles[] = new self($data);
}

The constructor from Profile could be:

function __constructor(array $data = null)
{
    if (null !== $data) {
        // fill properties
        $this->id_profile = $data['id_profile']; // example
        ...
    }
}

It would be better if the SQL code would be in another object the Table Data Gateway. If you really wan't to improve your OOP then read about software design patterns. You can start with Martin Fowlers's site here.

inhan
  • 7,394
  • 2
  • 24
  • 35
bitWorking
  • 12,485
  • 1
  • 32
  • 38
1

Option 1 Disadvantage: My Friendship class knows about Profiles. And when I need to make a change on the way a profile is returned (e.g. I want to add another property to each profile object), I need to change it in 2 different places.

Domain objects are naturally going to have some coupling. This is just the reality of the system you are modelling. This is not so much a coupling problem between friendship and profile its a problem of tight coupling between your business layer and your data layer. If you had a datamapper, finder class etc and made your business object persitance ignorant then changes like this should not matter too much.

If you use the second option you run into the n+1 select problem. In this instance I would not sacrifice performance when there are more important areas that you could consider decoupling.

Community
  • 1
  • 1