Estendere il modello del peso mosca: un ossimoro?

3

Sto sviluppando un parser BNF in PHP, per il mio gusto di BNF. Per mantenere l'albero di analisi risultante, costituito da oggetti, il più leggero possibile, ho deciso di utilizzare il modello di peso mosca per i letterali (terminali):

class Literal
{
  private static $cache = array();

  private $string = '';

  // private constructor for flyweight pattern
  private function __construct( $string ) {
    $this->setString( $string );
  }

  // flyweight pattern
  public static function fromString( $string ) {
    if( !isset( self::$cache[ $string ] ) ) {
      self::$cache[ $string ] = new Literal( $string );
    }

    return self::$cache[ $string ];
  }

  public function match( SourceInterface $source ) {
    /* algorithm to match source against $this->string */
  }

  /* ... */
}

I letterali sono indicati come stringhe tra virgolette (singole o doppie), ad esempio "A" o 'A' .

Il mio aroma di BNF, tuttavia, supporterà anche rappresentazioni codificate di letterali a singolo carattere, denotate da un segno di percentuale e un simbolo di demarcazione decimale o esadecimale seguito da un valore, ad esempio: %d48 o %x30 .

Per implementarlo, preferirei estendere la classe Literal , ereditando alcune delle funzionalità di Literal , come il suo metodo match , perché in definitiva EncodedLiteral rappresenta la stringa Literal , che la sorgente di input verrà confrontata.

Quindi, quello con cui giocavo era qualcosa del genere, ma poiché il costruttore di Literal doveva essere mantenuto private per il modello di peso mosca, mi sono confuso con cosa fare all'interno del EncodedLiteral costruttore.

class EncodedLiteral
  extends Literal
{
  private static $cache = array();

  private $encodedString;

  // private constructor for its own flyweight pattern
  private function __construct( $encodedString ) {

    // this won't work, since parent::__construct() is private
    // and it would bypass the flyweight pattern if it was protected
    parent::__construct( $this->decodeEncodedString( $encodedString ) );

    // ... and this doesn't make sense, since it would become composition
    // losing the inherited functionality
    $this->literal = Literal::fromString( $this->decodeEncodedString( $encodedString ) );
  }

  // flyweight pattern
  public static function fromString( $encodedString ) {
    if( !isset( self::$cache[ $encodedString ] ) ) {
      self::$cache[ $encodedString ] = new EncodedLiteral( $encodedString );
    }

    return self::$cache[ $encodedString ];
  }

  private function decodeEncodedString( $encodedString ) {
    /* implementation */
  }

  /* ... */
}

Quindi, come puoi vedere, sono un po 'bloccato qui.

C'è un modo per risolvere questo, in modo elegante, senza perdere i vantaggi del peso piuma? È questo un caso di scegliere la composizione sull'eredità, forse? Dovrei andare con il modello decoratore? Ho il terrore di dover proxy per i metodi che altrimenti erediterei con estendere Literal , però.

    
posta Decent Dabbler 02.03.2016 - 10:36
fonte

2 risposte

2

Poiché si desidera condividere il metodo match() , prenderei tutti i comportamenti relativi a flyweight / cache dai Literals in una classe Factory. Questo è un comportamento statico quindi non può essere stato ereditato comunque.

Qualcosa in questo senso (scusami il mio pessimo PHP)

public class Literal {

  protected $string = '';

  protected function __construct( $string ) {
    $this->setString( $string );
  }

  protected function getString() {
    return $this->string;
  }

  protected function setString($value) {
    $this->string = $value;
  }

  public function match( SourceInterface $source ) {
    // ...
  }
}

public class EncodedLiteral extends Literal {

  protected function setString($value) {
    parent::setString($this->decodeEncodedString($value)) ;
  }

  private function decodeEncodedString( $encodedString ) {
    // ...
  }
}

public class LiteralFactory {
  private static $cache = array();

  public static function fromString($string) {
    // use private cache here and return a Literal
  }
}

public class EncodedLiteralFactory {
  private static $cache = array();

  public static function fromString($string) {
    // use private cache here and return an EncodedLiteral
  }
}
    
risposta data 03.03.2016 - 09:24
fonte
0

Ogni volta che estendi una lezione di cemento, è un odore di codice. Di solito vuoi che tutte le classi concrete siano lasciate nella gerarchia.

Sposta il codice comune in una classe base astratta, quindi i tuoi due tipi di codifica letterale (quotata o numerica) sono classi di base uguali, specializzando solo le loro rappresentazioni testuali.

Potresti anche voler spostare la cache nella sua classe in quanto ti ritroverai a ripetere il pattern per tutti i tipi di letterale per i quali la tua grammatica si rivolge.

In alternativa, modifica il tokeniser BNF e passa l'intero letterale alla cache anziché solo la parte tra virgolette e può determinare dal primo carattere " , ' o % come decodificarlo , quindi non hai bisogno di due classi.

    
risposta data 02.03.2016 - 16:55
fonte

Leggi altre domande sui tag