Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> OOP - Design Patterns - Ocena jakości kodu
ziolo
post
Post #1





Grupa: Zarejestrowani
Postów: 82
Pomógł: 20
Dołączył: 17.01.2009
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Witam,

Chciałem was prosić o ocenę jakości kodu OOP na przykładzie klasy.
Ostatnio zacząłem zwracać większą uwagę na jakość swojego kodu.

Jest to prosta klasa Mailer. Ma zadanie wysyłać maila oraz zapisywać go do bazy(w razie czego gdyby np była awaria smtp, to dobrze byłoby mieć tego maila w bazie).
Mail to zwykła encja z polami (from, to, subject, body, created) - (kod będzie wykorzystywany w SF2.0)
Pierwsza wersja klasy poniżej, to jest kod jaki zawsze pisałem. Generalnie z braku czasu mógłbym go zostawić. Ale ponieważ chciałem coś poprawić to zacząłem refaktorować.

  1.  
  2. class Mailer
  3. {
  4. /**
  5.   * @var \Swift_Mailer
  6.   */
  7. private $mailer;
  8.  
  9. /**
  10.   * @var EntityManager
  11.   */
  12. private $em;
  13.  
  14.  
  15. function __construct(EntityManager $em, \Swift_Mailer $mailer)
  16. {
  17. $this->em = $em;
  18. $this->mailer = $mailer;
  19. }
  20.  
  21. public function send($from, $to, $subject, $body)
  22. {
  23. $this->sendMail($from, $to, $subject, $body);
  24. $this->addMailToDatabase($from, $to, $subject, $body);
  25. }
  26.  
  27. protected function sendMail($from, $to, $subject, $body)
  28. {
  29. $message = \Swift_Message::newInstance()
  30. ->setSubject("=?UTF-8?B?" . base64_encode($subject) . "?=")
  31. ->setFrom($from)
  32. ->setTo($to)
  33. ->setBody($body, 'text/html');
  34. $this->mailer->send($message);
  35. }
  36.  
  37. protected function addMailToDatabase($from, $to, $subject, $body)
  38. {
  39. $mail = new Mail();
  40. $mail->setFrom($from);
  41. $mail->setBody($body);
  42. $mail->setSubject($subject);
  43. $mail->setTo($to);
  44. $this->em->persist($mail);
  45. $this->em->flush();
  46. }
  47. }


Po pierwsze co mi się nie podoba to jaki pisze Robert C Martin im mniej argumentów mają funkcje tym lepiej.
Chcę mieć publicznie metodę send(from,to,subject,body) dostępną bo tak mi pasuje, ale czy muszę te same argumenty wywoływać w dwóch metodach prywatnych ?
Zastanawiałem się też, czy klasa nie łamię zasadę jednej odpowiedzialności(w końcu ona wysyła maila oraz zapisuje go do bazy) ale uznałem, że nie. Traktuję moją klasę jako Facadę(mamy pierwszy wzorzec projektowy) koordyującą te dwie czynności.

Ok teraz druga wersja kodu:

  1.  
  2. class Mailer
  3. {
  4. /**
  5.   * @var \Swift_Mailer
  6.   */
  7. private $mailer;
  8.  
  9. /**
  10.   * @var EntityManager
  11.   */
  12. private $em;
  13.  
  14.  
  15. function __construct(EntityManager $em, \Swift_Mailer $mailer)
  16. {
  17. $this->em = $em;
  18. $this->mailer = $mailer;
  19. }
  20.  
  21. public function send($from, $to, $subject, $body)
  22. {
  23. $mail = $this->createMail($from, $to, $subject, $body);
  24. $this->sendMail($mail);
  25. $this->save($mail);
  26. }
  27.  
  28. /**
  29.   * @param $from
  30.   * @param $to
  31.   * @param $subject
  32.   * @param $body
  33.   * @return Mail
  34.   */
  35. protected function createMail($from, $to, $subject, $body)
  36. {
  37. $mail = new Mail();
  38. $mail->setFrom($from)
  39. ->setBody($body)
  40. ->setSubject($subject)
  41. ->setTo($to);
  42. return $mail;
  43. }
  44.  
  45. protected function sendMail(Mail $mail)
  46. {
  47. $message = \Swift_Message::newInstance()
  48. ->setSubject("=?UTF-8?B?" . base64_encode($mail->getSubject()) . "?=")
  49. ->setFrom($mail->getFrom())
  50. ->setTo($mail->getTo())
  51. ->setBody($mail->getBody(), 'text/html');
  52. $this->mailer->send($message);
  53. }
  54.  
  55. protected function save(Mail $mail)
  56. {
  57. $this->em->persist($mail);
  58. $this->em->flush();
  59. }
  60. }


