В PHP обзор структуры данных и критика


Я в последнее время работаю по образовательной рамках проекта, чтобы узнать больше о веб-разработке и проблемы, которые возникают в более сложных приложениях. Часть принципов Рамочной основы должна быть как ориентированный объект, как это возможно. Например, большинство данных, возвращаемых из запроса к источнику данных будет возвращена в качестве конкретного объекта домена, или набор объектов предметной области. Я мог бы просто вернуть массив с соответствующими объектами, но это не достаточно хорошо для моих потребностей. Итак, мне нужна лучшая структура данных.

Некоторые ключевые аспекты структуры данных для хранения этих объектов:

  • Должны принимать только объекты определенного типа; Тип объекта разрешается хранить должны быть переданы в метод__Construct() и должно быть строковое представление класса или интерфейса название.
  • Никакие другие параметры не должны быть нужны в метод__Construct метод.
  • Надо уметь итерации в цикле foreach петли.
  • Структура данных должна иметь какой-то механизм, чтобы ограничить количество элементов, которые могут быть добавлены к нему.

С этими вещами в виду, я создал следующий интерфейс: таблицы. Поскольку данные структуры предназначены для использования в рамках объекты переданы продлить CoreObject, что является очень простой абстрактный класс, который обеспечивает равна(CoreObject $объект), метод hashCode() и __метод toString() методы. Это было сделано для того, что есть способ, чтобы сравнить равенство объектов для целей поиска.

Ниже приведены классы, используемые в моей первой реализации таблицы интерфейс. Первый класс-это BaseIteratingList , что обеспечивает несколько основных функций и реализует итератор интерфейс, чтобы позволить зацикливание. Второй класс UniqueList , что не добавит дублировать объекты в список и реализует остальные методы интерфейса. Заключительный класс ObjectTypeValidator , используемых для обеспечения объектов добавить() и установить() выполнить или расширяет правильного родительского типа.

BaseIteratingList:

/**
 * This is a base list, it provides a means to store the objects and perform basic
 * common functionality on those elements.
 *
 * This list also implements the methods necessary for the list to be iterated
 * over.  This list should be able to be used in a foreach() loop
 */
abstract class BaseIteratingList extends CoreObject implements DataList {

    /**
     * The array structure used to store the objects
     *
     * @var array
     */
    protected $dataStorage = array();

    /**
     * The number of elements in the list.
     *
     * This value also represents the index used for the next saved element.
     *
     * @var int
     */
    protected $size = 0;

    /**
     * An object used to verify that the type of object being added to the list
     * implements the correct interface or extends the correct class.
     *
     * @var ObjectTypeValidator
     */
    private $TypeValidator;

    /**
     * The position of the pointer used for the iterator functions.
     *
     * @var int
     */
    private $currentPointer = 0;

    /**
     * Each list should be passed the name of the interface or class that is allowed to
     * be stored in the list.
     *
     * @param string $parentType
     */
    public function __construct($parentType) {
        $this->TypeValidator = new ObjectTypeValidator($parentType);
    }

    public function rewind() {
         $this->currentPointer = 0;
    }

    /**
     * @return CoreObject
     */
    public function current() {
        return $this->dataStorage[$this->currentPointer];
    }

    /**
     * @return int
     */
    public function key() {
        return $this->currentPointer;
    }

    public function next() {
        ++$this->currentPointer;
    }

    /**
     * @return boolean
     */
    public function valid() {
        return isset($this->dataStorage[$this->currentPointer]);
    }

    /**
     * Returns the number representing the index for the passed element, or -1 if
     * the element could not be found.
     *
     * @param CoreObject $Object
     * @return int
     */
    public function indexOf(CoreObject $Object) {
        $index = -1;
        for ($i = 0; $i < $this->size(); $i++) {
            $ListedObject = $this->dataStorage[$i];
            if ($Object->equals($ListedObject)) {
                $index = $i;
                break;
            }
        }
        return $index;
    }

    /**
     * Will ensure the index passed is a valid range and return the CoreObject
     * associated with it if it is a valid range.
     *
     * @param int $index
     * @return CoreObject
     * @throws OutOfRangeException
     */
    public function get($index) {
        $this->throwExceptionIfIndexOutOfRange($index);
        $Object = $this->dataStorage[$index];
        return $Object;
    }

    /**
     * @param int $index
     * @throws OutOfRangeException
     */
    protected function throwExceptionIfIndexOutOfRange($index) {
        if (!is_int($index) || $index < 0 || $index >= $this->size()) {
            throw new OutOfRangeException('The requested index is not valid.');
        }
    }

    /**
     * @param CoreObject $Object
     * @throws InvalidArgumentException
     */
    protected function throwExceptionIfObjectNotParentType(CoreObject $Object) {
        $isObjectValid = $this->TypeValidator->isObjectParentType($Object);
        if (!$isObjectValid) {
            throw new InvalidArgumentException('The object passed does not implement/extend ' . $this->TypeValidator->getParentType());
        }
    }

