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
- Klient tworzy kontekst.
- Wykorzystuje dostępne wyrażenia, albo na ich podstawie buduje wyrażenia złożone.
- Wywołuje metodę
interpret()
przekazując kontekst i podejmuje odpowiednie działania.
Konsekwencje
- Łatwo można dodawać kolejne warunki lub tworzyć swoje.
- 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;
}