Edytor tekstu oparty o MVC

Cel

W ramach realizacji tego przykładu konieczne było takie zmodyfikowanie gotowej aplikacji napisanej w J2SE i będącej bardzo prostą, tutorialową demonstracją wykorzystania modelu MVC, aby zastąpić implementacje Modelu z obiektów Javy na Logtalk oraz aby zmodyfikować w jak najmniejszym stopniu pozostałą część aplikacji.

Stan wejściowy

Wejściowa aplikacja jest prostym edytorem tekstu zawierącym, jak widać na poniższym diagramie :

:pl:miw:miw08_ruleruntimeo:figure2.gif

Widok (View)

a dokładnie dwa widoki :

  1. Wyświetlający wprowadzony tekst, klasa DisplayViewPanel
  2. Umożliwiający wprowadzanie tekstu oraz modyfikowanie jego właściwości takich jak czcionka, rozmiar czcionki, przeźroczystość tekstu, obrót tekstu wokół osi itp, zaimplementowany jako klasa PropertiesViewPanel

Model

służy do przechowywania wprowadzonego tekstu oraz wartości tych właściwości. Klasy modelu w projekcie to :

  1. TextElementModel - przechowuje stan tekstu wyświetlanego w dokumencie, takie jak wartość tekstu, czcionka itp
  2. DocumentModel - zawiera stan całego dokumentu tekstowego, czyli nazwę, wysokość, szerokość

Kontroler (Controller)

służy do przekazywania do Modelu odpowiednich zmian właściwości na podstawie komunikatów nadsyłanych z Widoku, czyli generalnie odpowiada za mapowanie zmian dokonanych przez użytkownika na odpowiednie zmiany w Modelu. Kontroler został zaimplementowany w klasie DefaultController.

Będąc bardziej szczegółowym projekt ten używa lekko zmodyfikowanej wersji wzorca MVC, w której zmiany stanu Modelu nie są bezpośrednio propagowane do Widoku, ale są przekazywane poprzez Kontroler. Czyli projekt używa poniższej architektury : :pl:miw:miw08_ruleruntimeo:figure4.gif

Czyli zmiany wykonane przez użytkownika w jednym z widoków, powodują wywołanie odpowiedniej metody w Kontrolerze. Kontroler ma dostęp do odpowiedniego modelu i jest w stanie odpowiednio zmodyfikować stan Modelu. Model wykrywając zmianę jednej z swoich właściwości, wywołuje odpowiednie zdarzenie zmiany stanu. Zdarzenie to jest obsługiwane przez Kontrolera, który w właściwy sposób modyfikuje Widok. Jak widać główną różnicą jest fakt pośredniczenia Kontrolera w każdej komunikacji pomiędzy Widokiem a Modelem co okazało się bardzo korzystne w trakcie przechodzenia na implementacje Modelu w Logtalk'u.

Dokładny opis oraz źródła wejściowej aplikacji można znaleźć w Materiałach.

Dokonane modyfikacje

Jak widać architektura tego projektu sprzyjała procesowi implementacji części klas w Logtalk'u ze względu na silne odseparowanie modułów, oraz posługiwanie się referencjami do klas abstrakcyjnych takich jak AbstractController, AbstractView czy AbstractModel co bardzo ułatwiało podmianę docelowej implementacji tych obiektów.

Modyfikacje Widoku

Klasy Widoku, zgodnie z założeniami, nie uległy żadnym modyfikacjom.

Modyfikacje Modelu

Klasy Modelu tak naprawdę służą tylko do przechowywania wartości kilku właściwości i cała ich implementacja polega na polach klasy oraz getter'ach i setter'ach. Jesteśmy zatem w stanie bez większego problemu wymienić klasy modelu napisane w Javie na obiekty Logtalk, na zasadzie 1:1. Czyli tworzymy:

  1. documentModel w pliku documentModel.lgt
  2. textElementModel w pliku textElementModel.lgt

Aby powtórzyć tą samą funkcjonalność w Logtalku wystarczy zatem napisać prostą Kategorie, którą potem zaimportujemy do każdego z obiektów Modelu :

:- public(set_property/2).
    :- mode(set_property(+nonvar, +nonvar), one).
 
    :- public(get_property/2).
    :- mode(get_property(?nonvar, ?nonvar), zero_or_more).
 
    :- private(property_/2).
    :- mode(property_(?nonvar, ?nonvar), zero_or_more).
    :- dynamic(property_/2).
 
    set_property(Property, Value):-
        write('Property test : ' + OldValue),nl,
        ::retractall(property_(Property, OldValue)),
 
        ::assertz(property_(Property, Value)),
        ::firePropertyChange(Property, OldValue,  Value).
 
    get_property(Property, Value):-
        ::property_(Property, Value),
	write(Value).
 
 
:- end_category.

Tak dzięki tym predykatom jesteśmy w stanie dokonywać dynamiczego zapisu wartości odpowiednich właściwości Modelu do Bazy wiedzy Prologa. Kod obiektu documentModel wygląda następująco :

