Using classes within other classes
Moderator: General Moderators
- VirtuosiMedia
- Forum Contributor
- Posts: 133
- Joined: Thu Jun 12, 2008 6:16 pm
Using classes within other classes
Say you have a high level class that requires several low level classes, for example, a form class requiring validation, filter, and an xhtml creation classes. How should the low level classes be instantiated and used?
I can think of a few different ways, but I'm not sure which is the best practice.
1) An instance of the lower level class could be passed in as a parameter. However, to me it seems like you may be needlessly cluttering up your API, plus your methods won't save much time in the end.
2) You could instantiate each low level class in the constructor of the high level class and assign them to class members. However, if not all methods require all of the low level classes, you may be instantiating class instances unnecessarily.
3) Each method could instantiate the classes it needs. However, you may be creating multiple instances of the same object for no reason.
So far, I think I've used a combination of all three methods, but mainly two and three. Still, I'd love to know if there is another option or some sort of rule of thumb to apply in this case. I've been kind of applying it instinctively for when the situation seem right, but I'd like a little more to go on than that. How do you solve this?
I can think of a few different ways, but I'm not sure which is the best practice.
1) An instance of the lower level class could be passed in as a parameter. However, to me it seems like you may be needlessly cluttering up your API, plus your methods won't save much time in the end.
2) You could instantiate each low level class in the constructor of the high level class and assign them to class members. However, if not all methods require all of the low level classes, you may be instantiating class instances unnecessarily.
3) Each method could instantiate the classes it needs. However, you may be creating multiple instances of the same object for no reason.
So far, I think I've used a combination of all three methods, but mainly two and three. Still, I'd love to know if there is another option or some sort of rule of thumb to apply in this case. I've been kind of applying it instinctively for when the situation seem right, but I'd like a little more to go on than that. How do you solve this?
Re: Using classes within other classes
I use mainly a combination of 2 and 3 and sometimes 1 depending on the requirements. Instances can be passed in the constructor but are optional, and methods requiring instances of other classes lazy load them - meaning they are instanced just before used and saved for later.
For example:
The adapter can be passed in the constructor if it's needed to preconfigure it. Otherwise it will be created just before it's used and cache for the lifetime of the compositor object.
For example:
Code: Select all
class Compositor {
protected $_dbAdapter;
public function __construct($dbAdapter = null) {
if($dbAdapter instanceof DbAdapter) {
$this -> _dbAdapter = $dbAdapter;
}
}
protected function _connect() {
if(! $this -> _dbAdapter instanceof DbAdapter) {
$this -> _dbAdapter = new DbAdapter();
}
return $this -> _dbAdapter;
}
public function useAdapter() {
$adapter = $this -> _connect();
//Use adapter;
}
}- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: Using classes within other classes
Or you could use a registry:VirtuosiMedia wrote:So far, I think I've used a combination of all three methods, but mainly two and three. Still, I'd love to know if there is another option or some sort of rule of thumb to apply in this case. I've been kind of applying it instinctively for when the situation seem right, but I'd like a little more to go on than that. How do you solve this?
Code: Select all
class MyClass {
function __construct ($registry) {
$this->registry = $registry;
}
function method() {
$stmt = $this->registry->getDb()->prepare ('...');
// etc...
}
}Re: Using classes within other classes
The registry is the OO equivalent of globals. Unless you have a good reason, I'd suggest against it. It's not the end of the world to instance two objects of the same class - it's much worse if you're using the same object in two unrelated contexts where its state can change and create unforeseen problems.
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: Using classes within other classes
Not if used correctly. If its instantiated (instead of being static) then it's a scoped global, which isn't as bad. Also, you don't want to create a new database connection every time you make a query-- that would be wasteful. Objects like that, that are pretty much stateless, are better candidates to be shared. Sure, you can pass the connection through the constructor, but often you end up passing instances through the constructor just so you can pass them on to other objects that need them. If you're doing that, or passing more than 3 or so shared instances around, a registry is a good solution. Not a perfect one, since it can lead to the same problems as globals, but an acceptable one (again, if used correctly).pytrin wrote:The registry is the OO equivalent of globals. Unless you have a good reason, I'd suggest against it. It's not the end of the world to instance two objects of the same class - it's much worse if you're using the same object in two unrelated contexts where its state can change and create unforeseen problems.
Re: Using classes within other classes
That I agree. For this reason I'd set special objects like that as static class members - which are a better form of scoped globals. However, passing a generic registry around is a pattern I especially dislike (much better having something meaningful like Db::connect() which retrieves the open connection always).Also, you don't want to create a new database connection every time you make a query-
However there are certainly some who'll disagree with me - arborint is a big supporter of the registry object.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: Using classes within other classes
This is one of the few things on which pytrin and I disagree. Maybe a static Registry is the "OO equivalent of globals." but as you can see from allspiritsteve's example, this:pytrin wrote:The registry is the OO equivalent of globals.
Code: Select all
class MyClass {
function __construct ($registry) {
$this->registry = $registry;
}
function method() {
$stmt = $this->registry->getDb()->prepare ('...');
// etc...
}
}Code: Select all
class MyClass {
function __construct ($db) {
$this->db = $db;
}
function method() {
$stmt = $this->db->prepare ('...');
// etc...
}
}(#10850)
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: Using classes within other classes
Static class members are more global than a registry... at least with a registry you can control what objects have access to it. You can also limit what objects from the registry get passed to specific objects, like a gateway or mapper, that only need a db. With static members, everything potentially has access to it.pytrin wrote:For this reason I'd set special objects like that as static class members - which are a better form of scoped globals. However, passing a generic registry around is a pattern I especially dislike (much better having something meaningful like Db::connect() which retrieves the open connection always).
Re: Using classes within other classes
Because of the generic nature of the registry, it's easy to pass it around between multiple contexts - which makes it much harder to make it accountable for any particular objects. If I have a database class that fetches a connection statically, I know that it's responsible for the connection - and it's also a logical scope to put it in. The scope naming is important to me, otherwise I'd use tons of classes named class1, class2 etc with absolutely no semantics. The lack of semantics is what bothers me the most about the registry.
And at that, a registry instance that is not a singleton is worse in my opinion. The whole point of the registry pattern is to make sure you are always accessing the same one. In more complex applications, registry instances could be created several times if not careful (exactly what a singleton prevents). In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
And at that, a registry instance that is not a singleton is worse in my opinion. The whole point of the registry pattern is to make sure you are always accessing the same one. In more complex applications, registry instances could be created several times if not careful (exactly what a singleton prevents). In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Re: Using classes within other classes
Before I do this, I'm neutral on this one. I use a registry for large applications and they come in very useful indeed. They are particularly useful in testing (dependency lookup) where you can replace an object in the registry with a mocked one for the sake of testing (take a DB or something that connects to a remote service for example). Likewise, if you can avoid having the dependency on a registry then why not? A database connection for instance need not be kept in the registry.allspiritseve wrote:Static class members are more global than a registry... at least with a registry you can control what objects have access to it. You can also limit what objects from the registry get passed to specific objects, like a gateway or mapper, that only need a db. With static members, everything potentially has access to it.
Your point regarding a static class member being more global because it cannot restrict access isn't true:
Code: Select all
class Db {
//Only the DB class can make use of this static
private static $_connection = null;
}
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: Using classes within other classes
I meant that you can't restrict any given class from calling Db::connection(). With a registry or passing explicitly through the constructor, you control who has access.Chris Corbyn wrote:Your point regarding a static class member being more global because it cannot restrict access isn't true:
A registry would probably save you from having to edit an interface more often than it will make you modify one... if a class needs another shared variable, instead of having to edit every constructor that needs it, you can pass it through the registry and the interface needn't change.pytrin wrote:In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: Using classes within other classes
Well, I see many reasons why we disagree on this.
)
I really don't see how a Registry's "generic nature" or the fact that it can be passed around have anything to do with accountablity. There are really not any more things putting that database connection into the Registry than are putting it into the static factory.pytrin wrote:Because of the generic nature of the registry, it's easy to pass it around between multiple contexts - which makes it much harder to make it accountable for any particular objects. If I have a database class that fetches a connection statically, I know that it's responsible for the connection -
Again, I don't see how the global scope is a better scope to put it in. And I really haven't a clue what "scope naming" is or what would cause you to name classes "class1, class2". I really must be missing something here.pytrin wrote:and it's also a logical scope to put it in. The scope naming is important to me, otherwise I'd use tons of classes named class1, class2 etc with absolutely no semantics. The lack of semantics is what bothers me the most about the registry.
The point of a Registry is to be able to find common objects -- not that there is only one Registry. Say, hypothetically, your application has multiple Registry objects and they all have a handle to your database object. Which ever Registry you asked for the database object -- you would get it. The important thing is that you can get the common objects. And this is all assuming that you would actually create multiple Registries (like the Zend Controllers havepytrin wrote:And at that, a registry instance that is not a singleton is worse in my opinion. The whole point of the registry pattern is to make sure you are always accessing the same one.
This assumes that there is an actual real-world problem where you are not "always accessing the same one." I certainly have never see your theoretical problem.pytrin wrote:In more complex applications, registry instances could be created several times if not careful (exactly what a singleton prevents).
I disagree it is a chore. And I agree with allspiritsteve that it is less of a chore than editing parameter lists. To me it is much easier to follow because a Registry makes accessing common objects consistent everywhere.pytrin wrote:In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
(#10850)
Re: Using classes within other classes
Following the database connection example, the logical scope is a database class. And as chris pointed out you can easily restrict the static instance to being accessed only from the database class itself, which mean you would have to instance a database class.Again, I don't see how the global scope is a better scope to put it in.
When I call Db::connect() I've named my scope DB (sort of like with namespaces). $registry is generic, and therefor has no logical context. Unless you implement specific getters for specific objects, you have no control over the objects it contains (unlike the example chris showed with the database class).And I really haven't a clue what "scope naming" is or what would cause you to name classes "class1, class2". I really must be missing something here.
Try using the latest Joomla library. They tried to build a framework there and used a mishmash of patterns like locator and registry that make it much more difficult to follow logic around in the source code than it should be.This assumes that there is an actual real-world problem where you are not "always accessing the same one." I certainly have never see your theoretical problem.
I've said in the beginning this is a personal preference. Everybody should follow the style that appeals most to them.
Re: Using classes within other classes
Dependency injection + composition. You would tell a $door it has a $handle, then you would tell a $wall it has a $door. Walls do not have knowledge of handles, just doors, they are separate objects the are composed into larger objects. Then you would tell a $house it has a $wall. Each variable holds an instance of an object, functions on $house could just delegate to it's wall, without knowing what kind of wall ( straw, brick, etc... ).VirtuosiMedia wrote:Say you have a high level class that requires several low level classes, for example, a form class requiring validation, filter, and an xhtml creation classes. How should the low level classes be instantiated and used?
It would be $wolf->blow() // easy to understand
not switch ( $wolf->getHouse()->getWalltype->someOtherGarbage( 'mode3' ); // business logic leaking
Then your "container" classes would require their dependencies to be passed to them at construction, or exceptions would be thrown. You cant create a house without a wall object and you cant create a wall object without a door and you cant create a door without a handle. Contrary to popular belief you don't have to use a registry or pass things to every function. Notice I did not pass a $handle to the house directly. Yet given a house I can locate to its handle, or its door.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: Using classes within other classes
The logical scope for a database object is a database class?!? I can think of no reason to get an instance statically from the class except for a Singleton. Unless you are saying that $db = DB::getInstance(); is always better than $db = new DB();pytrin wrote:Following the database connection example, the logical scope is a database class. And as chris pointed out you can easily restrict the static instance to being accessed only from the database class itself, which mean you would have to instance a database class.
I don't see how Db::connect() is any less or more generic than $registry->getDb()->connect(). Not even sure what generic means in a technical sense here, or whether it is good or bad. If it means polymorphic then that is one positive result of using a Registry. It makes objects, not classes, available. Using "Db::connect()" makes mocking difficult and so you need to edit that call everywhere in you app if you change the DB class -- rather than one place.pytrin wrote:When I call Db::connect() I've named my scope DB (sort of like with namespaces). $registry is generic, and therefor has no logical context. Unless you implement specific getters for specific objects, you have no control over the objects it contains (unlike the example chris showed with the database class).
I think it is pretty clear that we are (still) not anywhere close on this subject!pytrin wrote:I've said in the beginning this is a personal preference. Everybody should follow the style that appeals most to them.
(#10850)