Newsgroup: it.comp.www.php
Titolo: trait per decoratore standard

<?php
trait StandardDecoratorTrait {
	function __construct(parent $wrapped) {
		$this->_wrapped = $wrapped;
	}
}
 
abstract class Component {
	//abstract function...
}
 
class GenericComponent extends Component {
	//function...
}
 
abstract class AdvancedComponent extends Component {
	use StandardDecoratorTrait;
}
 
class SpecificAdvancedComponent extends AdvancedComponent {
	//function...
}
 
Adesso vediamo se funziona:
 
new SpecificAdvancedComponent(
	new GenericComponent()
);
 
Ok, tutto funziona!!!
 
Adesso però, invece della classe Component astratta, vogliamo usare 
un'interfaccia. Quindi apportiamo queste modifiche:
 
interface Component {
	//function...
}
 
class GenericComponent implements Component {
	//function...
}
 
abstract class AdvancedComponent implements Component {
	use StandardDecoratorTrait;
}
 
Vediamo se funziona...
 
Fatal error: Cannot access parent:: when current class scope has no 
parent in test.php on line 3
 
In pratica *parent* deve essere per forza una classe...
Se è un'interfaccia, il trait non la accetta...
Soluzione?

 Soluzione?
 
Non cercare di implementare decoratori coi trait.
 
Bye.
 


lo si fa per evitare rindondanze e scrivere sempre le stesse istruzioni 
di base
 
trait WrapperBasicStructure {
	private $_wrapped;
 
	protected function _set($wrapped) {
		//  if (!isValid($wrapped)) throw new ...
		$this->_wrapped = $wrapped;
	}
 
	function get() {
		return $this->_wrapped;
	}
}
 
stavolta non ho messo il __construct per il problema di cui sopra...

      function get() {
 
Un decoratore non deve avere setter e getter per l'oggetto wrappato.
Sia perchè l'interface dell'oggetto wrappato potrebbe volerli usare per 
i suoi motivi, sia perchè deve essere totalmente trasparente nell'uso, e 
permettere a chiunque di cambiare l'oggetto wrappato o di andarselo a 
prendere, è sbagliato.
 
E se poi hai un oggetto decorato 4 volte, per risalire all'oggetto 
originale fai ->get()->get()->get()->get() ?
 
Poi ognuno sceglie lo strumento più doloroso per farsi male, quindi puoi 
continuare quello che stai facendo, solo se lo rendi pubblico magari non 
chiamarlo decoratore o wrapper. :)
 
Bye.
 

 
 
A me non sembra un decorator nel senso classico... di solito si fa come
sotto:
 
Come base hai un' interfaccia:
interface Component { public function operation(); };
 
Senza alcun decorator hai:
class ConcreteComponent implements Component {
    public function operation() { /* ... */ }
}
 
Se ti serve di usare decorators per quella *interfaccia*:
abstract class ComponentDecorator implements Component {
    public $components; /* array di Components */
}
class ConcreteDecorator1 extends ComponentDecorator {
    public function operation() { /* ... */ }
}
class ConcreteDecorator2 extends ComponentDecorator {
    public function operation() { /* ... */ }
}
 
In questo modo puoi usare indifferentemente ConcreteComponent,
ConcreteDecorator1 o ConcreteDecorator2 ovunque serva un Component. Quello
che tu chiami "le stesse istruzioni di base" vanno nella classa astratta
ComponentDecorator.
 
Ciao!


 
Quindi come si dovrebbe fare?
 

 ComponentDecorator.
 
E se le stesse istruzioni (o solo una parte) vanno bene anche per un 
altro decorator (AppDecorator) perchè non metterle in un trait?
 
trait MioTrait {// parte di istruzioni comuni}
 
abstract class ComponentDecorator extends Component {
     use MioTrait;
     // altra roba...
}
 
abstract class AppDecorator extends App{
     use MioTrait;
     // altra roba...
}
 
Dov'è il problema?


Ah beh, questo però non c'entra nulla con i decorators, è una domanda che vale
per qualsiasi tipo di classe :), o meglio ogni volta che vorresti avere
ereditarietà multipla.
 
