Wzorce projektowe (15)

Autor: Damian Chodorek • Opublikowany: 24 lipca 2015 • Kategoria: kursy, wzorce projektowe

Interpreter.

Interpreter to wzorze klasowy. Jego celem jest interpretacja poleceń innych języków (programowania, matematycznych), dopasowywania wzorców (np. wyrażenia regularne), parsowania/walidacji reguł. Jest rzadko wykorzystywanym wzorcem.

Klasy

class Context{/*
klasa przechowująca dane, które powinny zostać poddane interpretacji
*/};

class AbstractionExpression{/*
abstrakcyjna klasa deklarująca metodę interpret()
*/};

/*
konkretne implementacje AbstractionExpression, które interpretują treść kontekstu, tzn. implementują metodę interpret()
*/
class ConcreteExpression1: public AbstractExpression{};
class ConcreteExpression2: public AbstractExpression{};
class ConcreteExpression3: public AbstractExpression{};

class Client{/*
korzysta z jakichś wyrażeń
*/};

Etapy działania

  1. Klient tworzy kontekst.
  2. Wykorzystuje dostępne wyrażenia, albo na ich podstawie buduje wyrażenia złożone.
  3. Wywołuje metodę interpret() przekazując kontekst i podejmuje odpowiednie działania.

Konsekwencje

  1. Łatwo można dodawać kolejne warunki lub tworzyć swoje.
  2. Wzorzec jest rzadko używany, gdyż jest przeznaczony do specyficznych zadań.

Przykłady

Przykład 1.

#include <iostream>
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
#include <boost/ptr_container/ptr_list.hpp>
 
 
/*
Reprezentuje (pod)wyrażenie, będące częścią ciągu cyfr liczby rzymskiej.

Każde wyrażenie różni się aktualnym kontekstem, który jest analogią "miejsca dziesiętnego" w danej liczbie rzymskiej.

Od miejsca zalezą symbole dla poszczególnych "cyfr" oraz mnożnik, czyli np. w kontekście "setek" symbol "D" reprezentuje "pięć" z mnożnikiem 100, czyli 500.

W ogólności liczba rzymska składa się z cyfr, które sumują się do wartości całej liczby, a także z cyfr, które modyfikują następujące po nich cyfry, tworząc "podwójne" cyfry. (np. "IX" jest "podwójną" cyfrą, a "V" pojedynczą).
 */
class Wyrazenie : boost::noncopyable
{
    public:
 
        /*Zwraca wartość kolejnego, znajdującego się na początku ciągu s wyrażenia, w zależności od aktualnego kontekstu. Usuwa wykorzystane w aktualnym kroku cyfry z ciągu.*/
        int interpretuj( std::string & s );
 
        /*Zwracają symbole cyfr dla konkretnego - zależnego od konkretnej klasy pochodnej - kontekstu (miejsca w liczbie). Mamy cztery rodzaje cyfr w systemie rzymskim: jeden, cztery, pięć i dziewięć. Odpowiedniki pozostałych cyfr arabskich są tworzone przez sumowanie tych cyfr. W systemie dziesiętnym znaki odpowiadające cyfrom są zawsze takie same, "1" jest taka sama dla jedności, dziesiątek, setek itd. Natomiast w systemie rzymskim "1" różni się w zależności od kontekstu: "I" dla jedności, "X" dla dziesiątek itd.*/
        virtual std::string jeden() = 0;
        virtual std::string cztery() = 0;
        virtual std::string piec() = 0;
        virtual std::string dziewiec() = 0;
 
        /*Mnożnik, przez który należy pomnożyć wynikową cyfrę w danym kontekście. Analogia do mnożenia kolejnych cyfr liczby dziesiętnej przez kolejne potęgi 10.*/
        virtual int mnoznik() = 0;
};
 
/*Oblicza wartość wszystkich cyfr w danym kontekście (miejscu w liczbie), czyli np. dla wszystkich cyfr składających się na setki i usuwa wykorzystane cyfry z ciągu.*/
int Wyrazenie::interpretuj( std::string & s )
{
    /*Sprawdza, czy ciąg wejściowy zaczyna się od podanego podciągu.*/
    using boost::algorithm::istarts_with;
 
    if ( s.length() == 0 )
        return 0;
 
    /*suma cyfr dla danego kontekstu*/
    int suma = 0;
 
    /*Mamy dwa rodzaje cyfr podwójnych - dziewięć i cztery. W jednym kontekście żadne inne cyfry nie mogą wystąpić z nimi równocześnie.*/
    if ( istarts_with( s, dziewiec() ) )
    {
        /* Usuń dwa pierwsze znaki cyfry podwójnej z ciągu*/
        s.erase( 0, 2 );
        suma += 9 * mnoznik();

        /*Po dziewiątce (i podobnie po czwórce) nie mogą wystąpić inne cyfry w jednym kontekście, wiec możemy wyjść od razu */
        return suma;
    }
    else if ( istarts_with( s, cztery() ) )
    {
        s.erase( 0, 2 );
        suma += 4 * mnoznik();
        return suma;
    }
    else if ( istarts_with( s, piec() ) )
    {
        s.erase( 0, 1 );
        suma += 5 * mnoznik();
        /*po piątce natomiast mogą wystąpic jedynki, wiec nie możemy wyjść przedwcześnie */
    }
 
 
    /*Jedynki jako jedyne mogą wystąpić wielokrotnie po sobie w jednym kontekście, np. dla "III" zostanie trzykrotnie usunięty symbol jedynki i trzykrotnie do sumy dodane zostanie 1 * mnoznik, czyli po prostu mnożnik.*/
    while ( istarts_with( s, jeden() ) )
    {
        s.erase( 0, 1 );
        suma += mnoznik();
    }
 
    return suma;
}
 
