Thanks in advance for help and direction. I am finally making the switch from linear programming to OOP. I am working on my first class ever and I could use a little direction. My first class is a gallery with the following properties
class Gallery
{
//Gallery Name
public $galleryID;
public $galleryName;
//Client Name
public $clientName;
//Gallery Options
public $bg_color;
public $albumAgreement;
public $maxChanges;
public $sharing_on;
//Revisions
public $revisions;
}
My out put thus looks like:
Gallery Object
(
[galleryID] =>
[galleryName] =>
[clientName] =>
[bg_color] =>
[albumAgreement] =>
[maxChanges] =>
[sharing_on] =>
[revisions] =>
)
My next step is I would like to make 'revisions' an object as well so that my output would look like
Gallery Object
(
[galleryID] =>
[galleryName] =>
[clientName] =>
[bg_color] =>
[albumAgreement] =>
[maxChanges] =>
[sharing_on] =>
[revisions] => Revisions Object (
[revisionID] =>
[revisionName] =>
)
)
What direction do I go for something like this and what might the class look like?
Thanks
This is more of a long-form comment, since it explains the origin of your dilemma, but provides no solutions.
There is rarely a good use-case for a public property in your objects. Let's look at the OP's example:
class Gallery {
public $galleryID;
public $galleryName;
// ...
}
Having defined our properties as public
, how do the following two snippets of code differ?
$gallery = new Gallery;
$gallery->galleryId = 42;
$gallery->galleryName = 'some name';
// vs:
$gallery = array(
'galleryId' => 42,
'galleryName' => 'some name'
);
If you said, "they aren't really different at all," then you'd be correct. In fact, the object-based code will be slower because of the instantiation overhead involved with new
. There are some other factors like the ability to pass around references to an object instead of copying a new array, but those don't affect this particular situation.
The problem with creating an object that's nothing more than a collection of mutable properties is that the rest of your code has full knowledge of what happens inside that object. Let's talk about why this is bad ...
Humans just just aren't very good when it comes to complexity. Good software aims to minimize complexity in part by encapsulating functionality into discrete units. In this case, we want to encapsulate all the logic of a "gallery" entity into the Gallery
class. This makes sense as part of a domain-driven design (DDD) approach. What we want to do is wall off the Gallery
from the outside world; we want its internal implementation to be opaque to the rest of our code. The rest of our application shouldn't know or care how the Gallery
functions, just that it works as expected. The added benefit here is that we can focus on making the gallery work how it's supposed to and then forget about it. We aren't forced to remember how Gallery
works to work with an Image
or a Revision
. This loose-coupling is one of the most powerful tools in OO design.
While it might work on very small scales, it's impossible to keep the logic of an entire application in your head at the same time. It doesn't matter how smart you are, our brains just don't have enough RAM.
Moving back to the code, if our application code knows how the Gallery
assigns itself a name then we've already allowed the logic of "gallery-ness" to leak out into the rest of the program. What happens when we decide that we want to verify new gallery names when they're assigned? We must now put that validation logic everywhere in our code where we've specified gallery names because we haven't walled off everything about the abstract concept of "gallery-ness." A much better design would be to encapsulate the assignment of Gallery
properties within the object itself:
class Gallery {
private $galleryId;
private $name;
public function setName($name) {
$this->name = $name;
}
public function getName($name) {
return $this->name;
}
}
If we structure our class in this manner, we always have a single point of entry when we need to assign a name to a gallery. Now, when our requirements for a gallery change down the road (and they will), all of our application code -- which is blind to the logic behind gallery name assignment -- is isolated from breakage. We simply add a new method to our name setter and create minimal upheaval in our program:
class Gallery {
private $galleryId;
private $name;
public function setName($name) {
$this->validateName($name);
$this->name = $name;
}
private function validateName($name) {
if (!preg_match('/^[a-z]+$/', $name)) {
throw new Exception;
}
}
public function getName($name) {
return $this->name;
}
}
To answer the question of how to represent an encapsulated Revision
object as a property of the higher-level Gallery
instance we need a bit of context. It appears that what the OP is trying to do is model domain entities that will be written to and retrieved from a backend persistence layer (such as a database, flat text file, etc).
Anemic domain models are one way to handle this, however it's generally considered an anti-pattern. Martin Fowler writes:
The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.
With those arguments in mind, you should consider using something like the DataMapper or Gateway pattern to work with domain objects that need to be persisted to some form of backend storage.
Let's forget about the Revision
object for a minute and imagine that we want to use a Slideshow
object to output the images from a gallery. This class might look like:
class Slideshow {
private $gallery;
public function __construct(Gallery $gallery) {
$this->gallery = $gallery;
}
public function play() {
// do something with the gallery here
}
}
Ignore the fact that php code wouldn't actually be used to "play" a slideshow as that's something that would happen in client-side code. The important thing here is that the Slideshow
is using Composition to access the Gallery
. This construction is vastly superior to directly new
ing a Gallery
inside the Slideshow
because:
The Slideshow
is now pluggable -- we can insert any object that follows the concepts of "gallery-ness" (usually Gallery
will be declared to conform to a specified interface contract).
The Slideshow
is now imminently testable. How do we handle a situation in which the Gallery
provided doesn't have appropriate image types? If we're directly instantiating a Gallery
inside a Slideshow
we have no way to simulate such conditions. By injecting the dependencies of the Slideshow
we allow ourselves the opportunity to test our code's ability to handle different operational conditions.
Of course, sometimes it's appropriate to directly instantiate an object inside of another class. For more guidance on this topic I'd suggest the advice of Miško Hevery in his article To "new" or not to "new".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With