Certamente lo puoi fare, perché no.
 
Comunque se ti serve per getter/setter ha ragione Alessandro, non lo fare.
Tutta 'sta pantomima serve perché vuoi scrivere funzioni tipo:
function doStuff(Component $c) {
    /*...*/
    $c->operation();
    /*...*/
}
fregandotene di cosa sia effettivamente quella $c.
 
Se sei in una situazione di dover sapere cosa sia o cosa ci sia dentro stai
sbagliando qualcosa.
 
Ciao!

 Quindi come si dovrebbe fare?
 
Non devi mai accedere all'oggetto wrappato da niente che non sia il 
decoratore stesso:
 
interface LoggerInterface {
  public function set($log);
  public funciton get($log);
}
 
class Log implements LoggerInterface
{
   protected $logs = [];
   public function set($log) { $this->logs[] = $log; }
   public function get() { return $this->logs; }
}
 
 
class HtmlLog implements LoggerInterface
{
   private $wrapped;
   public function __construct(LoggerInterface $log) {
     $this->wrapped = $log;
   }
 
   public function get() {
     return '<ul><li>'.
       implode('<\li><li>', $this->wrapped->get()).
       '<\li><\ul>';
   }
   public function set($log) { $this->wrapper->set($log); }
}
 
$htmlLogger = new HtmlLog(new Log());
 
var_dump($htmlLogger instanceof LoggerInterface); // -> true
 
 
Nota che il costruttore forza il parametro ad essere la stessa 
interfaccia che lui stesso implementa.
 
Questo coi trait non lo puoi fare.
A meno di non impazzire con le Reflection, ma ha senso, per risparmiare 
3 righe di codice?
 
Nota che $wrapped è privato, perchè solo HtmlLog deve poterci accedere. 
Nessun'altro sa che è un decoratore, altrimenti perdi il senso dei 
decoratori (devono essere invisibili).
 
Cosa ti rimane da mettere in un trait? Niente.
 
Bye.
 

 
 
Vero in generale, però dipende sempre dalla struttura che sta cercando di
tirare su..
 
Nell'esempio che ha messo in risposta a me ci sono due gerarchie separate di
oggetti; visto che in PHP l'ereditarietà è singola e nei concrete decorator
serve di ereditare da due diverse classi base, a meno di non ristrutturare
tutto il codice facendo derivare due classi che concettualmente non c'appizzano
nulla da una stessa classe fatta ad-hoc, una trait potrebbe far comodo.
 
Su uno dei commenti della documentazione ufficiale c'è penso la frase più 
sensata riguardante le trait che abbia mai letto: sono "language assisted copy
and paste" :) Usarle per strutturare gli oggetti è impossibile, ma se proprio
uno deve copia-incollare, a 'sto punto, meglio una trait :)
 
Ciao!
 
 
P.S.
Per esempio:
 
interface BaseComponent { };
interface Component1 extends BaseComponent { public function operation1(); }; 
interface Component2 extends BaseComponent { public function operation2(); }; 
 
trait Decorator1and2Helper {
    protected $components; /* array of BaseComponents */
    protected function addComponent(BaseComponents $c) {
        $this->components[] = $c;
    }
    protected function removeComponent(BaseComponents $c) { /* .. */ }
    protected function serializeAll() { /* .. */ }
    /* .. */
}
 
abstract class ComponentDecorator1 implements Component1 { 
    use DecoratorHelper;
}
abstract class ComponentDecorator2 implements Component2 { 
    use DecoratorHelper;
}
 
class ConcreteDecorator1a extends ComponentDecorator1 { 
    public function operation1() { foreach($this->components as $c) /*...*/ } 
}
class ConcreteDecorator1b extends ComponentDecorator1 { 
    public function operation1() { foreach($this->components as $c) /*...*/ } 
}
class ConcreteDecorator2 extends ComponentDecorator2 { 
    public function operation2() { foreach($this->components as $c) /*...*/ } 
}

 Per esempio:
 
