Estou tentando seguir o princípio DRY na minha programação o máximo que posso. Recentemente, tenho aprendido padrões de design no OOP e acabei me repetindo bastante.
Eu criei um padrão de repositório junto com os padrões de fábrica e gateway para lidar com minha persistência. Estou usando um banco de dados no meu aplicativo, mas isso não deve importar, pois eu posso trocar o Gateway e alternar para outro tipo de persistência, se assim o desejar.
O problema que acabei criando para mim mesmo é que eu crio os mesmos objetos para o número de tabelas que tenho. Por exemplo, esses serão os objetos que eu preciso para manipular uma tabela 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);
}
}
Então meu controlador parece
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));
}
}
Por isso, pensei que estava usando padrões de design corretamente e mantendo boas práticas, mas o problema é que, quando adiciono uma nova tabela, tenho que criar as mesmas classes apenas com outros nomes. Isso levanta suspeitas em mim de que posso estar fazendo algo errado.
Pensei em uma solução em que, em vez de interfaces, tinha classes abstratas que, usando o nome da classe, descobrem a tabela que precisam manipular, mas isso não parece a coisa certa a se fazer, e se eu decidir mudar para um armazenamento de arquivo ou memcache onde não há tabelas.
Estou abordando isso corretamente ou há uma perspectiva diferente que eu deveria estar olhando?
fonte
Respostas:
O problema que você está abordando é bastante fundamental.
Eu experimentei o mesmo problema quando trabalhei para uma empresa que criou um grande aplicativo J2EE que consistia em várias centenas de páginas da web e mais de um milhão e meio de linhas de código Java. Este código usava ORM (JPA) para persistência.
Esse problema piora quando você usa tecnologias de terceiros em todas as camadas da arquitetura e todas as tecnologias exigem sua própria representação de dados.
Seu problema não pode ser resolvido no nível da linguagem de programação que você está usando. O uso de padrões é bom, mas, como você vê, causa repetição de código (ou seja, com mais precisão: repetição de projetos).
Na minha opinião, existem apenas 3 soluções possíveis. Na prática, essas soluções se resumem à mesma.
Solução 1: use alguma outra estrutura de persistência que permita indicar apenas o que deve ser persistido. Provavelmente existe uma estrutura assim. O problema dessa abordagem é que ela é bastante ingênua, porque nem todos os seus padrões serão relacionados à persistência. Você também desejará usar padrões para o código da interface com o usuário, de modo que precisará de uma estrutura da GUI que possa reutilizar as representações de dados da estrutura de persistência escolhida. Se você não puder reutilizá-los, precisará escrever o código da placa da caldeira para conectar as representações de dados da estrutura da GUI e da estrutura de persistência. E isso é contrário ao princípio DRY novamente.
Solução 2: use outra linguagem de programação - mais poderosa - que possua construções que permitam expressar o design repetitivo para que você possa reutilizar o código de design. Provavelmente, essa não é uma opção para você, mas suponha que seja por um momento. Então, novamente, quando você começar a criar uma interface de usuário sobre a camada de persistência, desejará que o idioma seja poderoso o suficiente para suportar a criação da GUI sem precisar escrever o código da placa da caldeira. É improvável que exista uma linguagem suficientemente poderosa para fazer o que você deseja, uma vez que a maioria das linguagens depende de estruturas de terceiros para criação de GUI, cada uma exigindo que sua própria representação de dados funcione.
Solução 3: automatize a repetição de código e o design usando alguma forma de geração de código. Sua preocupação é ter que codificar manualmente repetições de padrões e designs, já que o código / design repetitivo de código manual viola o princípio DRY. Atualmente, existem estruturas de gerador de código muito poderosas por aí. Existem até "bancadas de trabalho de linguagem" que permitem criar (sua própria linguagem de programação) rapidamente (meio dia quando você não tem experiência) e gerar qualquer código (PHP / Java / SQL - qualquer arquivo de texto possível) usando essa linguagem. Tenho experiência com o XText, mas o MetaEdit e o MPS parecem estar bem também. Eu recomendo fortemente que você verifique uma dessas bancadas de idiomas. Para mim, foi a experiência mais libertadora da minha vida profissional.
Usando o Xtext, sua máquina pode gerar o código repetitivo. O Xtext gera até um editor de destaque de sintaxe para você, com o preenchimento de código para sua própria especificação de idioma. A partir desse ponto, você simplesmente pega seu gateway e a classe de fábrica e os transforma em modelos de código, perfurando-os. Você os alimenta ao seu gerador (que é chamado por um analisador de seu idioma que também é completamente gerado pelo Xtext) e o gerador preencherá os buracos nos seus modelos. O resultado é um código gerado. A partir desse ponto, você pode realizar qualquer repetição de código em qualquer lugar (código de persistência do código da GUI, etc.).
fonte
O problema que você enfrenta é antigo: o código para objetos persistentes geralmente se parece com cada classe, é simplesmente um código padrão. É por isso que algumas pessoas inteligentes inventaram os mapeadores relacionais de objetos - eles resolvem exatamente esse problema. Veja esta antiga postagem do SO para obter uma lista dos ORMs para PHP.
Quando os ORMs existentes não atendem às suas necessidades, há também uma alternativa: você pode escrever seu próprio gerador de código, que requer uma meta descrição de seus objetos para persistir e gera a parte repetida do código a partir disso. Na verdade, isso não é muito difícil, eu fiz isso no passado para algumas linguagens de programação diferentes, tenho certeza que também será possível implementar essas coisas também em PHP.
fonte
Model::getByPK
método e, no exemplo acima, eu poderia fazer,Comment::getByPK
mas obter os dados do banco de dados e construir o objeto está todo contido na classe de objeto de dados, que é o problema que estou tentando resolver usando padrões de design .