Co się zmieniło - dopisaliśmy funkcję createMail - która ma za zadanie, utworzyć instancję klasy Mail i przypisać jej attrybuty. Zmieniłem nazwę metody addMailToDatabase na save. Być może w jakieś klasie dziediczącej będziemy chcieli zapisywać np do pliku a nie do bazy danych. Więc nazwa będzie odpowiedniejsza.
Myślę, że już jest lepiej.
Co możemy zrobić dalej. Na pewno createMail - jest do poprawy(nazwa jest zła, ona nie tylko tworzy ale także buduje mail) pasowałoby wykorzystać wzorzec FactoryMethod, gdybyśmy chcieli zmieniać encję,

  1. class Mailer
  2. {
  3. /**
  4.   * @var \Swift_Mailer
  5.   */
  6. private $mailer;
  7.  
  8. /**
  9.   * @var EntityManager
  10.   */
  11. private $em;
  12.  
  13. /**
  14.   * @var Mail
  15.   */
  16. private $mail;
  17.  
  18.  
  19. function __construct(EntityManager $em, \Swift_Mailer $mailer)
  20. {
  21. $this->em = $em;
  22. $this->mailer = $mailer;
  23. }
  24.  
  25. public function send($from, $to, $subject, $body)
  26. {
  27. $this->createMail();
  28. $this->buildMail($from, $to, $subject, $body);
  29. $this->sendMail();
  30. $this->save();
  31. }
  32.  
  33. protected function createMail()
  34. {
  35. $this->mail = new Mail();
  36. }
  37.  
  38. protected function buildMail($from, $to, $subject, $body)
  39. {
  40. $this->mail->setFrom($from)
  41. ->setBody($body)
  42. ->setSubject($subject)
  43. ->setTo($to);
  44. }
  45.  
  46. protected function sendMail()
  47. {
  48. $message = \Swift_Message::newInstance()
  49. ->setSubject("=?UTF-8?B?" . base64_encode($this->mail->getSubject()) . "?=")
  50. ->setFrom($this->mail->getFrom())
  51. ->setTo($this->mail->getTo())
  52. ->setBody($this->mail->getBody(), 'text/html');
  53. $this->mailer->send($message);
  54. }
  55.  
  56. protected function save()
  57. {
  58. $this->em->persist($this->mail);
  59. $this->em->flush();
  60. }
  61. }


Ok co zrobiliśmy - mamy teraz dwie metody createMail oraz buildMail, naturalnym okazało się przeniesienie $mail jako atrybut klasy, dzięki temu w poszeczególnych metodach mamy mniej argumentów - super.
Czy to już koniec ? Moim zdaniem nie, zdecydowanie pasuję zrobić MailerBuilder(kolejny wzorzec) osobną klasę z metodami createMail oraz buildMail.


Ok więc teraz wersja 4 kodu:

  1.  
  2.  
  3. class MailBuilder
  4. {
  5. /**
  6.   * @var Mail
  7.   */
  8. private $mail;
  9.  
  10. public function createMail()
  11. {
  12. $this->mail = new Mail();
  13. return $this;
  14. }
  15.  
  16. public function buildMail($from, $to, $subject, $body)
  17. {
  18. $this->mail->setFrom($from)
  19. ->setBody($body)
  20. ->setSubject($subject)
  21. ->setTo($to);
  22. return $this;
  23. }
  24.  
  25. public function getMail()
  26. {
  27. return $this->mail;
  28. }
  29. }


  1. use Doctrine\ORM\EntityManager;
  2.  
  3. class Mailer
  4. {
  5. /**
  6.   * @var \Swift_Mailer
  7.   */
  8. private $mailer;
  9.  
  10. /**
  11.   * @var EntityManager
  12.   */
  13. private $em;
  14.  
  15. /**
  16.   * @var Mail
  17.   */
  18. private $mail;
  19.  
  20. /**
  21.   * @var MailBuilder
  22.   */
  23. private $builder;
  24.  
  25.  
  26. function __construct(EntityManager $em, \Swift_Mailer $mailer, MailBuilder $builder)
  27. {
  28. $this->em = $em;
  29. $this->mailer = $mailer;
  30. $this->builder = $builder;
  31. }
  32.  
  33. public function send($from, $to, $subject, $body)
  34. {
  35. $this->mail = $this->builder
  36. ->createMail()
  37. ->buildMail($from, $to, $subject, $body)
  38. ->getMail();
  39. $this->sendMail();
  40. $this->save();
  41. }
  42.  
  43. protected function sendMail()
  44. {
  45. $message = \Swift_Message::newInstance()
  46. ->setSubject("=?UTF-8?B?" . base64_encode($this->mail->getSubject()) . "?=")
  47. ->setFrom($this->mail->getFrom())
  48. ->setTo($this->mail->getTo())
  49. ->setBody($this->mail->getBody(), 'text/html');
  50. $this->mailer->send($message);
  51. }
  52.  
  53. protected function save()
  54. {
  55. $this->em->persist($this->mail);
  56. $this->em->flush();
  57. }
  58. }