Faccio il pignainculo :) ma io questo lo considererei più un proxy verso 
componenti multipli, o un multiplexer, più che un decoratore, ma capisco 
cosa cerchi di fare e l'idea è interessante.
 
Ma stai implementando tramite trait le operazioni degli oggetti base, non 
la logica di decorator, che è la cosa giusta. :)
 
Bye.


 
Certamente
 

 uno deve copia-incollare, a 'sto punto, meglio una trait:)
 
Condivido
 

 }
 
Il problema è che così non funziona. :)
 
HtmlLog, ereditando da LoggerInterface sia lo storage che il setter, salva 
i log nel suo spazio (la sua $this->logs) e non in quello dell'oggetto 
wrappato ($this->wrapped->logs), ma la sua get va a prenderli nell'oggetto 
decorato, quindi saranno sempre vuoti.
 
Per questo non conviene che un decorator extend-a una classe abstract, ma 
è meglio che implement-i un'interface. In questo modo non hai zavorra con 
rischi, come in questo caso, di dimenticarti di wrappare una funzione e 
spaccare tutto.
 
Bye.

 
 
ehehe, meno male! Come ti ho detto non le uso mai, magari avevo pure scritto
'na ca**ata! ;)
 
Ciao!
 


 
Vero. Ma a questo punto userei la cara vecchia ereditarietà
 
interface LoggerInterface
{
  function get($key);
}
 
class Log implements LoggerInterface
{
  private $logs = [];
 
  // todo: questa funzione sarebbe meglio chiamarla add
  final function set($log)
  {
    $this->logs[] = $log; // aggiunge un logger
  }
 
  function get($key) { return $this->logs[$key]; }
}
 
class HtmlLog extends Log
{
   function get($key) {
     return '<ul><li>'.
       implode('<\li><li>', parent::get($key)).
       '<\li><\ul>';
   }
}
 
 
 
Lo dico perchè implementare roba inutile come questa
 
function set($log)// a cosa serve questa funzione? ad appesantire...
{
  $this->wrapper->set($log);
}
 
non me la sento!


 
Ancora meglio
 
class Logs
{
	private $logs = [];
 
	final function add($log)
	{
		$this->logs[] = $log;
	}
 
	final function get($key) {
		return $this->logs[$key];
	}
}
 
class HtmlLog
{
	private $wrapped;
 
	function __construct($wrapped)
	{
		$this->wrapped=
				'<ul><li>'.
				$wrapped.
				'<\li><\ul>'
		;
	}
 
	function __toString()
	{
		return $this->wrapped;
	}
}
 
$logs = new Logs();
$log->add(new HtmlLog(123));

 $log->add(new HtmlLog(123));
 
E si potrebbe buttare (se non serve) anche la classe Logs.
 
$logs[] = new HtmlLog(123);

 Vero. Ma a questo punto userei la cara vecchia ereditarietà
 
Il che dimostra che non hai ancora capito bene a cosa servono i 
decoratori. :)
 
Niente di male, eh. Ci ho messo anche io un po', e finché non ho trovato 
un caso pratico in cui servissero non capivo nemmeno perché la gente li 
trovasse utili.
 
Con un singolo decoratore il discorso ha poco senso. Il senso arriva 
quando hai più decoratori che puoi wrappare in ordine diverso.
Quello che fai con una classe base e 3 decoratori può richiedere anche 
9-10 classi se usi l'ereditarietà.
 
Continuando l'esempio stupido del Log, pensa ad avere un decoratore che li 
stampa in HTML, uno che li salva in un DB, uno che li manda a Kibana, uno 
che li multiplexa.
 
$logger = new Log();
 
$htmlDb = new DbLog(new HtmlLog($logger));
 
$kibanaAndDb = new Multiplexer(new KibanaLog($logger), new DbLog($logger));
 
È un esempio stupido, perché alla fine del Log originale non rimane quasi 
niente, ma prova a fare il conto di quante combinazioni puoi creare coi 
decoratori e quante classi ti servono per farlo con l'ereditarietà.
 
Bye.

 $log->add(new HtmlLog(123));
 
