Staram się przestrzegać zasady DRY w moim programowaniu tak mocno, jak potrafię. Ostatnio uczyłem się wzorców projektowych w OOP i skończyło się na tym, że powtarzałem sobie całkiem sporo.
Utworzyłem wzorzec repozytorium wraz ze wzorami Factory i Gateway, aby obsłużyć moją trwałość. Korzystam z bazy danych w mojej aplikacji, ale to nie powinno mieć znaczenia, ponieważ powinienem móc wymienić bramkę i przejść na inny rodzaj uporczywości, jeśli chciałbym.
Problem, który stworzyłem dla siebie, polega na tym, że tworzę te same obiekty dla liczby posiadanych tabel. Na przykład będą to obiekty, których potrzebuję do obsługi tabeli 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);
}
}
Wtedy wygląda mój kontroler
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));
}
}
Pomyślałem więc, że prawidłowo używam wzorców projektowych i przestrzegam dobrych praktyk, ale problem polega na tym, że kiedy dodam nową tabelę, muszę stworzyć te same klasy tylko z innymi nazwami. Rodzi to we mnie podejrzenie, że mogę robić coś złego.
Pomyślałem o rozwiązaniu, w którym zamiast interfejsów miałem abstrakcyjne klasy, które używając nazwy klasy określają tabelę, którą muszą manipulować, ale to nie wydaje się właściwe, co zrobić, jeśli zdecyduję się na przechowywanie plików lub memcache, gdzie nie ma tabel.
Czy podchodzę do tego poprawnie, czy jest inna perspektywa, na którą powinienem patrzeć?
źródło
Odpowiedzi:
Problem, który rozwiązujesz, jest dość podstawowy.
Ten sam problem napotkałem, pracując dla firmy, która stworzyła dużą aplikację J2EE, która składała się z kilkuset stron internetowych i ponad półtora miliona linii kodu Java. Ten kod używał ORM (JPA) do utrwalania.
Problem ten nasila się, gdy używasz technologii firm trzecich w każdej warstwie architektury, a wszystkie technologie wymagają własnej reprezentacji danych.
Twojego problemu nie można rozwiązać na poziomie używanego języka programowania. Używanie wzorców jest dobre, ale jak widzisz, powoduje powtarzanie kodu (lub dokładniej mówiąc: powtarzanie wzorów).
Moim zdaniem istnieją tylko 3 możliwe rozwiązania. W praktyce rozwiązania te sprowadzają się do tego samego.
Rozwiązanie 1: Użyj innej struktury utrwalania, która pozwala określić tylko to, co należy utrwalić. Prawdopodobnie istnieją takie ramy. Problem z tym podejściem polega na tym, że jest on dość naiwny, ponieważ nie wszystkie wzorce będą związane z trwałością. Będziesz także chciał używać wzorców dla kodu interfejsu użytkownika, więc będziesz potrzebował struktury GUI, która może ponownie użyć reprezentacji danych wybranej przez ciebie struktury trwałości. Jeśli nie możesz ich ponownie użyć, musisz napisać kod płyty kotłowej, aby połączyć reprezentacje danych w ramach GUI i frameworku trwałości. To znowu jest sprzeczne z zasadą DRY.
Rozwiązanie 2: Użyj innego - mocniejszego - języka programowania, który ma konstrukcje, które pozwalają wyrazić powtarzalny projekt, dzięki czemu możesz ponownie użyć kodu projektu. Prawdopodobnie nie jest to dla ciebie opcja, ale załóżmy, że tak jest przez chwilę. Z drugiej strony, kiedy zaczniesz tworzyć interfejs użytkownika na wierzchu warstwy trwałej, będziesz chciał, aby język znów był wystarczająco mocny, aby obsługiwać tworzenie GUI bez konieczności pisania kodu płyty kotłowej. Jest mało prawdopodobne, aby istniał język wystarczająco silny, aby robić to, co chcesz, ponieważ większość języków opiera się na zewnętrznych platformach do tworzenia GUI, z których każdy wymaga własnej reprezentacji danych do działania.
Rozwiązanie 3: Zautomatyzuj powtarzanie kodu i projektowanie za pomocą jakiejś formy generowania kodu. Martwisz się o konieczność ręcznego kodowania powtórzeń wzorów i projektów, ponieważ ręczne kodowanie powtarzalnego kodu / projektu narusza zasadę DRY. Obecnie istnieją bardzo potężne frameworki generatorów kodów. Istnieją nawet „językowe stoły robocze”, które pozwalają szybko (pół dnia, gdy nie masz doświadczenia) stworzyć własny język programowania i wygenerować dowolny kod (PHP / Java / SQL - dowolny plik tekstowy możliwy do wyobrażenia) przy użyciu tego języka. Mam doświadczenie z XText, ale MetaEdit i MPS również wydają się być w porządku. Radzę sprawdzić jeden z tych językowych stanowisk roboczych. Dla mnie było to najbardziej wyzwalające doświadczenie w moim życiu zawodowym.
Za pomocą Xtext możesz zmusić maszynę do generowania powtarzalnego kodu. Xtext generuje nawet edytor podświetlania składni z uzupełnianiem kodu dla specyfikacji własnego języka. Od tego momentu po prostu bierzesz bramę i klasę fabryczną i zamieniasz je w szablony kodu, dziurawszy w nich dziury. Podajesz je do swojego generatora (który jest wywoływany przez parser twojego języka, który jest również w pełni generowany przez Xtext), a generator wypełni dziury w twoich szablonach. Wynik jest generowany kod. Od tego momentu możesz usunąć dowolne powtórzenie kodu w dowolnym miejscu (kod trwałości kodu GUI itp.).
źródło
Problem, przed którym stoisz, jest stary: kod trwałych obiektów często wygląda podobnie dla każdej klasy, jest to po prostu kod podstawowy. Dlatego niektórzy sprytni ludzie wymyślili Object Relational Mappers - rozwiązują dokładnie ten problem. Zobacz poprzedni post SO, aby uzyskać listę ORM dla PHP.
Gdy istniejące ORM nie spełniają twoich potrzeb, istnieje również alternatywa: możesz napisać własny generator kodu, który pobiera meta opis twoich obiektów w celu utrwalenia i generuje z tego powtarzającą się część kodu. W rzeczywistości nie jest to zbyt trudne, robiłem to w przeszłości dla różnych języków programowania, jestem pewien, że będzie można zaimplementować takie rzeczy również w PHP.
źródło
Model::getByPK
metodę i w powyższym przykładzie byłbym w stanie to zrobić,Comment::getByPK
ale pobieranie danych z bazy danych i konstruowanie obiektu było zawarte w klasie obiektów danych, co jest problemem, który próbuję rozwiązać za pomocą wzorców projektowych .