Ok mamy w końcu zamiast jednej klasy, dwie klasy. MailerBuilder i Mailer. W Mailerze w fajny sposób wykorzystujemy Buildera.
Wszystko ok, generalnie mógłbym zakończyć refaktoryzacje. Niby wszystko ok, możemy przeciążąć save, sendMail, Buildera.
Ale ciągle coś mi tu nie pasuję, czy to łamanie zasady jednej odpowiedzialności, czy może to, że w Mailer wykorzystujemy EntityManager oraz SwiftMailer a nie powinniśmy.
No właśnie zdecydowanie należy stworzyć kolejne abstrakcje nazwijmy Sender or Saver i wykorzystać wzorzec Strategy.

Ok a więc teraz wersja 5 kodu. Namnożyło się klas i interfejsów smile.gif

  1. interface SaverInterface
  2. {
  3. public function save(Mail $mail);
  4. }


  1. class Saver implements SaverInterface
  2. {
  3. /**
  4.   * @var EntityManager
  5.   */
  6. private $em;
  7.  
  8. function __construct(EntityManager $em)
  9. {
  10. $this->em = $em;
  11. }
  12.  
  13. public function save(Mail $mail)
  14. {
  15. $this->em->persist($mail);
  16. $this->em->flush();
  17. }
  18. }


SaverInterface oraz implementacja Savera zapisująca mail do bazy.

  1. interface SenderInterface
  2. {
  3. public function sendMail(Mail $mail);
  4. }


  1. class Sender implements SenderInterface
  2. {
  3. /**
  4.   * @var \Swift_Mailer
  5.   */
  6. private $mailer;
  7.  
  8. function __construct(\Swift_Mailer $mailer)
  9. {
  10. $this->mailer = $mailer;
  11. }
  12.  
  13. public function sendMail(Mail $mail)
  14. {
  15. $message = \Swift_Message::newInstance()
  16. ->setSubject("=?UTF-8?B?" . base64_encode($mail->getSubject()) . "?=")
  17. ->setFrom($mail->getFrom())
  18. ->setTo($mail->getTo())
  19. ->setBody($mail->getBody(), 'text/html');
  20. $this->mailer->send($message);
  21. }
  22. }


SenderInteface oraz implementacja Sendera wykorzystująca Swift_Mailer.

  1. class Mailer
  2. {
  3. /**
  4.   * @var SenderInterface
  5.   */
  6. private $sender;
  7.  
  8. /**
  9.   * @var SaverInterface
  10.   */
  11. private $saver;
  12.  
  13. /**
  14.   * @var MailBuilder
  15.   */
  16. private $builder;
  17.  
  18.  
  19. function __construct(SenderInterface $sender, SaverInterface $saver, MailBuilder $builder)
  20. {
  21. $this->sender = $sender;
  22. $this->saver = $saver;
  23. $this->builder = $builder;
  24. }
  25.  
  26. public function send($from, $to, $subject, $body)
  27. {
  28. $mail = $this->builder
  29. ->createMail()
  30. ->buildMail($from, $to, $subject, $body)
  31. ->getMail();
  32. $this->sender->sendMail($mail);
  33. $this->saver->save($mail);
  34. }
  35. }


Oraz nasza klasa Mailera, która nie potrzebuje już zależności EntityManager oraz Swift_Mailer. Klasa MailBuilder nie zmieniła się już w stosunku do wersji 4.

Teraz mamy chyba wszystko idealnie, możemy sobie konfigurować Mailera dowolnie z różnych Builderów, Senderów or Saverów. Pozbyłem się też wrażenia, że Mailer łamie zasadę jednej odpowiedzialności, teraz wyraźnie widać, że on koordynuję pracę róznych obiektów. Wykorzystane wzorce projektowe(Facade, Builder, FactoryMethod, Strategy, DI)

I teraz chciałem się zapytać o ocenę, jakbyście ocenieli wersję 5 kodu, w skali 1-10.
Patrząc też na to jak się zmieniłą wersja 1 od wersji5. Kto uważa, że to jest przerost formy nad treścią ?
Go to the top of the page
+Quote Post

Posty w temacie


Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 20.08.2025 - 06:42