PHP: kiedy używać tablic, a kiedy używać obiektów do konstrukcji kodu głównie przechowujących dane?

37

PHP jest mieszanym językiem paradygmatu, umożliwiającym używanie i zwracanie nieobiektywnych typów danych, takich jak tablice. Zadaję pytanie, aby spróbować wyjaśnić niektóre wytyczne dotyczące wyboru tablic vs obiektów przy podejmowaniu decyzji, jaką konstrukcję programistyczną zastosować w konkretnej sytuacji.

To jest naprawdę pytanie o sposoby kodowania danych za pomocą konstrukcji języka PHP i kiedy bardziej prawdopodobne jest wybranie jednego ze sposobów w celu przekazywania danych (tj. Architektura zorientowana na usługi lub usługi sieciowe).

Przykład

Załóżmy, że masz typ elementu składający się z {koszt, nazwa, numer części, liczba pozycji}. Twój program wymaga wyświetlenia kilku takich typów elementów, w przypadku gdy zdecydujesz się użyć tablicy jako zewnętrznego kontenera do przechowywania każdego z tych typów elementów. [Możesz także użyć PHP ArrayObjectdo paradygmatu OO, ale moje pytanie nie dotyczy tej (zewnętrznej) tablicy]. Moje pytanie dotyczy tego, jak zakodować dane typu elementu i jaki paradygmat zastosować. PHP pozwala na użycie PHP Native Arrayslub PHP Objects.

Mogę tutaj zakodować takie dane na dwa sposoby:

//PHP's associative arrays:
$ret = array(
    0 => array(
        'cost' => 10.00, 
        'name' => 'item1',
        'part_number' => 'zyz-100', 
        'item_count' => 15
        ),
    1 => array(
        'cost' => 34.00, 
        'name' => 'item2', 
        'part_number' => 'abc-230', 
        'item_count' => 42
        ),
  );

vs

//here ItemType is encapsulated into an object
$ret = array(
  0 => new ItemType(10.00, 'item1', 'zyz-100', 15),
  1 => new ItemType(34.00, 'item2', 'abc-230', 42),
);

class ItemType
{
    private $price;
    private $name;
    private $partNumber;
    private $itemCount;

    function __construct($price, $name, $partNumber, $itemCount) {..}
}

Co ja mysle

Kodowanie tablic jest lekkie i bardziej przystosowane do JSON, ale może być łatwiejsze do zepsucia. Błędnie przeliteruj jeden z kluczy tablicy asocjacyjnej i możesz napotkać błąd trudniejszy do uchwycenia. Ale łatwiej jest również zmienić kaprys. Powiedzmy, że nie chcę item_countjuż przechowywać , mogę użyć dowolnego oprogramowania do przetwarzania tekstu, aby łatwo usunąć wszystkie item_countwystąpienia w tablicy, a następnie zaktualizować inne funkcje, które z niego korzystają. Może to być bardziej żmudny proces, ale jest prosty.

Kodowanie obiektowe wywołuje funkcje języka IDE i PHP i ułatwia wcześniejsze wykrycie błędów, ale przede wszystkim trudniej jest je programować i kodować. Mówię mocniej, ponieważ musisz trochę pomyśleć o swoich obiektach, myśleć z wyprzedzeniem, a kodowanie OO wymaga nieco większego obciążenia poznawczego niż pisanie struktur tablicowych. To powiedziawszy, po zakodowaniu niektóre zmiany mogą być łatwiejsze do wdrożenia, w pewnym sensie, że usunięcie item_count, na przykład, wymagać będzie zmiany mniejszej liczby wierszy kodu. Jednak same zmiany mogą nadal wymagać większego obciążenia poznawczego w porównaniu z metodą tablicową, ponieważ w grę wchodzą urządzenia OO wyższego poziomu.

Pytanie

W niektórych przypadkach jest to jasne, jak w przypadkach, w których będę musiał wykonywać manipulacje na danych. Ale w niektórych przypadkach, gdy muszę po prostu przechowywać kilka wierszy danych „Typu przedmiotu”, nie mam jasnych wytycznych ani rozważań, na których mogę się oprzeć przy podejmowaniu decyzji, czy użyć tablic, czy też zbudować obiekty. Wygląda na to, że mogę rzucić monetą i wybrać jedną. Czy tak jest w tym przypadku?