Qui non stai decorando. Stai scrivendo dati "sporchi" dentro $log.
Per sporchi intendo non omogenei, perché puoi infilarci HTML, testo, 
immagini, ecc.
 
Magari è quello che vuoi, eh. Non discuto, ma converrai con me che non è 
né trasparente (chi scrive il log deve sapere cosa sta scrivendo) né pulito 
(come distingui i dati in estrazione?).
 
Questo può avvicinarsi a uno strategy pattern se nella variabile 
$log->logs salvi l'oggetto HtmlLog, invece di una stringa generica. In 
questo modo in fase di estrazione sai cos'è (un oggetto HtmlLog) e puoi 
decidere una strategia per visualizzarlo.
 
Bye.

 il design fa quel che deve  :)
 
Poco ma sicuro.
 
A partire dal fatto che compile-time e run-time sono quasi la stessa cosa, 
quindi la definizione "pattern per avere a run-time comportamenti che 
altrimenti sarebbero possibili solo a compile-time" non ha molto senso.
 
Bye.


 
Non fraintendermi.
E' vero, alla fine ho sforato nell'editarità, senza tener conto di ciò 
che hai detto; hai ragione!!!
Però implementare funzioni inutili "{return $this->wrapped->get();}" 
(che wrappano la funzione di base, senza aggiungere altre funzionalità) 
lo eviterei.


 
$textLogs = new Logs;
$htmlLogs = new Logs;
 

...
 
se poi vuoi un sistema di controllo maggiore (più intrinseco), le tue 
osservazioni sono giuste, e ci mancherebbe :P
 

 lo eviterei.
 
Quella è l'essenza stessa dei decoratori: fare override solo di alcune 
funzioni facendo transitare in modo trasparente tutte le altre.
 
Se non lo vuoi fare, non stai facendo un decoratore. :)
 
Bye.

 andar bene.
 
Se hai il pieno controllo di quello che stai facendo, nella maggior parte 
dei casi tutti i pattern astratti come questo non hanno senso.
 
Molti di questi pattern sono pensati per fare framework o librerie 
riutilizzabili, per condividere codice con altri sviluppatori, per riusare 
codice scritto da altri, ecc.
 
Se ti scrivi tutto tu da zero, non ha senso usarli.
 
Bye.

 Se ti scrivi tutto tu da zero, non ha senso usarli.
 
In effetti, poi dipende...
Vabè alla fine tutto è relativo :)

 Bye.
 
Ciao!

 
 
Nah.
Quando scrivi codice, pensa sempre come se lo leggesse uno che non ha la
minima idea di quello che stai facendo. Se tutto va bene, quella persona
sarai te, tra un anno. E fidati no, non ti ricorderai *nulla* di quello che
hai in testa ora.
:)
 
Ciao!

 Uno studia tutto, poi usa quello che serve! :)
 
+1  :D
 
Bye.
 

 Se non lo vuoi fare, non stai facendo un decoratore.:)
 
Infatti l'eredietà, usata con moderazione, non è da demonizzare :)

 Infatti l'eredietà, usata con moderazione, non è da demonizzare :)
 
Mai detto il contrario, ma il segreto è sempre "con moderazione".
 
Appena il progetto cresce oltre un certo livello, fidati che 
l'ereditarietà diventa più un peso che un vantaggio, e sempre più spesso 
mi pento di averla usata.
 
In pratica, se ti trovi a estendere qualcosa di già esteso, stai 
sbagliando qualcosa.
 
Bye.
 

 Per l'impiego di codice basta una __call()
 
in pratica come si può sfruttare (adesso non mi viene in mente)?

 in pratica come si può sfruttare (adesso non mi viene in mente)?
 
class BlaDecorator {
 public function __call($method, $args) {
  return call_user_func_array([$this->wrapped, $method], $args);
 }
}
 
 
Bye.


interface A{
	function f();
}
 
class B implements A{
	function __call($a,$b) {
		;
	}
}
 
 
Fatal error: Class B contains 1 abstract method and must therefore be 
declared abstract or implement the remaining methods (A::f)
 
Ma che interpete usi?

 Ma che interpete usi?
 
