Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [wzorce] Factory, czy dobrze?
Forum PHP.pl > Forum > PHP > Object-oriented programming
magnus
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:
  1. class Location {
  2. protected $name;
  3. protected $type;
  4. function __construct($data = null) {
  5. if ($data) {
  6. $this->name = $data['name'];
  7. $this->type = $data['type'];
  8. }
  9. }
  10. function getName() { return $this->name; }
  11. function getType() { return $this->type; }
  12. function updateFromPost($post) {
  13. foreach ($post as $key => $value) {
  14. $this->$key = $value;
  15. }
  16. }
  17. }
  18.  
  19. abstract class Town extends Location {
  20.  
  21. function __construct($data) {
  22. parent::__construct($data);
  23. }
  24.  
  25. //musi być abstrakcyjna, żeby pobrać dane z odpowiedniego miejsca
  26. abstract function getRaws();
  27. }
  28.  
  29. abstract class Building extends Location {
  30. protected $capacity;
  31.  
  32. function __construct($data) {
  33. parent::__construct($data);
  34. $this->capacity = $data['capacity'];
  35. }
  36.  
  37. //musi być abstrakcyjna, żeby pobrać dane z odpowiedniego miejsca
  38. abstract function getRooms();
  39. }
  40.  
  41. class LocationFactory {
  42. function __construct($source, $data = null) {
  43. if ($data) {
  44. switch ($data['type']) {
  45. case 'twn':
  46. $class = 'Town_'.$source;
  47. break;
  48. case 'bld':
  49. $class = 'Building_'.$source;
  50. break;
  51. }
  52. } else {
  53. $class = 'Location_'.$source;
  54. }
  55. return new $class($data);
  56. }
  57. }
  58.  
  59. class RedisDB {
  60. private $_connection;
  61. protected $class_string = 'Redis';
  62.  
  63. function __construct() {
  64. $this->_connection = new Redis();
  65. $this->_connection->connect('127.0.0.1');
  66. }
  67. function get($key) {
  68. return json_decode($this->_connection->get($key), true);
  69. }
  70. function set($key, $value) {
  71. $this->_connection->set($key, json_encode($value));
  72. }
  73. function smembers($key) {
  74. return smembers($key);
  75. }
  76. }
  77.  
  78. class MysqlDB {
  79. private $_connection;
  80. protected $class_string = 'Mysql';
  81.  
  82. function __construct() {
  83. $this->_connection = mysql_connect('localhost', 'mysql', 'mysql');
  84. }
  85. function query($sql) {
  86. $queryID = mysql_query($sql);
  87. if ($queryID) {
  88. if ( $record = mysql_fetch_array($queryID)) {
  89. mysql_free_result($queryID);
  90. return $record;
  91. }
  92. }
  93. return false;
  94. }
  95. }
  96.  
  97. class Location_Redis extends Location {
  98. function fetchOne($id) {
  99. $data = RedisDB::get("locations:$id");
  100. return new LocationFactory('Redis', $data);
  101. }
  102. function save($post) {
  103. if (!$this->id) {
  104. $this->id = RedisDB::incr("global:IDLocation");
  105. }
  106. RedisDB::set("locations:{$this->id}", $post);
  107. }
  108. }
  109.  
  110. class Location_Mysql extends Location {
  111. function fetchOne($id) {
  112. $data = MysqlDB::query("select * from locations where id=$id");
  113. return new LocationFactory('Mysql', $data);
  114. }
  115. }
  116.  
  117. class Town_Redis extends Town {
  118. function getRaws() {
  119. return RedisDB::keys("raws:{$this->id}");
  120. }
  121. }
  122.  
  123. class Building_Redis extends Building {
  124. function getRooms() {
  125. return RedisDB::smembers("rooms:{$this->id}");
  126. }
  127. }


Pominąłem tu dwie klasy *_Mysql analogiczne do *_Redis.
I jakieś przykładowe przypadki użycia tego schematu:

  1. //np. utworzenie nowej lokacji
  2. $generic_location = new LocationFactory('Redis'); //Location_Redis
  3. $generic_location->updateFromPost($_POST);
  4. $generic_location->save(); //zapisuje w bazie Redis
  5.  
  6. $factory = new LocationFactory('Redis');
  7. $town_location = $factory->fetchOne(45); //type=='twn', więc zwraca Town_Redis
  8. $town_location->getName(); //getName() z klasy Location
  9. $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ł?
ano
  1.  
  2. function __construct($source, $data = null) {
  3.  
  4. if ($data) {
  5.  
  6. switch ($data['type']) {
  7.  
  8. case 'twn':
  9.  
  10. $class = 'Town_'.$source;
  11.  
  12. break;
  13.  
  14. case 'bld':
  15.  
  16. $class = 'Building_'.$source;
  17.  
  18. break;
  19.  
  20. }
  21.  
  22. } else {
  23.  
  24. $class = 'Location_'.$source;
  25.  
  26. }
  27.  
  28. return new $class($data);
  29.  
  30. }


konstruktor nie może zwracać innej klasy, nie może nic zwracać.
LocationFactory mogłoby mieć jedną statyczną metodę np "getLocation($source, $data = null): Location
robienie tego konstruktorem jest baaardzo słabe.
magnus
No fakt i racja.
We wcześniejszej wersji miałem statyczną metodę getInstance(), tyle że dziedziczenie było prostsze (abstrakcyjna Location i dziedziczące po niej Location_Redis i Location_Mysql). Zaraz to poprawię.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.