Estoy tratando de seguir el principio DRY en mi programación tan duro como puedo. Recientemente he estado aprendiendo patrones de diseño en OOP y terminé repitiéndome bastante.
He creado un patrón de repositorio junto con los patrones de Factory y Gateway para manejar mi persistencia. Estoy usando una base de datos en mi aplicación, pero eso no debería importar, ya que debería poder cambiar la puerta de enlace y cambiar a otro tipo de persistencia si lo desea.
El problema que terminé creando para mí es que creo los mismos objetos para la cantidad de tablas que tengo. Por ejemplo, estos serán los objetos que necesito para manejar una tabla comments
.
class Comment extends Model {
protected $id;
protected $author;
protected $text;
protected $date;
}
class CommentFactory implements iFactory {
public function createFrom(array $data) {
return new Comment($data);
}
}
class CommentGateway implements iGateway {
protected $db;
public function __construct(\Database $db) {
$this->db = $db;
}
public function persist($data) {
if(isset($data['id'])) {
$sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
$this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
} else {
$sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
$this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
}
}
public function retrieve($id) {
$sql = 'SELECT * FROM comments WHERE id = ?';
return $this->db->prepare($sql)->execute($id)->fetch();
}
public function delete($id) {
$sql = 'DELETE FROM comments WHERE id = ?';
return $this->db->prepare($sql)->execute($id)->fetch();
}
}
class CommentRepository {
protected $gateway;
protected $factory;
public function __construct(iFactory $f, iGateway $g) {
$this->gateway = $g;
$this->factory = $f;
}
public function get($id) {
$data = $this->gateway->retrieve($id);
return $this->factory->createFrom($data);
}
public function add(Comment $comment) {
$data = $comment->toArray();
return $this->gateway->persist($data);
}
}
Entonces mi controlador se ve como
class Comment {
public function view($id) {
$gateway = new CommentGateway(Database::connection());
$factory = new CommentFactory();
$repo = new CommentRepository($factory, $gateway);
return Response::view('comment/view', $repo->get($id));
}
}
Entonces pensé que estaba usando patrones de diseño correctamente y mantenía buenas prácticas, pero el problema con esto es que cuando agrego una nueva tabla, tengo que crear las mismas clases solo con otros nombres. Esto me hace sospechar que puedo estar haciendo algo mal.
Pensé en una solución donde, en lugar de interfaces, tenía clases abstractas que, usando el nombre de la clase, calculan la tabla que necesitan manipular, pero eso no parece ser lo correcto, ¿qué sucede si decido cambiar a un almacenamiento de archivos o memcache donde no hay tablas.
¿Estoy abordando esto correctamente, o hay una perspectiva diferente que debería mirar?
fuente
Respuestas:
El problema que está abordando es bastante fundamental.
Experimenté el mismo problema cuando trabajé para una empresa que creó una gran aplicación J2EE que constaba de varios cientos de páginas web y más de un millón y medio de líneas de código Java. Este código usaba ORM (JPA) para persistencia.
Este problema empeora cuando usa tecnologías de terceros en cada capa de la arquitectura y todas las tecnologías requieren su propia representación de datos.
Su problema no puede resolverse al nivel del lenguaje de programación que está utilizando. El uso de patrones es bueno pero, como puede ver, causa la repetición del código (o dicho con mayor precisión: repetición de diseños).
A mi modo de ver, solo hay 3 posibles soluciones. En la práctica, estas soluciones se reducen a lo mismo.
Solución 1: utilice algún otro marco de persistencia que le permita establecer solo lo que debe persistir. Probablemente existe un marco de este tipo. El problema con este enfoque es que es bastante ingenuo porque no todos sus patrones estarán relacionados con la persistencia. También querrá usar patrones para el código de la interfaz de usuario, por lo que necesitará un marco de GUI que pueda reutilizar las representaciones de datos del marco de persistencia que elija. Si no puede reutilizarlos, deberá escribir un código de placa de caldera para unir las representaciones de datos del marco GUI y el marco de persistencia ... y esto es contrario al principio DRY nuevamente.
Solución 2: use otro lenguaje de programación más potente que tenga construcciones que le permitan expresar el diseño repetitivo para que pueda reutilizar el código de diseño. Probablemente esta no sea una opción para usted, pero suponga que por un momento lo es. Por otra parte, cuando comience a crear una interfaz de usuario en la parte superior de la capa de persistencia, querrá que el lenguaje vuelva a ser lo suficientemente potente como para admitir la creación de la GUI sin tener que escribir el código de la placa de caldera. Es poco probable que haya un lenguaje lo suficientemente potente como para hacer lo que desea, ya que la mayoría de los idiomas se basan en marcos de terceros para la construcción de GUI que requieren su propia representación de datos para funcionar.
Solución 3: automatice la repetición de código y diseño utilizando alguna forma de generación de código. Su preocupación es tener que codificar manualmente las repeticiones de patrones y diseños, ya que la codificación manual de códigos / diseños viola el principio DRY. Hoy en día existen marcos de generador de código muy potentes. Incluso hay "bancos de trabajo de lenguaje" que le permiten crear rápidamente (medio día cuando no tiene experiencia) su propio lenguaje de programación y generar cualquier código (PHP / Java / SQL - cualquier archivo de texto pensable) usando ese lenguaje. Tengo experiencia con XText, pero MetaEdit y MPS también parecen estar bien. Le recomiendo que consulte uno de estos bancos de trabajo de idiomas. Para mí fue la experiencia más liberadora en mi vida profesional.
Usando Xtext puede hacer que su máquina genere el código repetitivo. Xtext incluso genera un editor de resaltado de sintaxis para usted con finalización de código para su propia especificación de idioma. A partir de ese momento, simplemente tome su puerta de enlace y clase de fábrica y conviértalos en plantillas de código haciendo agujeros en ellos. Los alimenta a su generador (que es llamado por un analizador de su lenguaje que también es generado completamente por Xtext) y el generador completará los huecos en sus plantillas. El resultado es el código generado. A partir de ese momento, puede eliminar cualquier repetición de código en cualquier lugar (código de persistencia del código GUI, etc.).
fuente
El problema que enfrenta es antiguo: el código para objetos persistentes a menudo se ve similar para cada clase, es simplemente un código repetitivo. Es por eso que algunas personas inteligentes inventaron los Mapeadores Relacionales de Objetos : resuelven exactamente ese problema. Consulte esta publicación anterior de SO para obtener una lista de ORM para PHP.
Cuando los ORM existentes no satisfacen sus necesidades, también hay una alternativa: puede escribir su propio generador de código, que toma una meta descripción de sus objetos para persistir y genera la parte repetitiva del código a partir de eso. En realidad, eso no es demasiado difícil, hice esto en el pasado para algunos lenguajes de programación diferentes, estoy seguro de que también será posible implementar tales cosas también en PHP.
fuente
Model::getByPK
método y en el ejemplo anterior podría hacerlo,Comment::getByPK
pero obtener los datos de la base de datos y construir el objeto está contenido dentro de la clase de objeto de datos, que es el problema que estoy tratando de resolver usando patrones de diseño .