    /**
     * Determines whether or not the list contains the given object.
     *
     * @param CoreObject $Object
     * @return boolean
     */
    public function contains(CoreObject $Object) {
        $objectContained = false;
        $objectIndex = $this->indexOf($Object);
        if ($objectIndex >= 0) {
            $objectContained = true;
        }
        return $objectContained;
    }

    /**
     * @return boolean
     */
    public function isEmpty() {
        return ($this->size === 0);
    }

    /**
     * @return int
     */
    public function size() {
        return $this->size;
    }

} 

UniqueList:

/**
 * This class is an implementation of the DataList interface, with data storage and
 * iteration responsibilities being handled by its immediate superclass.
 *
 * This data structure guarantees that the objects added to it implement the appropriate
 * interface or extends the appropriate class.  This data structure will also ensure
 * that only unique objects are added to the List.  The only means in which to set the
 * type of objects stored in the class is through the constructor.  If you want
 * the list to store different types of objects you will need to create a new one.
 *
 * @see ObjectTypeValidator
 */
class UniqueList extends BaseIteratingList {

    /**
     * A zero in the $maxSize property value means the list may have an unlimited
     * number of objects.
     *
     * @var int
     */
    private $maxSize;

    /**
     * Will add the passed object to the list, given (a) there are enough empty buckets
     * in the list, (b) the object properly implements the $parentType and (c) the
     * object does not already exist in the list.
     *
     * @param CoreObject $Object
     * @throws OutOfRangeException
     *         IllegalArgumentException
     */
    public function add(CoreObject $Object) {
        $this->throwExceptionIfNoAvailableBuckets();
        $this->throwExceptionIfObjectNotParentType($Object);
        $isObjectInList = $this->contains($Object);
        if (!$isObjectInList) {
            $this->_add($Object);
        }
    }

    /**
     * @throws OutOfRangeException
     */
    private function throwExceptionIfNoAvailableBuckets() {
        $isThereBucketsInList = $this->hasAvailableBuckets();
        if (!$isThereBucketsInList) {
            throw new OutOfRangeException('The list does not have any buckets left to store the object.');
        }
    }

    /**
     * Will return whether or not the list has enough buckets to add 1 additional
     * element.
     *
     * Note, if the $maxSize is set to 0 then there will always be available buckets
     * for the list.
     *
     * @return boolean
     */
    private function hasAvailableBuckets() {
        $hasBuckets = true;
        if ($this->maxSize > 0) {
            if ($this->size >= $this->maxSize) {
                $hasBuckets = false;
            }
        } 
        return $hasBuckets;
    }

    /**
     * Will add the passed object to the storage and increment the size of the
     * list.
     *
     * @param CoreObject $Object
     */
    private function _add(CoreObject $Object) {
        $arrayIndex = $this->size;
        $this->dataStorage[$arrayIndex] = $Object;
        $this->size++;
    }

    /**
     * Should change the index of the given list to the object passed.
     *
     * @param int $index
     * @param CoreObject $Object
     * @throws OutOfRangeException
     *         IllegalArgumentException
     */
    public function set($index, CoreObject $Object) {
        $this->throwExceptionIfIndexOutOfRange($index);
        $this->throwExceptionIfObjectNotParentType($Object);
        $this->dataStorage[$index] = null;
        $this->dataStorage[$index] = $Object;
    }

    /**
     * Should remove the object from the list if it exists and resize the array.
     *
     * Please note that it is highly unrecommended that you remove objects from a
     * list while looping through the list, for example in a foreach() loop.  The
     * initial implementation simply wasn't sufficient to handle this, please avoid
     * doing so.  This feature may be added in a future release.
     *
     * @param CoreObject $Object
     */
    public function remove(CoreObject $Object) {
        $objectIndex = $this->indexOf($Object);
        if ($objectIndex >= 0) {
            $this->dataStorage[$objectIndex] = null;
            $this->resizeList();
        }
    }

    /**
     * Should be called after an element is removed from the list, will create a
     * new array that does not contain any null values and then assign that array
     * to the $dataStorage property.
     */
    private function resizeList() {
        $newArray = array();
        for ($i = 0; $i < $this->size(); $i++) {
            if (isset($this->dataStorage[$i])) {
                $newArray[] = $this->dataStorage[$i];
            }
        }
        $this->dataStorage = $newArray;
        $this->size = count($this->dataStorage);
    }

