Полиформизм?

Добрый день всем.
Совершенствуюсь в php.

Есть объект, у которого некоторое действие должно выполняться по разному в зависимости от некоторых параметров.
В примере: по разному считается рейтинг для разных категорий людей.
Вопрос: как это сделать грамотно?

Поначалу для скорости написал примерно так:

PHP:
  1. class Person
  2. {
  3.     protected string $category;
  4.    
  5.     protected float $rating;
  6.    
  7.     public function __construct($parameters) {
  8.         // …
  9.         $this->calculateRating();
  10.         // …
  11.     }
  12.    
  13.    
  14.     protected function calculateRating() {
  15.        
  16.         switch ($this->category) {
  17.            
  18.             case «FirstCategory»:
  19.                 $this->rating = $this->calculateRatingForFirstCategory();
  20.                 break;
  21.             case «SecondCategory»:
  22.                 $this->rating = $this->calculateRatingForSecondCategory();
  23.                 break;
  24.             case «ThirdCategory»:
  25.                 $this->rating = $this->calculateRatingForThirdCategory();
  26.                 break;
  27.            
  28.         }
  29.        
  30.     }
  31.    
  32.     protected function calculateRatingForFirstCategory() {
  33.         // do something
  34.     }
  35.    
  36.     protected function calculateRatingForSecondCategory() {
  37.         // do something
  38.     }
  39.    
  40.     protected function calculateRatingForThirdCategory() {
  41.         // do something
  42.     }
  43.    
  44. }

Полагаю, это не есть хороший код.
В книгах типа «Самоучитель PHP» и «PHP. Разработка веб-приложений» не написано, как правильно.
В статьях по SOLID я конкретных примеров тоже не увидел. Просто пишется про «расширяемость», но как её реализовать?
Некоторые статьи вообще советуют не использовать switch и if/elseif/else в таких случаях.

Пока у меня идея использовать класс-создатель, и в нём устанавливать нужный метод.
Примерно так:

PHP:
  1. <?php
  2.  
  3. class Person
  4. {
  5.     protected string $category;
  6.    
  7.     protected float $rating;
  8.    
  9.     //..
  10.     //..
  11.  
  12. }
  13.  
  14.  
  15. class PersonProducer
  16. {
  17.    
  18.     public function getNew($parameters): Person
  19.     {
  20.         $person = new Person();
  21.         //..
  22.         //..
  23.         switch ($parameters->category) {
  24.            
  25.             case «FirstCategory»:
  26.                 $calculateRating = $this->calculateRatingForFirstCategory;
  27.                 break;
  28.             case «SecondCategory»:
  29.                 $calculateRating = $this->calculateRatingForSecondCategory;
  30.                 break;
  31.             case «ThirdCategory»:
  32.                 $calculateRating = $this->calculateRatingForThirdCategory;
  33.                 break;
  34.            
  35.         }
  36.         $person->setCalculateRating = $calculateRating;
  37.         //..
  38.         //..
  39.         return $person;
  40.     }
  41.    
  42.    
  43.    
  44.     protected function calculateRatingForFirstCategory(): float
  45.     {
  46.         // do something
  47.     }
  48.    
  49.     protected function calculateRatingForSecondCategory(): float
  50.     {
  51.         // do something
  52.     }
  53.    
  54.     protected function calculateRatingForThirdCategory(): float
  55.     {
  56.         // do something
  57.     }
  58.    
  59. }
  60.  
  61.  
  62. $personProducer = new PersonProducer();
  63.  
  64. $person = PersonProducer->getNew($parameters);

Или даже использовать разные классы.
Примерно так:

PHP:
  1. class Person
  2. {
  3.     protected string $category;
  4.    
  5.     protected float $rating;
  6.    
  7.     //..
  8.  
  9. }
  10.  
  11. class PersonFirstCategory Extends Person
  12. {
  13.     //..
  14.     protected function calculateRating(): float
  15.     {
  16.         // calculate rating for FirstCategory
  17.     }
  18.     //..
  19. }
  20.  
  21. class PersonSecondCategory Extends Person
  22. {
  23.     //..
  24.     protected function calculateRating(): float
  25.     {
  26.         // calculate rating for SecondCategory
  27.     }
  28.     //..
  29. }
  30. class PersonThirdCategory Extends Person
  31. {
  32.     //..
  33.     protected function calculateRating(): float
  34.     {
  35.         // calculate rating for ThirdCategory
  36.     }
  37.     //..
  38. }
  39.  
  40. class PersonProducer
  41. {
  42.    
  43.     public function getNew($parameters): Person
  44.     {
  45.         switch ($parameters->category) {
  46.            
  47.             case «FirstCategory»:
  48.                 $person = new PersonFirstCategory();
  49.                 break;
  50.             case «SecondCategory»:
  51.                 $person = new PersonSecondCategory();
  52.                 break;
  53.             case «ThirdCategory»:
  54.                 $person = new PersonThirdCategory();
  55.                 break;
  56.            
  57.         }
  58.  
  59.         return $person;
  60.     }
  61.    
  62. }
  63.  
  64.  
  65. $personProducer = new PersonProducer();
  66.  
  67. $person = PersonProducer->getNew($parameters);

