Dodawanie pola do klasy w czasie wykonywania - wzorzec projektowy

15

Wyobraź sobie, że Twoi klienci chcą mieć możliwość dodania nowej właściwości (np. Koloru) do produktu w swoim sklepie internetowym w CMS.

Zamiast mieć właściwości jako pola:

class Car extends Product {
   protected String type;
   protected int seats;
}

Prawdopodobnie skończyłbyś robić coś takiego:

class Product {
   protected String productName;
   protected Map<String, Property> properties;
}

class Property {
   protected String name;
   protected String value;
}

To znaczy tworzenie własnego systemu typów na istniejącym. Wydaje mi się, że może to być postrzegane jako tworzenie języka specyficznego dla domeny, czy nie?

Czy to podejście jest znanym wzorcem projektowym? Czy rozwiązałbyś problem inaczej? Wiem, że istnieją języki, w których mogę dodać pole w czasie wykonywania, ale co z bazą danych? Wolisz dodawać / zmieniać kolumny lub używać czegoś, jak pokazano powyżej?

Dziękuję za Twój czas :).

Filip
źródło
Nie jestem pewien, jakiego języka używasz, ale jeśli jest to C #, możesz użyć typu dynamicznego, który zasadniczo przechowuje KVP w słowniku, podobnie jak to, co robisz na produktach i pozwala po prostu na właściwości bez konieczności bezpośredniego dodawania ich do kolekcja jako kolekcja. Jednak nie będziesz mieć silnego pisania. Wiem, że poprosiłeś o wzorzec projektowy, ale nie sądzę, abyś potrzebował czegoś złożonego, aby z nich skorzystać. msdn.microsoft.com/en-us/magazine/gg598922.aspx
Tony
Tony: Używam tutaj Javy, ale uważam to za pseudo coude :). Czy C # pozwoli mi zachować ten dynamiczny obiekt w bazie danych? Wątpię w to, ponieważ baza danych musi znać strukturę danych z góry.
Filip
Istnieje State Desing Pattern a-la Gang-of-Four, który sprawia, że ​​obiekt wydaje się zmieniać swój typ lub klasę w czasie wykonywania. Inne alternatywy to wzorzec projektu obserwatora lub wzorzec projektu proxy
Nikos M.
1
Dlaczego nie użyć po prostu typu danych mapy? W DB może być reprezentowane jako {id} + {id, klucz, wartość}, jeśli nie pytasz o wydajność.
Shadows In Rain

Odpowiedzi:

4

Gratulacje! Właśnie okrążyłeś globus systemu / języka programowania, przybywając na drugą stronę świata, skąd odszedłeś. Właśnie wylądowałeś na granicy dynamicznego obiektu opartego na języku / prototypie!

Wiele języków dynamicznych (np. JavaScript, PHP, Python) umożliwia rozszerzenie lub zmianę właściwości obiektu w czasie wykonywania.

Skrajną formą tego jest język oparty na prototypach, taki jak Self lub JavaScript. Nie mają zajęć, ściśle mówiąc. Możesz robić rzeczy, które wyglądają jak oparte na klasach, obiektowe programowanie z dziedziczeniem, ale reguły są znacznie bardziej rozluźnione w porównaniu do ostrzej zdefiniowanych języków opartych na klasach, takich jak Java i C #.

Langauges, takie jak PHP i Python, żyją pośrodku. Mają regularne, idiomatyczne systemy oparte na klasach. Ale atrybuty obiektów mogą być dodawane, zmieniane lub usuwane w czasie wykonywania - aczkolwiek z pewnymi ograniczeniami (takimi jak „oprócz typów wbudowanych”), których nie można znaleźć w JavaScript.

Dużym kompromisem dla tego dynamizmu jest wydajność. Zapomnij, jak mocno lub słabo wpisany jest język lub jak dobrze można go skompilować do kodu maszynowego. Obiekty dynamiczne muszą być reprezentowane jako elastyczne mapy / słowniki, a nie proste struktury. Zwiększa to dostęp do każdego obiektu. Niektóre programy dokładają wszelkich starań, aby zmniejszyć ten narzut (np. Z przypisywaniem kwantowego kwarga i klasami opartymi na slotach w Pythonie), ale dodatkowy narzut jest zwykle równy kursowi i cenie wstępu.