:-object(documentModel, imports(properties)).
:-public(firePropertyChange/3).
 
firePropertyChange(PropertyName, OldValue, NewValue):-
	write('Document: PropertyName ' + PropertyName),nl,
	write('Document: NewValue ' + NewValue),nl
	.
:-end_object.

Tak naprawdę w tym przypadku kod modelu ograniczy się do importowania tej kategorii, jeden predykat służy tylko do wypisania na konsole zmiany stanu modelu. Niestety jako, że problematyczne okazało się wywoływanie zdarzeń Javowych bezpośrednio z kodu Prologa, niestety wymagałoby to przechowania referencji do Obiektu Kontrolera wewnątrz obiektu Logtalk, co niestety okazało się niemożliwe przy użyciu JPL, który dość dobrze radzi sobie z przechowywaniem referencji do obiektów Javy tworzonych przy użyciu JPL, natomiast niekoniecznie takich istniejących już wcześniej.Konieczne było zastosowanie pewnej modyfikacji działania Kontrolera, ale o tym poniżej.

Modyfikacje Kontrolera

Drugą modyfikacją konieczną do implementacji Modelu w Logtalk'u była implementacja Kontrolera w taki sposób, aby wykorzystując JPL będzie tworzył obiekty Logtalk oraz wywoła na nich metodę zapisującą wartość danej właściwości. Jako, że kłopotliwe okazało się przechowywanie referencji do kontrolera wewnątrz obiektu Logtalk'a, co jest konieczne do ustawienia Listnera, nie można było dokładnie skopiować rozwiązania z wejściowego projektu. Zamiast tego kontroler po ustawieniu danej właściwości, sprawdza jej wartość i odpowiednio modyfikuje widok.

Zatem architektura została zachowana, podmieniono obiekty Modelu na zasadzie 1:1 i jedyne co musiało się zmienić to implementacja Kontrolera, która była o tyle ułatwiona, że w pozostałych modułach używana jest referencja do klasy abstrakcyjnej AbstractController, zatem podmiana jej implementacji z DefaultController na LogtalkControler była właściwie bezbolesna. Co do implementacji kontrolera to klasa LogtalkControler zawiera dwa bardziej interesujące fragmenty :

Konstruktor:

  • nawiązujemy przez JPL komunikacje z SWIProlog i konfigurujemy runtime Logtalk'a,szczegółowy opis
  • Tworzymy obiekty Modelu w następujący sposób :
Query loadDocumentModel = new Query("logtalk_load",
				new Term[]{new Atom("src/com/sun/example/mvc/model/documentModel")}
				);
 
		if(loadDocumentModel.hasSolution())
		{
			System.out.println("DocumentModel loaded succesfully");
		}
		else
		{
			throw new RuntimeException("Unable to load DocumentModel");
		}

Metoda setModelProperty(String propertyName, Object newValue) - dokonująca właściwej zmiany w modelu a nastepnie update w Widoku.

 
		Query setLogtalkProperty  = null;
		Query getLogtalkProp = null;
		if (this.isDocumentModel) {
			setLogtalkProperty 
			//= new Query("documentModel::set_property('"
+propertyName + "', jpl_new('java.lang.String',['" 
+ newValue.toString() + "'], _)).");
			= new Query("documentModel::set_property('"
+propertyName + "', '" + newValue.toString() + "').");
 
			getLogtalkProp = new Query(
					"documentModel::get_property('" + propertyName + "', X).");
		}else {
			setLogtalkProperty = new Query("textElementModel::set_property('"+propertyName + "', '" + newValue.toString() + "').");
 
			getLogtalkProp = new Query(
					"textElementModel::get_property('" + propertyName + "', X).");
		}
 
 
		if(setLogtalkProperty.hasSolution())
		{
		}
		else
		{
			throw new RuntimeException("Unable to set property named : " + propertyName );
		}
 
		Variable X = new Variable("X");
 
 
		if(getLogtalkProp.hasMoreElements())
		{
			Object elem = getLogtalkProp.nextElement();
			if (elem instanceof Hashtable) {
				Hashtable<String, Atom> solution = (Hashtable<String, Atom>) elem;
 
				System.out.println("Solution is " + solution.get("X"));
 
 
 
					Atom atomVal = (Atom)solution.get("X");
					String strValue = atomVal.name(); 
					Object value = strValue;
					try {
						value = java.lang.Integer.valueOf( strValue).intValue();
					} catch (Exception e) {
						// TODO: handle exception
					}
 
					DisplayViewPanel displayView = (DisplayViewPanel) this.registeredViews.get(0);
					PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, newValue, value);
					displayView.modelPropertyChange(event);
 
			}
		}

Dzięki tym modyfikacją da się zachować pełną funkcjonalność aplikacji, jednocześnie implementując obiekty Modelu w Logtalk'u.

Źródła projektu znajdują się w dziale Projekt.

pl/miw/miw08_ruleruntimeo/mvceditor.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0