Kowariancja typu parametru w specjalizacjach
Jakie są strategie pokonywania niezmienności typów parametrów dla specjalizacji w języku (PHP) bez wsparcia dla leków generycznych?
Uwaga: Chciałbym powiedzieć, że moje rozumienie teorii typów / bezpieczeństwa / wariancji / itd. Było bardziej kompletne; Nie jestem CS major.
SytuacjaMasz klasę abstrakcyjną,Consumer
, które chciałbyś rozszerzyć.Consumer
deklaruje metodę abstrakcyjnąconsume(Argument $argument)
który wymaga definicji. Nie powinno być problemu.
Twój wyspecjalizowanyConsumer
, nazywaSpecializedConsumer
nie ma logicznej współpracykażdy typArgument
. Zamiast tego powinien zaakceptowaćSpecializedArgument
(i ich podklasy). Nasz podpis metody zmienia się naconsume(SpecializedArgument $argument)
.
abstract class Argument { }
class SpecializedArgument extends Argument { }
abstract class Consumer {
abstract public function consume(Argument $argument);
}
class SpecializedConsumer extends Consumer {
public function consume(SpecializedArgument $argument) {
// i dun goofed.
}
}
Łamiemy sięZasada substytucji Liskowai powodujące problemy z bezpieczeństwem typów. Rufa.
PytanieOk, więc to nie zadziała. Jednak biorąc pod uwagę tę sytuację, jakie istnieją wzorce lub strategie, aby pokonać problem bezpieczeństwa typu i naruszenieLSP, ale nadal utrzymuj relację typuSpecializedConsumer
doConsumer
?
Przypuszczam, że jest całkowicie do przyjęcia, że odpowiedź może zostać poddana destylacji do „yun dofeded, z powrotem do deski kreślarskiej„
Rozważania, szczegóły i errataW porządku, natychmiastowe rozwiązanie wygląda jak „nie definiujconsume()
metoda wConsumer
„. Ok, to ma sens, ponieważ deklaracja metody jest tak dobra, jak podpis. Semantycznie, chociaż nie maconsume()
, nawet z nieznaną listą parametrów, trochę boli mój mózg. Być może jest lepszy sposób.
Z tego, co czytam, niewiele języków obsługuje kowariancję typu parametru; PHP jest jednym z nich i jest tutaj językiem implementacyjnym. Dalsze komplikacje, widziałem kreatywne ”rozwiązania„angażującygeneryczne; inna funkcja nie jest obsługiwana w PHP.
Z WikiWariancja (informatyka) - Potrzeba kowariancji typów argumentów?:
Stwarza to problemy w niektórych sytuacjach, gdzie typy argumentów powinny być kowariantne w celu modelowania wymagań rzeczywistych. Załóżmy, że masz klasę reprezentującą osobę. Osoba może zobaczyć się z lekarzem, więc ta klasa może mieć wirtualną próżnięPerson::see(Doctor d)
. Załóżmy teraz, że chcesz utworzyć podklasęPerson
klasa,Child
. To jestChild
jest osobą. Można wtedy chcieć stworzyć podklasęDoctor
, Pediatrician
. Jeśli dzieci odwiedzają tylko pediatrów, chcielibyśmy to wprowadzić w systemie typu. Jednak naiwna implementacja nie powiedzie się: ponieważ aChild
jestPerson
, Child::see(d)
musi wziąć każdyDoctor
, nie tylkoPediatrician
.
Artykuł mówi dalej:
W tym przypadkuwzór gościa może być wykorzystany do egzekwowania tego związku. Innym sposobem rozwiązania problemów w C ++ jest użycieprogramowanie ogólne.
Jeszcze raz,generyczne może być twórczo wykorzystany do rozwiązania problemu. Badamwzór gościa, ponieważ zresztą i tak mam częściowo wdrożoną implementację, jednak większość implementacji opisanych w artykułach wykorzystuje przeciążanie metody, kolejną nieobsługiwaną funkcję w PHP.
<too-much-information>
Ze względu na niedawną dyskusję omówię szczegółowe szczegóły implementacji, których nie uwzględniłem (jak w, prawdopodobnie zawrę zbyt wiele).
Dla zwięzłości wykluczyłem ciała metod dla tych, które są (powinno być) całkowicie jasne w ich celu. Jawypróbowany aby zachować to zwięźle, ale mam tendencję do bycia słownym. Nie chciałem zrzucać ściany kodu, więc wyjaśnienia następują / poprzedzają bloki kodu. Jeśli masz uprawnienia do edycji i chcesz to wyczyścić, zrób to. Ponadto bloki kodu nie są kopią makaronu z projektu. Jeśli coś nie ma sensu, może nie; krzyczeć na mnie o wyjaśnienie.
W odniesieniu do pierwotnego pytania, poniżejRule
klasa jestConsumer
iAdapter
klasa jestArgument
.
Klasy związane z drzewem są następujące:
abstract class Rule {
abstract public function evaluate(Adapter $adapter);
abstract public function getAdapter(Wrapper $wrapper);
}
abstract class Node {
protected $rules = [];
protected $command;
public function __construct(array $rules, $command) {
$this->addEachRule($rules);
}
public function addRule(Rule $rule) { }
public function addEachRule(array $rules) { }
public function setCommand(Command $command) { }
public function evaluateEachRule(Wrapper $wrapper) {
// see below
}
abstract public function evaluate(Wrapper $wrapper);
}
class InnerNode extends Node {
protected $nodes = [];
public function __construct(array $rules, $command, array $nodes) {
parent::__construct($rules, $command);
$this->addEachNode($nodes);
}
public function addNode(Node $node) { }
public function addEachNode(array $nodes) { }
public function evaluateEachNode(Wrapper $wrapper) {
// see below
}
public function evaluate(Wrapper $wrapper) {
// see below
}
}
class OuterNode extends Node {
public function evaluate(Wrapper $wrapper) {
// see below
}
}
Więc każdyInnerNode
zawieraRule
iNode
obiekty i każdyOuterNode
tylkoRule
przedmioty.Node::evaluate()
ocenia każdyRule
(Node::evaluateEachRule()
) do wartości logicznejtrue
. Jeśli każdyRule
przechodzi,Node
minął i jestCommand
jest dodawany doWrapper
i zstąpi do dzieci do oceny (OuterNode::evaluateEachNode()
) lub po prostu wróćtrue
, dlaInnerNode
iOuterNode
obiekty odpowiednio.
Co się tyczyWrapper
;Wrapper
obiekty proxyRequest
obiekt i ma kolekcjęAdapter
przedmioty. TheRequest
obiekt jest reprezentacją żądania HTTP. TheAdapter
obiekt to specjalistyczny interfejs (i utrzymuje określony stan) do konkretnego zastosowania ze specyficznymRule
przedmioty. (tu pojawiają się problemy z LSP)
TheCommand
obiekt to akcja (porządnie zapakowany callback, naprawdę), który jest dodawany doWrapper
obiekt, gdy wszystko zostanie powiedziane i zrobione, tablicaCommand
obiekty zostaną wystrzelone w sekwencji, mijającRequest
(między innymi) w.
class Request {
// all teh codez for HTTP stuffs
}
class Wrapper {
protected $request;
protected $commands = [];
protected $adapters = [];
public function __construct(Request $request) {
$this->request = $request;
}
public function addCommand(Command $command) { }
public function getEachCommand() { }
public function adapt(Rule $rule) {
$type = get_class($rule);
return isset($this->adapters[$type])
? $this->adapters[$type]
: $this->adapters[$type] = $rule->getAdapter($this);
}
public function commit(){
foreach($this->adapters as $adapter) {
$adapter->commit($this->request);
}
}
}
abstract class Adapter {
protected $wrapper;
public function __construct(Wrapper $wrapper) {
$this->wrapper = $wrapper;
}
abstract public function commit(Request $request);
}
Więc dany użytkownik-ziemiaRule
akceptuje oczekiwaną ziemię użytkownikaAdapter
. JeśliAdapter
potrzebuje informacji o żądaniu, jest przekierowywaneWrapper
, w celu zachowania integralności oryginałuRequest
.
JakoWrapper
agregatyAdapter
obiekty, przekaże istniejące instancje do kolejnychRule
obiekty, tak aby stanAdapter
jest zachowany od jednegoRule
do następnego. Pewnego razucały drzewo minęło,Wrapper::commit()
jest nazywany, a każdy z zagregowanychAdapter
obiekty zastosują swój stan w zależności od oryginałuRequest
.
Pozostaje nam cały szeregCommand
obiekty i zmodyfikowaneRequest
.
O co chodzi, do diabła?
Cóż, nie chciałem odtworzyć prototypowej „tablicy routingu”, która jest powszechna w wielu frameworkach / aplikacjach PHP, więc zamiast tego poszedłem z „drzewkiem routingu”. Pozwalając na dowolne reguły, możesz szybko tworzyć i dołączaćAuthRule
(na przykład) do aNode
i już nie jest to, że cała gałąź jest dostępna bez przejściaAuthRule
. W teorii (w mojej głowie) jest jak magiczny jednorożec, zapobiegający powielaniu kodu i wymuszający organizację strefy / modułu. W praktyce jestem zdezorientowany i przestraszony.
Dlaczego zostawiłem tę ścianę nonsensów?
Cóż, to jest implementacja, dla której muszę rozwiązać problem LSP. KażdyRule
odpowiadaAdapter
i to nie jest dobre. Chcę zachować związek między każdymRule
, aby zapewnić bezpieczeństwo typu podczas konstruowania drzewa itp., jednak nie mogę zadeklarować metody klucza (evaluate()
) w skrócieRule
, jako zmiany podpisu dla podtypów.
Inna uwaga: pracuję nad uporządkowaniemAdapter
schemat tworzenia / zarządzania; czy jest to odpowiedzialnośćRule
stworzyć to itd.
</too-much-information>