Hai ragione. Non l'avevo provato. :)
 
Allora puoi usare i trait. :P
 
 
interface A {
 public function f();
}
 
trait TA {
 public function f() {
  $this->wrapped->f();
 }
}
 
class B implements A
{
 public function f() {
  print "B\n";
 }
}
 
class C implements A {
 use TA;
 private $wrapped;
 
 public function __construct(A $wrapped)
 {
  $this->wrapped = $wrapped;
 }
 
 public function f() { print "C\n"; }
}
 
$c = new C(new B());
$c->f();
 

 }
 
Ma non si fanno queste cose.
Non ha senso usare un traid (per calpestarlo) e contemporaneamente 
implementare il metodo :-O

 implementare il metodo :-O
 
Questo era un caso particolare con una sola funzione, e hai ragione che 
non ha senso.
 
Il caso generale prevede che un'interface abbia diversi metodi, e tu 
implementi tutte le funzioni dell'interface nel trait, allo stesso modo 
(lo so, è palloso e pessimo, ma non ci sono alternative), e poi nel 
decoratore sovrascrivi solo le funzioni che hanno un comportamento diverso.
 
Per il "calpestamento", il manuale dice questo:
 
The precedence order is that members from the current class override 
Trait methods, which in turn override inherited methods.
 
Quindi è previsto che si comporti così.
 
Bye.
 

 Trait methods, which in turn override inherited methods.
 
E' certo, anche se non è una soluzione proprio ottimale; pallosa appunto.

 $kibanaAndDb = new Multiplexer(new KibanaLog($logger), new DbLog($logger));
 
una curiosità: nella programmazione (non nell'ambito della musica o 
dell'elettronica) per multiplexer cosa si intende?


Io lo intendo come qualcosa che manda il segnale/dato a più destinazioni.
 
Non so se sia la definizione giusta. :D
 
Bye.
 

 Non so se sia la definizione giusta. :D
 
Google
 


 
Io ho sempre pensato al corrispettivo del circuito multiplexer che si usa in
elettronica :)
 
Ciao!


Infatti, vorrei solo sapere cosa fa di preciso il "fantasioso" 
multiplexer di Alessandro.
Ognuno ha le sue stravaganze :)))

 Ognuno ha le sue stravaganze :)))
 
Manda gli stessi log a due destinazioni diverse. :P
 
Bye.


Allora è un moltiplicatore, o nello specifico, un multi-logger.
Comunque se fossi in te, valuterei l'utilizzo della funzione array_map().


Allora semmai sarebbe un demultiplexer :)
 
Un N-mux ha N+1 entrate (N segnali e un selettore) e un'uscita, un N-demux ha 2
entrate (un segnale e un selettore) e N uscite.
 
In una classe di log, nella funzione che prende una stringa e la manda a varie
sottoclassi, il for/switch sarebbe il demux :)
 
Altrimenti, sempre per la terminologia elettronica, tutta la classe sarebbe
uno.. sdoppiatore! :)
 
Ciao!

 Allora semmai sarebbe un demultiplexer :)
 
Buono a sapersi, grazie. :)
 
Per fortuna non li ho mai chiamati così nel mio codice. :D
 
Bye.

 se fossi in te, valuterei l'utilizzo della funzione array_map().
 
In questo caso l'invio sarebbe in "tempo reale" e non in batch, quindi non 
capisco come array_map possa tornare utile.
 
Bye.


boh qual'è il problema?
cmq non sapendo come hai ingegnerizzato il codice non mi esprimo più di 
tanto


 
Comunque non è di certo un multiplexer ( caro Alessandro :) ),
semmai il contrario.
 
 


 
Boh, non mi sembra il nome indichi qualcosa di specifico, ma per chiunque
abbia fatto un elettronica, se un pezzo di codice fa quello che un demux (o
un mux) farebbe in hardware, non è strano che lo chiami così.
 
Specialmente quando si ha a che fare con colleghi elettronici, non è
infrequente che usino nomi del campo loro quando non sanno cosa dire :) Basta
abituarsi ;)
 
Ciao!

 abituarsi;)
 
purtroppo...