/*Konkretne klasy dla danego kontekstu. Zwracają symbole cyfr o konkretnym znaczeniu w danym kontekście, na przykład "czwórka" dla jedności jest "IV", a dla setek "CD".*/
class Tysiace : public Wyrazenie
{
    public:
 
        std::string jeden() { return "M"; }
        std::string cztery() { return " "; }
        std::string piec() { return " "; }
        std::string dziewiec() { return " "; }
 
        int mnoznik() { return 1000; }
};
 
class Setki : public Wyrazenie
{
    public:
 
        std::string jeden() { return "C"; }
        std::string cztery() { return "CD"; }
        std::string piec() { return "D"; }
        std::string dziewiec() { return "CM"; }
 
        int mnoznik() { return 100; }
};
 
class Dziesiatki : public Wyrazenie
{
    public:
 
        std::string jeden() { return "X"; }
        std::string cztery() { return "XL"; }
        std::string piec() { return "L"; }
        std::string dziewiec() { return "XC"; }
 
        int mnoznik() { return 10; }
};
 
class Jednosci : public Wyrazenie
{
    public:
 
        std::string jeden() { return "I"; }
        std::string cztery() { return "IV"; }
        std::string piec() { return "V"; }
        std::string dziewiec() { return "IX"; }
 
        int mnoznik() { return 1; }
};
 
int main()
{
    /*2938 to jest nasz kontekst*/
    std::string rzymska( "MMCMXXXVIII" );
 
    /*Stworzenie gramatyki liczb rzymskich, w której występują, w kolejności, cyfry składające się na: tysiące, setki, dziesiątki i jedności. W ten sposób każda sekwencja znaków jest inaczej interpretowana, w zależności do tego, na jakim miejscu znajduje się w ciągu. Tworzenie listy ma tylko na celu ułatwienie dalszego korzystania.
Ponadto jest to tylko jedna z możliwości, często korzysta się z gramatyk opartych o struktury drzewiaste.*/
    boost::ptr_list< Wyrazenie > wyrazenia;
    wyrazenia.push_back( new Tysiace() );
    wyrazenia.push_back( new Setki() );
    wyrazenia.push_back( new Dziesiatki() );
    wyrazenia.push_back( new Jednosci() );
 
    int suma = 0;
 
    /* Przeanalizuj cale wyrażenie dla kolejnych kontekstów zgodnie z gramatyką. Kolejno dla każdego kontekstu przeanalizuj początek ciągu i usuń wykorzystane cyfry, dodając do sumy aktualnie obliczane wyrażenia. Ciąg jest przetwarzany zgodnie z wcześniej ustalonym porządkiem przy tworzeniu listy (gramatyki) wyrażeń, począwszy od tysięcy po jedności.*/
    BOOST_FOREACH( boost::ptr_list< Wyrazenie >::reference w, wyrazenia)
    {
        /*Wywołuje interpretuj() dla konkretnego potomka klasy Wyrazenie (działa mechanizm funkcji wirtualnych)*/
        suma += w.interpretuj( rzymska );
    }
 
    assert( suma == 2938 );
    std::cout << suma << std::endl;
 
    return 0;
}

Przykład 2.

#include <iostream>
#include <string>

using namespace std;

///klasa AbstractExpression
class Expression
{
  public:
    virtual bool interpret(string str)=0;
    virtual ~Expression() {}
};

///class ConcreteExpression
class TerminalExpression: public Expression
{
  public:
    TerminalExpression(string str)
    {
      literal=str;
    }

    bool interpret(string str)
    {
      size_t found;
      found=str.find(literal);
      if(found!=string::npos)
         return true;
      else return false;
    }

  private:
    string literal;
};

class OrExpression: public Expression
{
  public:
    OrExpression(Expression* ex1, Expression* ex2)
    {
      ex_1=ex1;
      ex_2=ex2;
    }

    bool interpret(string str)
    {
      return (ex_1->interpret(str) || ex_2->interpret(str));
    }
    ~OrExpression()
    {
      delete ex_1;
      delete ex_2;
    }

  private:
    Expression * ex_1;
    Expression * ex_2;
};

class AndExpression: public Expression
{
  public:
    AndExpression(Expression* ex1, Expression* ex2)
    {
      ex_1=ex1;
      ex_2=ex2;
    }

    bool interpret(string str)
    {
      return (ex_1->interpret(str) && ex_2->interpret(str));
    }
    ~AndExpression()
    {
      delete ex_1;
      delete ex_2;
    }

  private:
    Expression *ex_1, *ex_2;
};

int main(int argc, const char* argv[])
{
    ///tablica stringow -nasz kontekst
    string names[]={string("John"), string("Henry"), string("Mary"),
string("Owen")};

       // Literal
        Expression* terminal1 = new TerminalExpression(names[0]);
        Expression* terminal2 = new TerminalExpression(names[1]);
        Expression* terminal3 = new TerminalExpression(names[2]);
        Expression* terminal4 = new TerminalExpression(names[3]);

        // Henry or Mary
        Expression* alternation1 = new OrExpression(terminal2,
terminal3); 

        // John or (Henry or Mary)
        Expression* alternation2 = new OrExpression(terminal1,
alternation1);

        // Owen and (John or (Henry or Mary))
        Expression* define=new AndExpression(terminal4, alternation2);
    
  if(define->interpret("Owen likes Mary"))
    cout<<"Owen and (John or (Henry or Mary))"<<
    " from \"Owen likes Mary\" is "
    <<"true"<<endl;
  else
    cout<<"Owen and (John or (Henry or Mary))"<<
        " from \"Owen likes Mary\" is "
        <<"false"<<endl;

}

część 16

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.