Wracając do swojego projektu, przeszczepiasz zdolność posiadania dynamicznych właściwości na podzbiorze swoich klas. A Productmoże mieć zmienne atrybuty; przypuszczalnie tak Invoiceczy Ordernie i nie mógł. To nie jest zła droga. Daje ci elastyczność, aby mieć zmienne tam, gdzie jest to potrzebne, pozostając w ścisłym, zdyscyplinowanym języku i systemie pisma. Z drugiej strony jesteś odpowiedzialny za zarządzanie tymi elastycznymi właściwościami i prawdopodobnie będziesz musiał to zrobić za pomocą mechanizmów, które wyglądają nieco inaczej niż bardziej natywne atrybuty. p.prop('tensile_strength')zamiast p.tensile_strengthna przykład i p.set_prop('tensile_strength', 104.4)raczej niżp.tensile_strength = 104.4. Ale pracowałem z wieloma programami w Pascal, Ada, C, Java, a nawet w językach dynamicznych, i korzystałem z nich, a także korzystałem z takiego dostępu do getter-setter dla niestandardowych typów atrybutów; podejście jest wyraźnie wykonalne.

Nawiasem mówiąc, napięcie między typami statycznymi a bardzo zróżnicowanym światem jest niezwykle powszechne. Analogiczny problem często pojawia się podczas projektowania schematu bazy danych, szczególnie w relacyjnych i przed relacyjnych magazynach danych. Czasami rozwiązuje się to poprzez tworzenie „superrzędów”, które zawierają wystarczającą elastyczność, aby pomieścić lub zdefiniować połączenie wszystkich wymyślonych odmian, a następnie upchnąć dane, które pojawią się w tych polach. WordPress wp_postsstół , na przykład, ma pola, na przykład comment_count, ping_status, post_parenti post_date_gmtże są tylko interesujące w pewnych okolicznościach, i że w praktyce często wygaszony. Innym podejściem jest bardzo oszczędny, znormalizowany stół wp_optionspodobny do twojegoPropertyklasa. Chociaż wymaga to bardziej precyzyjnego zarządzania, elementy w nim rzadko są puste. Zorientowane obiektowo i dokumentacyjne bazy danych (np. MongoDB) często łatwiej radzą sobie ze zmianą opcji, ponieważ mogą tworzyć i ustawiać atrybuty praktycznie do woli.

Jonathan Eunice
źródło
0

Podoba mi się pytanie, moje dwa centy:

Twoje dwa podejścia są radykalnie różne:

  • Pierwszy z nich to OO i silnie napisane - ale nie do rozszerzenia
  • Drugi jest słabo wpisany (ciąg znaków obejmuje wszystko)

W C ++ wielu użyłoby std :: map of boost :: variant, aby osiągnąć połączenie obu.

Disgression: Należy pamiętać, że niektóre języki, takie jak C #, umożliwiają dynamiczne tworzenie typów. Co może być dobrym rozwiązaniem ogólnego problemu dynamicznego dodawania członków. Jednak „modyfikowanie / dodawanie” typów po kompilacji powoduje uszkodzenie samego systemu typów i sprawia, że ​​„zmodyfikowane” typy są prawie bezużyteczne (np. Jak uzyskasz dostęp do takich dodanych właściwości, skoro nawet nie wiesz, że istnieją? Jedyny rozsądny sposób bądź systematyczną refleksją nad każdym obiektem ... kończąc na czystym dynamicznym języku _ możesz odwołać się do słowa kluczowego „dynamicznego” .NET)

quantdev
źródło
Tworzenie typów w środowisku uruchomieniowym jest dla mnie interesujące, ale zbyt egzotyczne (programuję w Javie). Takie rozwiązanie nie zadziałałoby, gdybym chciał przechowywać obiekt w bazie danych, w którą zawsze wierzę, że jest mocno wpisany. Proponowane przeze mnie źle napisane rozwiązanie można łatwo przechowywać w bazie danych.
Filip
0

