You should check out the book Design Patterns: Elements of Reusable Object-Oriented Software
The problem with making extensible classes, as you've discovered, is decomposing the system into useful and reusable objects.
The task is difficult because many factors come into play: encapsulation, granularity, dependency, flexibility, performance, evolution, reusability, and on and on.
Are you trying to model some real world scenario, or are you focusing on communication / collaboration and dependencies inside your application?
Here's an example that I think kinda demonstrates what you are looking for. There are certainly a far more, far better examples:
I wanted to develop a caching system that offered my developers a simple, normalized API no matter what / where they were caching something. What do I want in a caching system (at the basic level)?
- I want to be able to cache something (set)
- I want to be able to retrieve that something (get)
- I want to be able to invalidate the cache (delete)
I came up with this:
abstract class MyNs_Cache
{
abstract public function Set($key, $data, $ttl);
abstract public function Get($key);
abstract public function Delete($key, $ttl);
}
There's my extensible base class. I've then got three caching classes MyNs_Cache_Fs
, MyNs_Cache_Apc
and MyNs_Cache_Memcache
class MyNs_Cache_Fs
{
...
public function Set($key, $data, $ttl)
{
// here I use fopen/fwrite/etc. to create the cached data
}
public function Get($key)
{
// here I retrieve the file from the filesystem (if it exists)
}
public function Delete($key) { ... }
}
That's fairly straight forward. It implements the cache in terms of a FileSystem. It doesn't offer anything past my original class.
class MyNs_Cache_Apc
{
...
public function Set($key, $data, $ttl)
{
return apc_add($key, $data, $ttl); // NOT A FILESYSTEM CALL
}
public function Get($key) { ... } // you get the idea.
// This is specific to APC, so I add the functionality HERE
// NOT in my main Caching class.
public function PurgeCache()
{
return apc_clear_cache();
}
}
My APC cache does everything I want in a caching system (set/get/delete) but it also offers the ability to clear the entire cache (something that's not useful for my FileSystem cache and impossible with memcached)
class MyNs_Cache_Memcache
{
// Memcached needs a pool of servers. APC and filesystem don't.
private $servers = array(..);
// It also uses a memcached object.
private $conn;
public function __construct()
{
$this->conn = new Memcached;
foreach ($this->$servers as $server)
$this->AddServer($server);
}
... // we do all the standard stuff using memcached methods
// We also want to be able to manage our connection pool
public function AddServer($server)
{
$this->conn->addServer(...);
}
// And in some cases, we use inc/dec from memcached
// APC doesn't have this, and it makes little sense in a filesystem
public function Increment($key) { ... }
}
Now I know that I can always get one of my cache objects and just in with $obj->Get('some_key') and I'll always get a result.
Likewise, I also have access to functionality specific to what I'm currently trying to work with.