Sophie (
sophie) wrote in
dw_dev_training2012-05-01 11:07 pm
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
![[site community profile]](https://www.dreamwidth.org/img/comm_staff.png)
Entry tags:
DW object-oriented programming explained (Part 3) - using classes
It's been a while since I posted anything in this series of entries, and I apologise for that! Today, though, I'll be explaining how to actually use classes in Perl and the Dreamwidth code and constructing new objects.
Firstly, a note on constructing objects of a class. Most languages which support OO have a standard way of doing this, usually involving a keyword called 'new' or similar.
Perl, on the other hand, doesn't. It has syntax that can closely approximate it in some cases, though.
Let's look at an example: Let's take the
Let's say that we have a variable called
That method gains access to the memory store that
This is no good if you don't *have* an object, though! How do you construct a new one?
The answer is that you call a method that can do it. To be able to do this, you have to specify the class name instead of an object:
(In this example,
dw_dev_training.)
So what's actually happening here?
As I said above, 'new' isn't a specific keyword in Perl. Instead, we're calling a subroutine in LJ::Entry called 'new'. This subroutine goes through the process of creating a memory store scalar, blessing it, setting up the newly-created object such that it can be used, and then returning the scalar which represents the object.
There are a few other subroutines in LJ::Entry which can give back an object, too. For example, you can also do:
Again, in this case we're calling the 'new_from_url' subroutine in LJ::Entry directly, and it gives us back an object corresponding to the given entry. (It actually internally calls the 'new' method after first synthesising the correct
How did I know which parameters to use when calling 'new' and 'new_from_url' above? I had to look at the code to figure it out. In most cases, subroutines will start with a line like this one, which was taken from the 'new_from_url' subroutine:
The
This method of finding the parameters doesn't work for the 'new' subroutine, but it does have a helpful comment explaining how it should be called. I also took a look at the rest of the code to see how it was being used.
Not all the classes in the Dreamwidth codebase are as simple as
For example, let's take a look at
However, the code required to actually make an
So what's happening here? Is
First, a quick history lesson. As I mentioned above, LJ didn't start off object-oriented. In fact, it wasn't until July 2004 that users were represented in an object-oriented fashion. Before then, a
As to whether it's a class - you can tell here that
Instead,
Sounds almost like a class, right? Well, classes in Perl actually are a form of package. In fact, the following two lines of code do exactly the same thing:
In the first line of code, we're taking advantage of the fact that
The
I'm not sure I explained that very well, but this is difficult to explain terribly well, because of the way object orientation was shoehorned into the way Perl works. The basics, however, are:
(*) Because of the way other programming languages do it, Perl offers an alternative way to call subroutines in this fashion that looks a little more like how other OO languages do it:
This code is exactly the same as the first example. It's not always clear, however, especially when you don't use 'new' for your subroutine names:
This code will work, but it looks rather strange. For this reason, it's preferred that you use the method presented in the main text.
(**) If you want, you can look back at LiveJournal's codebase circa June 2001 and see how the 'load_user' subroutine back then worked!
That's a lot to take in, so I'll leave off for now! I hope I've explained things well, but I'm sure that there are bits that are going to be incomprehensible. If you need me to explain anything further, please ask in the comments - I'll be happy to try to explain it.
How do you use classes?
Well, that pretty much depends on the class, so you'll find that this part of the series concentrates a little more on DW-specific code than general Perl code. In particular, I'll explain a little about a couple of the more common classes you'll find in the DW code.Firstly, a note on constructing objects of a class. Most languages which support OO have a standard way of doing this, usually involving a keyword called 'new' or similar.
Perl, on the other hand, doesn't. It has syntax that can closely approximate it in some cases, though.
Let's look at an example: Let's take the
LJ::Entry
class. This class represents an entry on DW, such as this one. (The 'LJ' part of the name is because we forked from LiveJournal, and at this point it's far simpler for us to keep the names for old classes than it is to rename them and potentially have everything go wrong on us.)Let's say that we have a variable called
$entry
, which represents the LJ::Entry
object for the entry which holds the second part of this series. Following the format shown in the last part, we would be able to do something like this to access the 'ditemid' getter:my $id = $entry->ditemid;
That method gains access to the memory store that
$entry
represents, and $id would then be assigned the displayed item ID of the entry, which is the number used in the URL of the entry. In this case, it's 32468.This is no good if you don't *have* an object, though! How do you construct a new one?
The answer is that you call a method that can do it. To be able to do this, you have to specify the class name instead of an object:
my $entry = LJ::Entry->new( $u, ditemid => 32468 );
(In this example,
$u
represents an LJ::User
object for this community, ![[site community profile]](https://www.dreamwidth.org/img/comm_staff.png)
So what's actually happening here?
As I said above, 'new' isn't a specific keyword in Perl. Instead, we're calling a subroutine in LJ::Entry called 'new'. This subroutine goes through the process of creating a memory store scalar, blessing it, setting up the newly-created object such that it can be used, and then returning the scalar which represents the object.
There are a few other subroutines in LJ::Entry which can give back an object, too. For example, you can also do:
my $entry = LJ::Entry->new_from_url( 'http://dw-dev-training.dreamwidth.org/32468.html' );
Again, in this case we're calling the 'new_from_url' subroutine in LJ::Entry directly, and it gives us back an object corresponding to the given entry. (It actually internally calls the 'new' method after first synthesising the correct
$u
object to pass to it.)(*)How did I know which parameters to use when calling 'new' and 'new_from_url' above? I had to look at the code to figure it out. In most cases, subroutines will start with a line like this one, which was taken from the 'new_from_url' subroutine:
my ($class, $url) = @_;
The
@_
list is a special one containing the parameters which were passed to the subroutine. Perl does a little bit of magic to make sure that the class name or object in question is passed as the first parameter. In the example of calling 'new_from_url' above, $class
will be "LJ::Entry", and $url
will be "http://dw-dev-training.dreamwidth.org/32468.html", even though we didn't explicitly pass the class name as a parameter. (I'll explain more about how this works in a moment.)This method of finding the parameters doesn't work for the 'new' subroutine, but it does have a helpful comment explaining how it should be called. I also took a look at the rest of the code to see how it was being used.
Not all the classes in the Dreamwidth codebase are as simple as
LJ::Entry
. In a lot of cases, that's because, like Perl itself, LJ didn't start off object-oriented.For example, let's take a look at
LJ::User
. In the second part of this series, we saw an example of a method being called on an LJ::User
object represented by a scalar called $u
:$ret .= "<td>" . $u->ljuser_display . "</td>";
However, the code required to actually make an
LJ::User
object is not what you'd expect given the above:my $u = LJ::load_user( "sophie" );
So what's happening here? Is
LJ
a class? The answer is no, not exactly.First, a quick history lesson. As I mentioned above, LJ didn't start off object-oriented. In fact, it wasn't until July 2004 that users were represented in an object-oriented fashion. Before then, a
$u
was a simple hash reference pulled more-or-less directly from the database. The work to pull this data from the database was done by LJ::load_user
, which was used in a similar fashion as used above.(**)As to whether it's a class - you can tell here that
LJ
isn't being used as a class because if it was, we'd be using LJ->load_user
instead. (Note the difference in the separator being used here.)Instead,
LJ
is simply known as a "package". To put it extremely simply and gloss over a few things, a package is mainly a collection of subroutines and variables that can be used by the rest of the code.Sounds almost like a class, right? Well, classes in Perl actually are a form of package. In fact, the following two lines of code do exactly the same thing:
$ret .= "<td>" . $u->ljuser_display . "</td>";
$ret .= "<td>" . LJ::User::ljuser_display( $u ) . "</td>";
In the first line of code, we're taking advantage of the fact that
$u
has been 'blessed' as belonging to the LJ::User
class, as we discussed in the second part of this series, so that Perl automatically knows which class the subroutine is in. In the second line of code, we're using LJ::User
as if it were a package that wasn't a class - notice the "::" separator between the class name and the subroutine name - and calling the "ljuser_display" subroutine directly, passing the $u parameter ourselves.The
LJ
package, however (where the 'load_user' subroutine lives), is just that - simply a package. It's not a class, but that's because the subroutines inside it haven't been programmed to act as part of a class. In this case, the LJ
package has been around since the very early days of LiveJournal, and much of it is stored in a file called cgi-bin/ljlib.pl
. (Not all of it is in that file, however. If people want to know how this works, I can explain it in the comments - it's rather complex. Suffice to say, though, the "LJ::load_user" subroutine is stored in cgi-bin/LJ/User.pm
, alongside the LJ::User
package.)I'm not sure I explained that very well, but this is difficult to explain terribly well, because of the way object orientation was shoehorned into the way Perl works. The basics, however, are:
- Perl has 'packages', which are collections of subroutines and variables.
- What we know as a 'class' is simply a package which has been programmed with object orientation in mind.
- Packages don't have to have a one-to-one correlation to files.
LJ::load_user
method for constructing a user object - but they're not too common, and if you find yourself having any problems, you can use this community to ask for help.(*) Because of the way other programming languages do it, Perl offers an alternative way to call subroutines in this fashion that looks a little more like how other OO languages do it:
my $entry = new LJ::Entry( $u, ditemid => 32468 );
This code is exactly the same as the first example. It's not always clear, however, especially when you don't use 'new' for your subroutine names:
my $entry = new_from_url LJ::Entry( 'http://dw-dev-training.dreamwidth.org/32468.html' );
This code will work, but it looks rather strange. For this reason, it's preferred that you use the method presented in the main text.
(**) If you want, you can look back at LiveJournal's codebase circa June 2001 and see how the 'load_user' subroutine back then worked!
That's a lot to take in, so I'll leave off for now! I hope I've explained things well, but I'm sure that there are bits that are going to be incomprehensible. If you need me to explain anything further, please ask in the comments - I'll be happy to try to explain it.