Tworzenie typu w środowisku wykonawczym wydaje się znacznie bardziej skomplikowane niż tworzenie warstwy abstrakcji. Bardzo często tworzy się abstrakcję w celu oddzielenia systemów.

Pokażę przykład z mojej praktyki. Giełda w Moskwie ma rdzeń handlowy o nazwie Plaza2 z API handlowca. Handlowcy piszą swoje programy do pracy z danymi finansowymi. Problem polega na tym, że dane te są bardzo ogromne, złożone i bardzo narażone na zmiany. Może ulec zmianie po wprowadzeniu nowego produktu finansowego lub zmianie ról rozliczeniowych. Charakter przyszłych zmian nie można przewidzieć. Dosłownie może się zmieniać każdego dnia, a słabi programiści powinni edytować kod i wydawać nową wersję, a źli inwestorzy powinni zmieniać swoje systemy.

Oczywistą decyzją jest ukrycie całego finansowego piekła za abstrakcją. Wykorzystali dobrze znaną abstrakcję tabel SQL. Największa część ich rdzenia może współpracować z dowolnym prawidłowym schematem, tak samo jak oprogramowanie tradera może dynamicznie analizować schemat i sprawdzać, czy jest on zgodny z ich systemem.

Wracając do twojego przykładu, normalne jest tworzenie abstrakcyjnego języka przed jakąś logiką. Może to być „własność”, „tablica”, „wiadomość”, a nawet ludzki język, ale zwróć uwagę na wady tego podejścia:

  • Więcej kodu do analizowania i sprawdzania poprawności (i więcej czasu poza kursem). Wszystko, co kompilator zrobił dla Ciebie ze statycznym pisaniem, musisz zrobić w czasie wykonywania.
  • Więcej dokumentacji komunikatów, tabel lub innych prymitywów. Cała złożoność przechodzi od kodu do jakiegoś schematu lub standardu. Oto przykład wyżej wspomnianego finansowego schematu piekielnego: http://ftp.moex.com/pub/FORTS/Plaza2/p2gate_en.pdf (kilkadziesiąt stron tabel)
astef
źródło
0

Czy to podejście jest znanym wzorcem projektowym?

W XML i HTML byłyby to atrybuty węzła / elementu. Słyszałem też, że nazywają się właściwościami rozszerzonymi, parami nazwa / wartość i parametrami.

Czy rozwiązałbyś problem inaczej?

Tak rozwiązałbym problem, tak.

Wiem, że istnieją języki, w których mogę dodać pole w czasie wykonywania, ale co z bazą danych?

W pewnym sensie baza danych byłaby jak Java. W pesudo-sql:

TABLE products
(
    product_name VARCHAR(50),
    product_id INTEGER AUTOINCREMENT
)

TABLE attributes
(
    product_id INTEGER,
    name VARCHAR(50),
    value VARCHAR(2000)
)

Odpowiadałoby to Javie

class Product {
   protected String productName;
   protected Map<String, String> properties;
}

Zauważ, że nie ma potrzeby posiadania klasy Właściwość, ponieważ Mapa przechowuje nazwę jako klucz.

Wolisz dodawać / zmieniać kolumny lub używać czegoś, jak pokazano powyżej?

Próbowałem dodać / zmienić kolumnę i to był koszmar. Można to zrobić, ale rzeczy wciąż się nie synchronizują, a ja nigdy nie działałem dobrze. Struktura tabeli, którą opisałem powyżej, odniosła większy sukces. Jeśli trzeba zrobić wyszukiwań i porządku przez tych, rozważyć użycie atrybutów tabeli dla każdego typu danych ( date_attributes, currency_attributesetc.) lub dodając niektóre właściwości jak starych dobrych kolumn w tabeli bazy danych produktów. Raporty są często znacznie łatwiejsze do pisania w kolumnach bazy danych niż w podtabelach.

Guy Schalnat
źródło