2010-07-14 4 views
2

Как бы вы обрабатывали группировку разных объектов?Сортировка и группировка объектов в коллекциях

Например, скажем, у вас есть книга спортивных карт, и вы хотите организовать и выплевывать данные об этой книге спортивных карт. Очевидно, что каждая спортивная карта должна иметь свой собственный класс и быть ее собственным предметом, но как бы вы группировали различные наборы карт с пряностями? Что, если вы хотите посмотреть, сколько карт (и даже самих карт) было сделано данным изготовителем в данном году?

Там должен быть классом card_book, который организует каждую карту с функциями, как

getTotalBySport($sport) // Gets total number of cards within a given sport

getTotalByPrice($min, $max) // Gets total number of cards within a given price range

getCardsBySport($sport) // Get an array (or whatever other container) of cards within a given sport

или если эти функции будут реализованы непосредственно в cards класса?

ответ

3

У вас будет один класс, который описывает спортивные карты. Из этого класса вы создали бы объекты, представляющие каждую карту, со всеми своими подробностями.

Вам также может понадобиться класс, представляющий коллекцию спортивных карт, чтобы управлять коллекцией спортивных карт.

Функции в классе спортивной карты (называемые методами в разговоре с ООП) касаются управления данными карты. Функции в классе коллекции будут иметь дело с коллекцией.

Edit:

Для расширения этого:

Конструктор класса карты будет принимать параметры для инициализации объекта при его создании. Как вы это решаете, зависит от различных вопросов. Поскольку, вероятно, много данных, я бы предпочел ассоциативный массив. Это упростит создание пучка объектов.

Возможно, вам также понадобятся методы настройки для всей информации, которую представляет карта, и, конечно же, методы getter, чтобы получить эту информацию. В зависимости от ваших потребностей вы можете сделать это для каждой части информации, или кусочек вещей в разумных пределах. Так, например, может быть, один способ добавить статистику за год, а не один метод для каждого конкретного типа stat. Между тем, что-то вроде имени игрока будет иметь собственный набор методов.

2

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

Вы находитесь на правильном пути с вашим отдельным классом управления, чтобы делать такие вещи, как getTotalBySport() - это определенно подходящее место для этого.

Расширяете ли вы SportCard или нет, это немного изменит поведение вашего класса управления.

+0

+1 для поднятия вероятного изменения данных между видами спорта. –

3

Я бы сказал, следуя Single Responsibility Principle.

У вас есть SportsCard, которая ничего не делает сама по себе. Это всего лишь контейнер данных.

class SportsCard 
{ 
    protected $_price; 
    protected $_sport; 

    public function __construct($price, $sport) 
    { 
     $this->_price = $price; 
     $this->_sport = $sport; 
    } 

    public function getPrice() { return $this->_price; } 
    public function getSport() { return $this->_sport; } 
} 

Карты спорта должны храниться в карточке. Если вы думаете о CardBook, то он ничего не делает, для хранения SportsCards (не Покемон или Magic The Gathering карты), за исключением, так что давайте просто сделать это сделать:

class CardBook extends SplObjectStorage 
{ 
    public function attach($card) 
    { 
     if ($card instanceof SportsCard) { 
      parent::attach($card); 
     } 
     return $this; 
    } 

    public function detach($card) 
    { 
     parent::detach($card); 
    } 
} 

Хотя нет ничего плохого добавляя методы Finder к набору SportsCard, вы также не хотите, чтобы внутри него находилась логика метода поиска. Это хорошо до тех пор, пока у вас есть только

public function findByPrice($min, $max); 
public function findBySport($sport); 

Но второй вы также добавить

public function findByPriceAndSport($sport, $min, $max); 

вы либо дубликат кода или итерацию по коллекции дважды. К счастью, PHP предлагает элегантное решение для фильтрации коллекций с итераторами, поэтому давайте ответственность за фильтрацию в отдельные классы, а также:

abstract class CardBookFilterIterator extends FilterIterator 
{ 
    public function __construct($cardBook) 
    { 
     if ($cardBook instanceof CardBook || $cardBook instanceof self) { 
      parent::__construct($cardBook); 
     } else { 
      throw new InvalidArgumentException(
       'Expected CardBook or CardBookFilterIterator'); 
     } 
    } 
} 

Этот класс просто делает, что ребенок классы принимают только ребенок себя или CardBooks. Это важно, потому что мы позже получаем доступ к методам SportsCard в реальных фильтрах и потому, что CardBooks может содержать только SportsCards, мы убеждаемся, что итерационные элементы предоставляют эти методы. Но давайте посмотрим на конкретный фильтр:

class SportCardsBySportFilter extends CardBookFilterIterator 
{ 
    protected $sport = NULL; 

    public function filterBySport($sport) 
    { 
     $this->_sport = $sport; 
     return $this; 
    } 