Dennis
źródło
2
Uwaga można uzyskać lekki przedmiot oddając tablicę do obiektu: (object)['foo'=>'bar']. Wynikowa wartość ma klasę StdClass, zostanie w całości zakodowana w JSON, json_encode()a nazwy właściwości można zmienić tak łatwo, jak indeksy tablic (co nie zawsze jest takie łatwe, gdy dostęp do nich jest pośrednio przez zmienną). Istnieją jednak różne operacje na takich wartościach; na przykład nie masz związków obiektowych, ponieważ masz związki tablicowe i nie możesz bezpośrednio korzystać z array_*()funkcji.
poza
3
Masz 3 opcje: 1: array , 2: User-defined Class , 3: stdClass . Wydajność pod względem prędkości jest prawie taka sama podczas porównywania arrayi a User-defined class(jak twoje ItemType), ale zdefiniowane classes zwykle zużywają mniej pamięci niż arrays. stdClassz drugiej strony jest najwolniejsza z trzech opcji i zużywa najwięcej pamięci.
Andy,

Odpowiedzi:

33

Sposób, w jaki to widzę, zależy od tego, co zamierzasz zrobić z danymi później. Na podstawie kilku prostych kontroli możesz ustalić, która z dwóch struktur danych jest dla Ciebie lepsza:

  1. Czy dane te mają jakąkolwiek logikę?

    Na przykład jest $priceprzechowywany jako liczba całkowita centów, więc produkt o cenie 9,99 USD miałby, price = 999a nie miałby price = 9.99? (Prawdopodobnie tak) A może partNumbermusi pasować do określonego wyrażenia regularnego? Lub czy musisz być w stanie łatwo sprawdzić, czy itemCountjest dostępny w ekwipunku? Czy będziesz musiał wykonywać te funkcje w przyszłości? Jeśli tak, to najlepiej założyć teraz klasę. Oznacza to, że możesz zdefiniować ograniczenia i logikę wbudowane w strukturę danych: private $myPricejest ustawione na 999ale $item->getPriceString()zwraca $9.99i $item->inStock()jest dostępne do wywołania w twojej aplikacji.

  2. Czy zamierzasz przekazywać te dane do wielu funkcji PHP?

    Jeśli tak, skorzystaj z klasy. Jeśli generujesz te dane raz, aby wykonać na nim pewne transformacje lub po prostu wysłać dane JSON do innej aplikacji (JavaScript lub w inny sposób), tablica jest łatwiejszym wyborem. Ale jeśli masz więcej niż dwie funkcje PHP, które akceptują te dane jako parametr, użyj klasy. Jeśli nic więcej, pozwala to zdefiniować someFunction(MyProductClass $product) {i jest bardzo jasne, czego oczekują twoje funkcje jako dane wejściowe. W miarę skalowania kodu i posiadania większej liczby funkcji o wiele łatwiej będzie wiedzieć, jakiego rodzaju dane akceptuje każda funkcja. Widzenie someFunction($someArrayData) {nie jest tak jasne. Ponadto nie wymusza to spójności typu i oznacza, że ​​(jak powiedziałeś) elastyczna struktura tablicy może później powodować ból rozwojowy

  3. Budujesz bibliotekę lub współdzieloną bazę kodu?

    Jeśli tak, skorzystaj z klasy! Pomyśl o nowym programiście korzystającym z Twojej biblioteki lub innym programiście w firmie, który nigdy wcześniej nie używał Twojego kodu. O wiele łatwiej będzie im spojrzeć na definicję klasy i zrozumieć, co ta klasa robi, lub zobaczyć szereg funkcji w bibliotece, które akceptują obiekty określonej klasy, niż próbować odgadnąć, jaką strukturę muszą wygenerować w liczba tablic. Dotyczy to również problemów z spójnością danych w punkcie 1: jeśli tworzysz bibliotekę lub bazę kodu współużytkowanego, bądź miły dla użytkowników: daj im klasy, które wymuszają spójność danych i chronią je przed popełnianiem błędów przy projektowaniu danych .

  4. Czy to niewielka część aplikacji, czy tylko transformacja danych? Czy nie pasujesz do żadnego z powyższych?

    Klasa może być za duża; użyj tablicy, jeśli ci to odpowiada i łatwiej. Jak wspomniano powyżej, jeśli tylko generujesz uporządkowane dane do wysłania jako JSON, YAML lub XML lub cokolwiek innego, nie zawracaj sobie głowy klasą, chyba że jest taka potrzeba. Jeśli piszesz mały moduł w większej aplikacji i żadne inne moduły / zespoły nie muszą się komunikować z twoim kodem, być może tablica jest wystarczająca.

Ostatecznie weź pod uwagę potrzeby skalowania swojego kodu i zastanów się, czy tablica strukturalna może być szybką poprawką, ale klasa jest znacznie bardziej odpornym i skalowalnym rozwiązaniem.

Weź również pod uwagę następujące kwestie: jeśli masz klasę i chcesz wyprowadzać dane do JSON, nie ma powodu, dla którego nie możesz zdefiniować json_data()metody swojej klasy, która zwraca tablicę danych zgodną z JSON w klasie. Tak zrobiłem w aplikacjach PHP, w których musiałem przesyłać dane klasy jako JSON. Jako przykład:

class Order {
    private $my_total;
    private $my_lineitems;

    public function getItems() { return $this->my_lineitems; }
    public function addItem(Product $p) { $this->my_lineitems[] = $p; }
    public function getTotal() { return $this->my_total; }

    public function forJSON() {
        $items_json = array();
        foreach($this->my_lineitems as $item) $items_json[] = $item->forJSON();
        return array(
            'total' => $this->getTotal(),
            'items' => $items_json
        );
    }
}

$o = new Order();
// do some stuff with it
$json = json_encode($o->forJSON());
Josh
źródło
Nie zmieniasz elementów my_lineitems, więc uzyskanie ich przez odwołanie nie jest konieczne, zwłaszcza, że ​​zmienne przechowują tylko identyfikator obiektu, a nie sam obiekt. php.net/manual/en/language.oop5.references.php
Chinoto Vokro
Zgoda. Myślę, że to był fragment znacznie większej aplikacji, która potrzebowała referencji z jakiegoś powodu, czy coś ...
Josh
2

Możesz zaimplementować strukturę json za pomocą interfejsu JsonSerializable i użyć dowolnej zagnieżdżonej tablicy / klasy. Klasa jest szybsza w operacjach get / set i łatwiejsza do debugowania. Z Array nie potrzebujesz deklaracji.

class Order implements \JsonSerializable{
    private $my_total;
    private $my_lineitems;

    public function getItems() { return $this->my_lineitems; }
    public function addItem(Product $p) { $this->my_lineitems[] = $p; }
    public function getTotal() { return $this->my_total; }

    public function jsonSerialize(){
        return [
            'total'=>$this->my_total,
            'products'=>$this->my_lineitems;
        ];
    }
}

class Product implements \JsonSerializable{
    private $name;
    private $price;

    public function jsonSerialize(){ 
        return [
            'name'=>$this->name, 
            'price'=>$this->price
        ];
    }
}

$order = new Order();
$order->addProduct(new Product('Product1', 15));
$order->addProduct(new Product('Product2', 35));
$order->addProduct(new Product('Product3', 42));
$json = json_encode(['order'=>$order, 'username'=>'Eughen']);
/*
json = {
    order: {
        total: 92, 
        products: [
            {
                name: 'Product1',
                price: 15
            }, 
            {
                name: 'Product2',
                price: 35
            },
            {
                name: 'Product3',
                price: 42
            }
        ]
    }, 
    username: 'Eughen'
}
*/
El
źródło
Co to pytanie dodaje, że istniejąca zaakceptowana odpowiedź?
esoterik
@esoterik nesting. json_encode(['order'=>$o]);w existsing Akceptowane odpowiedź jest puste: {"order":{}}. Pewnie, że możesz użyć: za $o->forJSON()każdym razem na każdym obiekcie na każdym poziomie, ale nie jest to naprawdę dobry projekt. Ponieważ cały czas musisz pisać coś takiego: $items_json = array(); foreach($this->my_lineitems as $item) $items_json[] = $item->forJSON();
El