    /**
     * If the passed value is a valid, unsigned integer the maxSize is set.
     *
     * Note if this value is set to 0 this indicates that there is no limit on the
     * number of buckets that can be added to the list.  If the maxSize is set
     * AFTER the list has already been created the maxSize will automatically be
     * set to the maxSize of the existing list.  An error message should also be
     * triggered if this happens.
     *
     * @param int $maxSize
     * @return int
     */
    public function setMaxSize($maxSize) {
        $isSizeInteger = is_int($maxSize);
        $isSizeBigEnough = ($maxSize >= 0);
        $isSizeValid = ($isSizeInteger && $isSizeBigEnough);
        if (!$isSizeValid) {
            $maxSize = 0;
            error_log('The maximum size for the list was not a valid integer value, no maximum size set.');
        }
        $currentSize = $this->size();
        if ($currentSize > $maxSize) {
            $maxSize = $currentSize;
            error_log('The current size of the list is greater than the maximum size set, maximum size set to current size.');
        }
        return $this->maxSize = $maxSize;
    }

}

ObjectTypeValidator:

/**
 * This class ensures that a given object implements or extends a specific interface
 * or class.
 */
class ObjectTypeValidator extends CoreObject {

    /**
     * @var ReflectionClass
     */
    protected $ReflectedParentType;

    /**
     * Will create a ReflectionClass of the passed $parentType, if the
     *
     * @param string $parentType
     * @throws InvalidArgumentException
     */
    public function __construct($parentType) {
        $this->ReflectedParentType = $this->createReflectedParentType($parentType);
    }

    /**
     * @param string $parentType
     * @return ReflectionClass
     * @throws InvalidArgumentException
     */
    private function createReflectedParentType($parentType) {
        $ReflectedParent = null;
        try {
            $ReflectedParent = new ReflectionClass($parentType);
        } catch (ReflectionException $ReflectionExc) {
            throw new InvalidArgumentException('There was an error reflecting the parent type, ' . $parentType, null, $ReflectionExc);
        }
        return $ReflectedParent;
    }

    /**
     * Ensures that the passed $Object either is an instance of, extends  or implements
     * the $ReflectedParentType.
     *
     * We chose to go with Reflection instead of using the is_a() function or
     * instanceof operator due to the fact that the $parentType expected from the
     * user in __construct() is of type string.  By using Reflection we ensure that
     * the class can be included through the ClassLoader and can ensure that
     * interface implementation and class inheritance is covered.
     *
     * @param CoreObject $Object
     * @return boolean
     */
    public function isObjectParentType(CoreObject $Object) {
        $isValid = false;
        $ReflectedParent = $this->ReflectedParentType;
        $parentName = $ReflectedParent->getShortName();
        try {
            $ReflectedObject = new ReflectionClass($Object);
            if ($ReflectedParent->isInterface()) {
                if ($ReflectedObject->implementsInterface($parentName)) {
                    $isValid = true;
                }
            } else {
                if ($ReflectedObject->getShortName() === $parentName || $ReflectedObject->isSubclassOf($parentName)) {
                    $isValid = true;
                }
            }
        } catch (ReflectionException $ReflectionExc) {
            // @codeCoverageIgnoreStart
            error_log($ReflectionExc->getMessage());
            // @codeCoverageIgnoreEnd
        }
        return $isValid;
    }

    /**
     * @return string
     */
    public function getParentType() {
        return $this->ReflectedParentType->getShortName();
    }
}

Ну, есть в реализации, он был протестирован блок и пройти все испытания. Тем не менее, есть несколько известных проблем или проблем у меня с Код:

  • "Скрытые" зависимости ObjectTypeValidator. Я не уверен, что будет разумным решением для этого. В один момент эта функциональность была на самом деле жестко в списке, однако я также хочу, чтобы реализовать некоторые другие структуры данных, которые потребуются объект проверки тип, так что я абстрагировалась его. Однако, я не хочу, чтобы спросить пользователя, чтобы придать ObjectTypeValidator объект. Я должен попробовать, чтобы скрыть это от пользователя или требует объект зависимости, который будет введен вместо строки?

  • Когда пробегаюсь по списку существует ненадежное поведение, если объект удаляется. Сейчас мое решение-это документ, что она не предназначена для объектов, которые должны быть удалены из списка в циклической структуре...однако это меньше, чем идеальное решение. Как я могла позволить для удаления объектов из списка и убедитесь, что циклические структуры не повреждаются или теряют правильный объект, который должен быть указан?

  • Вы заметили что-нибудь нелогично, неправильно или некрасиво о моем коде?

Ну, я понимаю, что много читать, так что я оставлю его в этом. Я ценю любые отзывы на код или заданные вопросы.

Редактировать в ответ на Яннис Rizos ответ:

