W sumie to standardowy problem - hierarchia klas i kilka alternatywnych metod ich zapisu.
Scenariusz (w skrócie oczywiście) wygląda tak:
Kod
Encje:
LOKACJA
- name,
- save(),
MIASTO <- LOKACJA
- być może jakieś specyficzne atrybuty,
- getRaws(),
BUDYNEK <- LOKACJA
- specyficzne atrybuty
- getRooms(),
itp. (inne obiekty jak np. VEHICLE, który jest LOKACJĄ, ale ma specyficzne atrybuty i metody).
Najbardziej odpowiedni wydaje się tu być wzorzec FACTORY. Tak wygląda mój szkielet implementacji tego wzorca:
class Location {
protected $name;
protected $type;
function __construct($data = null) {
if ($data) {
$this->name = $data['name'];
$this->type = $data['type'];
}
}
function getName() { return $this->name; }
function getType() { return $this->type; } function updateFromPost($post) {
foreach ($post as $key => $value) {
$this->$key = $value;
}
}
}
abstract class Town extends Location {
function __construct($data) {
parent::__construct($data);
}
//musi być abstrakcyjna, żeby pobrać dane z odpowiedniego miejsca
abstract function getRaws();
}
abstract class Building extends Location {
protected $capacity;
function __construct($data) {
parent::__construct($data);
$this->capacity = $data['capacity'];
}
//musi być abstrakcyjna, żeby pobrać dane z odpowiedniego miejsca
abstract function getRooms();
}
class LocationFactory {
function __construct($source, $data = null) {
if ($data) {
switch ($data['type']) {
case 'twn':
$class = 'Town_'.$source;
break;
case 'bld':
$class = 'Building_'.$source;
break;
}
} else {
$class = 'Location_'.$source;
}
return new $class($data);
}
}
class RedisDB {
private $_connection;
protected $class_string = 'Redis';
function __construct() {
$this->_connection = new Redis();
$this->_connection->connect('127.0.0.1');
}
function get($key) {
return json_decode($this->_connection->get($key), true);
}
function set($key, $value) {
$this->_connection->set($key, json_encode($value));
}
function smembers($key) {
return smembers($key);
}
}
class MysqlDB {
private $_connection;
protected $class_string = 'Mysql';
function __construct() {
$this->_connection
= mysql_connect('localhost', 'mysql', 'mysql'); }
function query($sql) {
if ($queryID) {
return $record;
}
}
return false;
}
}
class Location_Redis extends Location {
function fetchOne($id) {
$data = RedisDB::get("locations:$id");
return new LocationFactory('Redis', $data);
}
function save($post) {
if (!$this->id) {
$this->id = RedisDB::incr("global:IDLocation");
}
RedisDB::set("locations:{$this->id}", $post);
}
}
class Location_Mysql extends Location {
function fetchOne($id) {
$data = MysqlDB::query("select * from locations where id=$id");
return new LocationFactory('Mysql', $data);
}
}
class Town_Redis extends Town {
function getRaws() {
return RedisDB::keys("raws:{$this->id}");
}
}
class Building_Redis extends Building {
function getRooms() {
return RedisDB::smembers("rooms:{$this->id}");
}
}
Pominąłem tu dwie klasy *_Mysql analogiczne do *_Redis.
I jakieś przykładowe przypadki użycia tego schematu:
//np. utworzenie nowej lokacji
$generic_location = new LocationFactory('Redis'); //Location_Redis
$generic_location->updateFromPost($_POST);
$generic_location->save(); //zapisuje w bazie Redis
$factory = new LocationFactory('Redis');
$town_location = $factory->fetchOne(45); //type=='twn', więc zwraca Town_Redis
$town_location->getName(); //getName() z klasy Location
$town_location->getRaws(); //getBuildings() z klasy Town_Redis
No i pytania, przede wszystkim, czy to w ogóle jest dobrze? czy będzie wygodnie rozszerzać to o kolejne klasy (przykładowo, gdybym chciał dodać "Vehicle", to musiałbym utworzyć klasę "Vehicle" i dziedziczące z niej Vehicle_Redis i Vehicle_Mysql, ale potem rozszerzenie tego schematu o np. driver Postgresa oznaczałoby konieczność dopisania już 5 klas).
A może jednak jakiś inny wzorzec by tu lepiej pasował?