    public function accept() 
    { 
     return $this->current()->getSport() === $this->_sport; 
    } 
} 

FilterIterators работают путем расширения класса и модификации он принимает метод() с пользовательской логики. Если метод не возвращает FALSE, текущему итерированному элементу разрешено передавать фильтр. Итераторы могут быть сложены, поэтому вы добавляете Filter on Filter сверху, каждый из которых заботится только об одной части фильтрации, что делает ее очень удобной и расширяемой. Второй фильтр:

class SportCardsByPriceFilter extends CardBookFilterIterator 
{ 
    protected $min; 
    protected $max; 

    public function filterByPrice($min = 0, $max = PHP_INT_MAX) 
    { 
     $this->_min = $min; 
     $this->_max = $max; 
     return $this; 
    } 

    public function accept() 
    { 
     return $this->current()->getPrice() > $this->_min && 
       $this->current()->getPrice() < $this->_max; 
    } 
} 

Здесь нет никакой магии. Только метод accept и setter для критериев. Теперь, чтобы собрать его:

$cardBook = new CardBook; 
$cardBook->attach(new SportsCard('10', 'Baseball')) 
     ->attach(new SportsCard('40', 'Basketball')) 
     ->attach(new SportsCard('50', 'Soccer')) 
     ->attach(new SportsCard('20', 'Rugby')) 
     ->attach(new SportsCard('30', 'Baseball')); 

$filteredCardBook = new SportCardsBySportFilter($cardBook); 
$filteredCardBook->setFilterBySport('Baseball'); 
$filteredCardBook = new SportCardsByPriceFilter($filteredCardBook); 
$filteredCardBook->filterByPrice(20); 

print_r(iterator_to_array($filteredCardBook)); 

И это даст:

Array (
    [4] => SportsCard Object (
      [_price:protected] => 30 
      [_sport:protected] => Baseball 
     ) 
) 

Комбинирование фильтров из внутри CardBook является ветер в настоящее время. Нет дублирования кода или двойной итерации. Сохраняете ли вы методы Finder внутри CardBook или CardBookFinder, через которые вы можете проходить CardBooks, зависит от вас.

class CardBookFinder 
{ 
    protected $_filterSet; 
    protected $_cardBook; 

    public function __construct(CardBook $cardBook) 
    { 
     $this->_cardBook = $this->_filterSet = $cardBook; 
    } 

    public function getFilterSet() 
    { 
     return $this->_filterSet; 
    } 

    public function resetFilterSet() 
    { 
     return $this->_filterSet = $this->_cardBook; 
    } 

Эти методы просто убедитесь, что вы можете создать новую FilterSets и что у вас есть CardBook внутри Finder, прежде чем использовать один из методов поиска:

public function findBySport($sport) 
    { 
     $this->_filterSet = new SportCardsBySportFilter($this->getFilterSet()); 
     $this->_filterSet->filterBySport($sport); 
     return $this->_filterSet; 
    } 

    public function findByPrice($min, $max) 
    { 
     $this->_filterSet = new SportCardsByPriceFilter($this->getFilterSet()); 
     $this->_filterSet->filterByPrice(20); 
     return $this->_filterSet; 
    } 

    public function findBySportAndPrice($sport, $min, $max) 
    { 
     $this->findBySport($sport); 
     $this->_filterSet = $this->findByPrice($min, $max); 
     return $this->_filterSet; 
    } 

    public function countBySportAndPrice($sport, $min, $max) 
    { 
     return iterator_count(
      $this->findBySportAndPrice($sport, $min, $max)); 
    } 
} 

И там вы идете

$cardBookFinder = new CardBookFinder($cardBook); 
echo $cardBookFinder->countBySportAndPrice('Baseball', 20, 100); 
$cardBookFinder->resetFilterSet(); 
foreach($cardBookFinder->findBySport('Baseball') as $card) { 
    echo "{$card->getSport()} - {$card->getPrice()}"; 
} 

Не стесняйтесь приспосабливаться и улучшать свою собственную карточку.

+0

+1 хороший подход, хотя вы довольно много ложкой кормили его.Тем не менее, почему нет сеттеров в классе SportsCard? Является ли это преднамеренным или опущенным для краткости? –

+0

очень хороший ответ и очень точный. Во всяком случае, не существует «ошибки» в вашей «публичной функции findBySport ($ sport)»: $ this -> _ filterSet-> filterBySport ('Baseball'); shouln't be $ this -> _ filterSet-> filterBySport ($ sport); ? – Aif

+0

@George Спасибо. Да, я был в пикантном настроении. На самом деле, это было отличное упражнение для меня. Сеттеры опущены для краткости, хотя OP может сохранить это таким образом, если он хочет, чтобы SportsCard была неизменной. – Gordon

Смежные вопросы