Киньте в меня мануалом с примерами, как писать такие вещи правильно.
Что бы если через годик мне или другому человеку было меньше проблем изменить/добавить новый расчёт рейтинга.

 

Я бы создал класс Person. Методы и свойства у него определил. Как у тебя в первом случае. Только создал бы всего один метод расчёта рейтинга на все случаи. Но подождем, что скажут другие.

 

@romt добрый день.
Вот хорошая статья на тему полиморфизма.
И в ней наглядно с примером показано как написать структуру и даже как её можно использовать.

Хотя и ваши рассуждения по коду о полиморфизме где-то рядом с истиной, но всё равно написаны с ошибками и не реализуют полиморфизм. При этом второй вариант кода совсем что-то мимо.

Поэтому очень рекомендую вам ознакомиться со статейкой, что я дал выше.

 

Почитать «PHP 8: объекты, шаблоны и методики программирования, 6-е издание «
Посмотреть как устроен ArgumentResolver у Symfony.
Псевдокод:

PHP:
  1. <?php
  2.  
  3. class Person
  4. {
  5.     protected string $category;
  6.  
  7.     protected RatingInterface $rating;
  8.  
  9.     public function __construct(RatingInterface $rating)
  10.     {
  11.         $this->rating = $rating;
  12.     }
  13.  
  14.     public function calculateRating(): float
  15.     {
  16.         return $this->rating->calculateRating();
  17.     }
  18. }
  19.  
  20. interface RatingInterface
  21. {
  22.     public function calculateRating(): float;
  23.  
  24. }
  25.  
  26. interface RatingForCategoryResolverInterface
  27. {
  28.     public function supports(string $categoryName): bool;
  29.  
  30.     public function resolve(): RatingInterface;
  31. }
  32.  
  33. class FirstCategoryRating implements RatingInterface
  34. {
  35.     public function calculateRating(): float
  36.     {
  37.         return 0;
  38.     }
  39. }
  40.  
  41. class SecondCategoryRating implements RatingInterface
  42. {
  43.     public function calculateRating(): float
  44.     {
  45.         return 1;
  46.     }
  47. }
  48.  
  49. class ThirdCategoryRating implements RatingInterface
  50. {
  51.     public function calculateRating(): float
  52.     {
  53.         return 2;
  54.     }
  55. }
  56.  
  57. class FirstCategoryRatingResolver implements RatingForCategoryResolverInterface
  58. {
  59.  
  60.     public function supports(string $categoryName): bool
  61.     {
  62.         return mb_strtolower(«FirstCategory») == mb_strtolower($categoryName);
  63.     }
  64.  
  65.     public function resolve(): RatingInterface
  66.     {
  67.         return new FirstCategoryRating;
  68.     }
  69. }
  70.  
  71. class SecondCategoryRatingResolver implements RatingForCategoryResolverInterface
  72. {
  73.  
  74.     public function supports(string $categoryName): bool
  75.     {
  76.         return mb_strtolower(«SecondCategory») == mb_strtolower($categoryName);
  77.     }
  78.  
  79.     public function resolve(): RatingInterface
  80.     {
  81.         return new SecondCategoryRating;
  82.     }
  83. }
  84.  
  85. class ThirdCategoryRatingResolver implements RatingForCategoryResolverInterface
  86. {
  87.  
  88.     public function supports(string $categoryName): bool
  89.     {
  90.         return mb_strtolower(«ThirdCategory») == mb_strtolower($categoryName);
  91.     }
  92.  
  93.     public function resolve(): RatingInterface
  94.     {
  95.         return new ThirdCategoryRating;
  96.     }
  97. }
  98.  
  99. class PersonProducer
  100. {
  101.     /**@var RatingForCategoryResolverInterface[] */
  102.     private array $ratings = [];
  103.  
  104.  
  105.     /**
  106.      * @return RatingForCategoryResolverInterface[]
  107.      */
  108.     public function getDefaultRatingResolvers(): array
  109.     {
  110.         return [
  111.             new FirstCategoryRatingResolver,
  112.             new ThirdCategoryRatingResolver,
  113.             new SecondCategoryRatingResolver
  114.         ];
  115.     }
  116.  
  117.     public function addRatingResolver(RatingForCategoryResolverInterface $ratingResolver)
  118.     {
  119.         if (!in_array($ratingResolver, $this->ratings)) {
  120.             $this->ratings[] = $ratingResolver;
  121.         }
  122.     }
  123.  
  124.     public function get($parameters): Person
  125.     {
  126.         $ratings = $this->ratings ?: $this->getDefaultRatingResolvers();
  127.  
  128.         foreach ($ratings as $rating) {
  129.             if ($rating->supports($parameters->category)) {
  130.                 return new Person($rating->resolve());
  131.             }
  132.         }
  133.         throw new RuntimeException(sprintf(‘Rating by category %s not found’, $parameters->category));
  134.     }
  135.  
  136. }
  137.  
  138.  
  139. $personProducer = new PersonProducer();
  140.  
  141. $person = $personProducer->get($parameters);
 

Спасибо,
статью почитал,
книгу скачал, будем изучать.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *