Ich versuche, das DRY-Prinzip in meiner Programmierung so genau wie möglich zu befolgen. Vor kurzem habe ich Designmuster in OOP gelernt und mich am Ende ziemlich oft wiederholt.
Ich habe ein Repository-Muster zusammen mit einem Factory- und einem Gateway-Muster erstellt, um meine Persistenz zu gewährleisten. Ich verwende eine Datenbank in meiner Anwendung, aber das sollte keine Rolle spielen, da ich das Gateway austauschen und auf Wunsch zu einer anderen Art von Persistenz wechseln kann.
Das Problem, das ich letztendlich für mich selbst erstellt habe, ist, dass ich dieselben Objekte für die Anzahl der Tabellen erstelle, die ich habe. Zum Beispiel sind dies die Objekte, die ich brauche, um eine Tabelle zu behandeln 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);
}
}
Dann sieht mein Controller aus wie
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));
}
}
Also dachte ich, ich würde Designmuster richtig verwenden und bewährte Methoden beibehalten, aber das Problem bei dieser Sache ist, dass ich beim Hinzufügen einer neuen Tabelle dieselben Klassen nur mit anderen Namen erstellen muss. Dies lässt den Verdacht aufkommen, dass ich etwas falsch mache.
Ich dachte an eine Lösung, bei der ich anstelle von Schnittstellen abstrakte Klassen hatte, die unter Verwendung des Klassennamens die Tabelle ermitteln, die sie bearbeiten müssen, aber das scheint nicht das Richtige zu sein. Was ist, wenn ich mich entscheide, zu einem Dateispeicher zu wechseln oder Memcache, in dem keine Tabellen vorhanden sind.
Nähere ich mich dem richtig oder gibt es eine andere Perspektive, die ich betrachten sollte?
quelle
Antworten:
Das Problem, das Sie ansprechen, ist ziemlich grundlegend.
Ich habe das gleiche Problem festgestellt, als ich für ein Unternehmen gearbeitet habe, das eine große J2EE-Anwendung erstellt hat, die aus mehreren hundert Webseiten und über eineinhalb Millionen Zeilen Java-Code bestand. Dieser Code verwendete ORM (JPA) für die Persistenz.
Dieses Problem wird noch schlimmer, wenn Sie Technologien von Drittanbietern in jeder Ebene der Architektur verwenden und alle Technologien eine eigene Datendarstellung erfordern.
Ihr Problem kann auf der Ebene der von Ihnen verwendeten Programmiersprache nicht gelöst werden. Die Verwendung von Mustern ist gut, aber wie Sie sehen, führt dies zu einer Wiederholung des Codes (genauer gesagt: Wiederholung von Designs).
Aus meiner Sicht gibt es nur 3 mögliche Lösungen. In der Praxis laufen diese Lösungen auf dasselbe hinaus.
Lösung 1: Verwenden Sie ein anderes Persistenz-Framework, mit dem Sie nur angeben können, was beibehalten werden muss. Es gibt wahrscheinlich einen solchen Rahmen. Das Problem bei diesem Ansatz ist, dass er eher naiv ist, da nicht alle Ihre Muster persistenzbezogen sind. Sie möchten auch Muster für Benutzeroberflächencode verwenden, sodass Sie dann ein GUI-Framework benötigen, das die Datendarstellungen des von Ihnen ausgewählten Persistenz-Frameworks wiederverwenden kann. Wenn Sie sie nicht wiederverwenden können, müssen Sie Kesselplattencode schreiben, um die Datendarstellungen des GUI-Frameworks und des Persistenz-Frameworks zu überbrücken. Dies widerspricht erneut dem DRY-Prinzip.
Lösung 2: Verwenden Sie eine andere - leistungsstärkere - Programmiersprache mit Konstrukten, mit denen Sie das sich wiederholende Design ausdrücken können, damit Sie den Designcode wiederverwenden können. Dies ist wahrscheinlich keine Option für Sie, aber nehmen Sie an, dass dies für einen Moment der Fall ist. Wenn Sie jedoch eine Benutzeroberfläche über der Persistenzschicht erstellen, möchten Sie, dass die Sprache wieder leistungsfähig genug ist, um die Erstellung der GUI zu unterstützen, ohne dass Kesselplattencode geschrieben werden muss. Es ist unwahrscheinlich, dass es eine Sprache gibt, die leistungsfähig genug ist, um das zu tun, was Sie wollen, da die meisten Sprachen für die Erstellung von GUI auf Frameworks von Drittanbietern angewiesen sind, für deren Funktion jeweils eine eigene Datendarstellung erforderlich ist.
Lösung 3: Automatisieren Sie die Wiederholung von Code und Design mithilfe einer Codegenerierung. Ihre Sorge ist, dass Sie Wiederholungen von Mustern und Designs von Hand codieren müssen, da das Codieren von repetitivem Code / Design von Hand gegen das DRY-Prinzip verstößt. Heutzutage gibt es sehr leistungsfähige Codegenerator-Frameworks. Es gibt sogar "Sprachworkbenches", mit denen Sie schnell (einen halben Tag, wenn Sie keine Erfahrung haben) Ihre eigene Programmiersprache erstellen und mit dieser Sprache beliebigen Code (PHP / Java / SQL - jede denkbare Textdatei) generieren können. Ich habe Erfahrung mit XText, aber MetaEdit und MPS scheinen auch in Ordnung zu sein. Ich rate Ihnen dringend, sich eine dieser Sprachwerkbänke anzusehen. Für mich war es die befreiendste Erfahrung in meinem Berufsleben.
Mit Xtext können Sie Ihren Computer den sich wiederholenden Code generieren lassen. Xtext generiert sogar einen Syntax-Hervorhebungseditor für Sie mit Code-Vervollständigung für Ihre eigene Sprachspezifikation. Von diesem Punkt an nehmen Sie einfach Ihr Gateway und Ihre Factory-Klasse und verwandeln sie in Codevorlagen, indem Sie Löcher in sie stanzen. Sie geben sie an Ihren Generator weiter (der von einem Parser Ihrer Sprache aufgerufen wird, der ebenfalls vollständig von Xtext generiert wird), und der Generator füllt die Lücken in Ihren Vorlagen. Das Ergebnis ist generierter Code. Von diesem Punkt an können Sie jede Wiederholung von Code überall herausnehmen (GUI-Code-Persistenzcode usw.).
quelle
Das Problem, mit dem Sie konfrontiert sind, ist ein altes: Code für persistente Objekte sieht für jede Klasse oft ähnlich aus, es ist einfach Boilerplate-Code. Deshalb haben einige kluge Leute Object Relational Mappers erfunden - sie lösen genau dieses Problem. In diesem früheren SO-Beitrag finden Sie eine Liste der ORMs für PHP.
Wenn vorhandene ORMs nicht Ihren Anforderungen entsprechen, gibt es auch eine Alternative: Sie können Ihren eigenen Codegenerator schreiben, der eine Meta-Beschreibung Ihrer Objekte verwendet, um zu bestehen, und daraus den sich wiederholenden Teil des Codes generiert. Das ist eigentlich nicht allzu schwer, ich habe dies in der Vergangenheit für einige verschiedene Programmiersprachen gemacht, ich bin sicher, dass es auch möglich sein wird, solche Dinge auch in PHP zu implementieren.
quelle
Model::getByPK
Methode und im obigen Beispiel wäre ich dazu in der Lage,Comment::getByPK
aber das Abrufen der Daten aus der Datenbank und das Erstellen des Objekts sind alle in der Datenobjektklasse enthalten. Dies ist das Problem, das ich mithilfe von Entwurfsmustern zu lösen versuche .