Правда, я не был осведомлен о широте итераторы доступен из шлицов, возможно, я вернусь и рефакторинг структуры данных использовать один из этих итераторов, возможно, фиксации мой цикл выпуска. Это, как говорится, моя ссылка на таблицы интерфейс java в список интерфейс и действительно то, что я попытался модель структуры данных после. Когда я смотрю на СПЛ классы выглядят они слишком большие и громоздкие, и главная причина моего письма этот проект-это опыт, я думаю, что некоторые колеса заново. Иногда вы хотите узнать о колесах, хотя.


Я определенно не хочу, чтобы мою базу CoreObject от ArrayObject...это слишком много для чего-то действительно намереваясь лишь предоставить некоторые общие методы для других классов. Мне нравится думать о CoreObject как stdClass или объект в Java, но в рамках. По сути, это только означает, чтобы обеспечить способ, чтобы сравнить 2 объекта, чтобы определить, если они одинаковы или нет и печатать определенное значение, когда объект используется в качестве строки, я не хочу, чтобы CoreObject делать нечто больше, чем это. Впрочем, возможно, я буду реализовывать этот интерфейс, объект, и просто поставить CoreObject в рамках реализации.


Я выбрал -1 в качестве возвращаемого значения для метода indexOf() исходя из 2 причин. (1) я изначально основывая список от реализации Java и вот что они делают. (2) я не хочу, чтобы пользователи, которые пытаются просто проверить ложь на возвращаемое значение. Например:

$index = $List->indexOf($Object);

// uh oh!  $index is '0' and is really valid
if (!$index) {
    // do stuff here
}

Тебе придется изменить это, чтобы проверить на 0 и проверить на ложь:

if (!$index && $index !== 0) {

}

Я не люблю этого и хотели, чтобы избежать этого, когда это возможно.



439
4
php
задан 6 декабря 2011 в 11:12 Источник Поделиться
Комментарии
1 ответ

Зачем изобретать велосипед и не строить на проходимым, интерфейса ArrayAccess и сериализуемые шлицов интерфейсы, или, более реалистично, на одном из своих конкретных детей?

На подобные требования я бы возможно построить свою CoreObject по ArrayObject.

/**
* Returns the number representing the index for the passed element, or -1 if
* the element could not be found.
*
* @param CoreObject $Object
* @return int
*/
public function indexOf(CoreObject $Object) { ... }

Я бы предложил вернуться ложные вместо -1 , если элемент не найден. Он чувствует себя немного более естественным и ожидаемым.

public function set($index, CoreObject $Object) 

Поскольку части каркаса принципов, чтобы быть объектно-ориентированным, как это возможно., вы должны рассмотреть возможность использования интерфейсов вместо классов, для зависимостей объектов.

И как "скрытые" зависимости ObjectTypeValidator: это сильно зависит от того, что эти рамки для. Вы зашли на общепринятый подход, а не академическую (инъекции). Там нет правильных или неправильных здесь, все зависит от назначения данной программы и аудитории, академический подход не всегда может быть воспитательный подход.


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


Редактировать в ответ на изменение в ответ на мой ответ :)


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

Я определенно не хотел бы я CoreObject от ArrayObject...это слишком много для чего-то действительно намереваясь лишь предоставить некоторые общие методы для других классов.

Как это опыт, вы должны сосредоточиться на изучении языка и его закидоны. Ява <> PHP, в довольно много способов. Вы не должны ограничивать себя в копировать Java-интерфейс, это хороший интерфейс для клонирования, но теперь, когда вы сделали, что вы должны изучить вопрос о повышении эффективности его собственные функции в PHP. Вы должны взять столько преимущество родной вещи, как это возможно, разница в производительности заметна.

Я должен признать, что ArrayObject был пример силы, моей основной целью было указать вам на СПЛ. Я сделал выбор что-то похожее на ArrayObject, но требования не были точно такие же.

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

public function remove(CoreObject $Object) {
$objectIndex = $this->indexOf($Object);
if ($objectIndex >= 0) {
$this->dataStorage[$objectIndex] = null;
$this->resizeList();
}
}

может быть переписан как:

public function remove(CoreObject $Object) {    
$objectIndex = $this->indexOf($Object);
if ($objectIndex >= 0) {
unset($this->dataStorage[$objectIndex]);
$this->dataStorage = array_values($this->dataStorage);

return true;
}

return false;
}

и избежать дорогостоящего вызова resizeList(). И сделать что-то вернуть, сообщить пользователю, если операция выполнена успешно или нет, независимо от того, что Java-интерфейса делает.


Я выбрал -1 в качестве возвращаемого значения для показателей() основаны на 2 причины.
...
(2) я не хочу, чтобы пользователи, которые пытаются просто проверить ложь на возвращаемое значение.

Пользователи должны проверить на ложь, как, что:

if ($index !== false) {
// do stuff here
}

Вы не должны компенсировать для пользователей, которые не знакомы с РНР операторы сравнения.

4
ответ дан 7 декабря 2011 в 01:12 Источник Поделиться