Jak to zaprojektować?

0

Cześć, mam pewną zagwozdkę. Mamy pięć klas : class A, class B .. class E.

Jest między nimi taka zależność :

klasa A zawiera referencję do obiektów klasy B i C;
klasa B zawiera referencję do obiektu klasy D;
klasa C zawiera referencję do obiektu klasy E;

W pewnym momencie potrzebujemy komunikacji między jakimiś polami klasy D i E - tymi położonymi "najniżej " w tym drzewie zależności.

W jaki sposób rozwiązać tą komunikację uwzględniając metody pisania czystego kodu ? Niech przykładowym problemem do rozwiązania będzie to, że trzeba dodać inta z klasy D do inta z klasy E;

Mój wstępny pomysł jest taki, aby to dodawanie przeprowadzić w klasie A, która jest "najwyżej":

b.getObjectDInstance().getInt1() + c.getObjectEInstance().getInt2()

Czy to jest ok ? Jest jakiś wzorzec projektowy, który opisuje taki problem ?

Bo zawsze można zrezygnować z klas B,C,D,E i dać wsio do klasy A, ale to jest przecież niezgodnie z zasadami OOP.

Pozdrawiam

0

Jedyna sytuacja którą kojarze gdzie realnie musiałem takie coś zrobić to przesyłanie komunikatów pomiędzy kontrolkami GUI w aplikacji i zrobiłem to zgodnie z MVC/MVP więc requesty od kontrolek leciały do Kontrolera a on je rozsyłał do zainteresowanych obiektów.

W innej sytuacji wydaje mi się to dość dziwne że te obiekty położone nisko miałyby ze sobą z jakiegoś powodu współpracować.

0

Ok konkretnie mam taką sytuację :

Piszę sobie prosty programik GUI z wykorzystaniem Swinga.

Mam klasę JFrame i w niej :

  • JPanel w NORTH, który zawiera 2 TextFieldy i 2 Buttony
  • TextArea w CENTER

Teraz chcę zrobić tak, że po kliknięciu przycisku coś się dzieje i w TextFieldzie i w TextArea i zastanawiam się gdzie wstawić wewnętrzną klasę listenera. Dodatkowo mam klasę DatabaseMenager, która zajmuje się połączeniem z bazą danych i wysyła zapytania. I chciałbym, żeby drugi button wysyłał np. te zapytania.

No i teraz jak ? Zrobię nadrzędną klasę dla całej apki i dam ta JFrame i DatabaseMenager, w DatabaseMeganer dam wewnętrznego listenera do Buttona to jak teraz go w buttonie, który znajduję się głęboko w d ... zarejestrować się tym listenerem ?

0

MVP Przekazywanie parametrów do komponentów widoku

Podsumowując tamten temat: w moim przypadku zrobiłłem tak że wszystkie akcje obsluguje prezenter, tzn klikniecie w buttom = wywołanie akcji na prezenterze. Prezenter następnie zajmuje się wywołaniem akcji na warstwie logiki biznesowej a potem przesyła wyniki do widoku. Widok ma w sobie GuavaEventBus i w tym event busie rejestrują się wszystkie komponenty które oczekują na odpowiednie eventy. Po przesłaniu danych od Prezentera do Widoku, Widok opakowuje dane w odpowiedni obiekt Event i wysyła przez EventBus.

0

Staram się od wczoraj rozkminić tamten temat i dodatkowo szukałem info o tym Guava EventBus bo pierwszy raz to słyszę i w ogóle zastanawia mnie enigmatyczna nazwa - Guava ?

Mogę bazować na tym, co jest tu napisane: [url]http://javarticles.com/2015/04/guava-eventbus-examples.html[/url] ?

Reasumując ..

Mam klasę zajmująca się komunikacją z bazą danych : DatabaseMenager,
klasę zajmująca się całym GU
i jedną, która zawiera obiekty obu klas.

Teraz w modelu MVP, modelem jest DatabaseMenager, widokiem klasa GUI, a prezenterem ta klasa, która spaja ?

A stan labelów i textArea z klasy GUI to też powinno należeć do widoku ?

Staram się to jakoś ogarnąć, żeby było zgodnie z MVP ( z którym 1. raz się spotykam prawdę mówiąc ) i zastanawiam się jak zrobić całą kompozycję - która klasa zawiera obiekt której.

0

Guava to po prostu Googlowa biblioteka do Javy, stąd nazwa ;]
Prezenter dostaje eventy z Widoku, woła operacje na logice biznesowej (np. pobiera coś z bazy) a potem przesyła uzyskane dane do Widoku. Nie ma tu wielkiej filozofii ;]
Prezenter to jest jedyna klasa która widzi zarówno Model jak i Widok i zajmuje sie pośredniczeniem między nimi.

Nie musisz używać event busa, ale wtedy będziesz miał sytuacje taką, że prezenter wysyła do widoku dane które mają sie pojawić w labelce, widok wysyła je do odpowiedniego okienka, to okienko przesyła do odpowiedniego panelu, panel do subpanelu, subpanel do... i na koniec po 100 delegowanych wywołaniach trafiają one w końcu do upragnionej labelki ;]

0

Ok, rozjaśnia mi się wszystko jak przeglądam sample z zastosowaniem tego MVP .. generalnie to już używałem w skrócie tego nie będąc świadomym.

Zostaje mi nie do końca jasna kwestia ( jeśli nie korzystam z tej Guavy ) gdzie dać wewnętrzne klasy listenerów. Z tego co mówisz, to prezenter odbiera od View eventy, więc w prezenterze powinny być te listenery. Ale wtedy przy rejestracji w buttonie, jeśli np ta rejestracja odbywa się w konstruktorze Presentera a button jest głęboko na n-tym poziomie subpanelu i ja potrzebując jego instancję do rejestracji to będzie wyglądać to niezbyt fajnie :

 
view.getSubPanel1().getSubPanel2(). .. .getButtonInstance().addActionListener ( buttonListener );
 

Czytałem jakiś temat jak pomagałeś też komuś z MVP i mówiłeś, żeby nie łączyć technologicznie listenerów z prezenterem.
To w takim razie dać w widoku wszystkie listenery i dodatkowo w widoku referencję do prezentera ?

W sensie

class Presenter
{
  Model model;
  View view;
}

class Model
{
// nie ma referencji 
}

class View
{
  Presenter presetner;
}

0

Absolutnie nie! Jejku, jej!
Listener to jest część widoku i powinien być tam gdzie tworzysz buttona! Po prostu musisz dostarczyć referencje do prezentera "w dół" tak żebyś mógł zrobić

buttonX.addActionListener(e -> presenter.buttonXClicked());

Zapominasz że Widok jak najbardziej powinien mieć referencje do Prezentera ;]

0

Ok, ale rejestracje listenera przeprowadzam na poziomie buttona jak Ty czy w ogólnej klasie widoku ? Tzn.

button.addListener ( .. ) ..

// czy

getPanelInstance().getButton().addListener ( .. )

?

I skoro mówisz, że listener winien być częścią widoku to moje rozumowanie nadal jest inne niż Twoje, bo Ty przekazałeś w addListenerze metodę Prezentera ( a czym jest tutaj e ? ) , ja chciałem przekazać wewnętrzną klasę widoku. W sensie tak to wszystko widzę :

Albo tak :


class Model
{
	private int x;
	
	public void setX ( int x )
	{
		this.x = x;
	}
	
	public int getX ()
	{
		return x;
	}
}

class SubPanel
{
	private Button button = new Button ();
	public getButtonInstance ()
	{
		return button;
	}
}

class View
{
	private SubPanel subPanel = new SubPanel();
	private Prezenter prezenter;
	
	public View ( Prezenter prezenter )
	{
		this.prezenter = prezenter;
		subPanel.getButtonInstance().addListener ( buttonListener );
		
		
	}
	
	private class buttonListener 
	{
		public void actionPerformed(ActionEvent event)
		{
			prezenter.model.setX ( 5 );
		}
	}
	
}

Albo tak:


class Presenter
{
	View view = new View ( this );
    Model model = new Model();
  
}

class Model
{
	private int x;
	
	public void setX ( int x )
	{
		this.x = x;
	}
	
	public int getX ()
	{
		return x;
	}
}

class SubPanel
{
	
	private Button button = new Button ();
	private Prezenter prezenter;
	
	public SubPanel ( Prezenter prezenter )
	{
		this.prezenter = prezenter;
		button.addActionListener( buttonListener );
		
	}
	
	
	
	private class buttonListener 
	{
		public void actionPerformed(ActionEvent event)
		{
			prezenter.model.setX ( 5 );
		}
	}
	
	
}

class View
{
	private SubPanel subPanel;
	private Prezenter prezenter; // w obecnym przypadku nie używany, bo na najwyższym poziomie widoku nie mamy końcowego elementu 
	
	public View ( Prezenter prezenter )
	{
		this.prezenter = prezenter;
		subPanel = new SubPanel ( prezenter );
	}
	
}


Czy któryś z tych 2 rozwiązań jest wg Ciebie ok ?

0

Oczywiście w 1 przypadku również znajduje się identyczna klasa prezentera - ucięło mi.

0

Moim zdaniem 2 to jedyna która ma tu sens. Pierwsza okaże sie niepraktyczna jak aplikacja będzie większa. Wyobrażasz sobie potem mieć w jednym miejscu 1000 listenerów dla wszystkich buttonów w calej aplikacji? o_O
Poza tym zamiast tej prywatnej klasy możesz sobie w Javie 8 zrobić lambde:

button.addActionListener(e->prezenter.model.setX(5));

I efekt będzie ten sam.

0

Dobra, zaczynam to kumać .. w sumie napisałeś już mi to, żeby przekazywać w widoku referencje do Prezentera w dół, ale nie skumałem z początku.

I teraz z tym EventBusem to chodzi o to, że w klasie widoku tworzę EventBus eventBus = new EventBus() i przekazuję referencję do tego w dół tak samo jak referencję prezentera ?

Bo tak jak mówisz, że gdyby to robić bez EventBusa i delegować w dół do docelowego elementu każdy update, to w gruncie rzeczy musimy mieć w klasie View tyle metod, które zaczynają łańcuch delegacji ile jest w sumie wszystkich końcowych komponentów w sumie w każdym poziomie - czyli, jeśli we głównym oknie mamy 2 panele, po 2 buttony to w View musimy mieć updateButton1() - ..4() ? Dobrze kombinuję ?

0

Jeszcze jedno pytanie mi się nasunęło odnośnie paneli i podpaneli. Dla każdych paneli i podpaneli i ich podpaneli tworzysz osobną klasę w osobnym pliku czy wszystko robisz za pomocą klas wewnętrznych ?

0

@MichałBambo

  1. Tak, wspomniałem o event busie właśnie po to żeby uniknąć takiej akcji że nagle ta klasa na samej górze ma milion metod bo wszystko deleguje w dół
  2. Możesz przekazywać referencje do Prezentera i do Event busa a mozesz mieć jakieś IoC, ServiceLocatora, Singleton czy coś podobnego, tu wiele zależy od tego jak to implementujesz
  3. To zależy. Jeśli jakiś panel jest skomplikowany to nie widzę problemu z jego wydzieleniem. Wygodniej potem mieć pare plików po 50 linijek niż jeden na 1000 ;]
0

Ty u siebie w jaki sposób zrobiłeś tam przekazywanie Prezentera i EventBusa w widoku, tak aby wszystkie komponenty miały dostęp ? Myślałem też, żeby zamiast tego przekazywać referencję do Widoku, w którym się dane komponenty znajdują.

A co do Singletona. To jeśli uczynię Prezentera Singletonem to czy Model nie będzie miał do niego też dostępu ? Nie jest to trochę sprzeczne z MVP ?

1

Jako że Widoków było u mnie dużo to po prostu w bazowej klasie dla nich jest EventBus, więc komponenty rejestrują się na otrzymywanie eventów właśnie w Widoku, który jest do nich przekazywany "w dół". Dodatkowo jest też kwestia tego jaka jest logika działania aplikacji. U mnie na przykład była możliwość że jednocześnie jest aktywnych kilka Widoków tego samego typu, co trochę komplikowało całą sprawę i wykluczalo użycie beanów/singletonów w warstwie widoku.

To że coś "może mieć dostęp" nie znaczy że z niego korzystać. Sam fakt że coś jest beanem / singletonem o niczym nie świadczy. Dopóki nie zaczniesz z jakiegoś powodu samodzielnie łamać MVP to się samo nie złamie.

0

@Shalom

Dzięki wielkie za pomoc w temacie - sporo ogarnąłem nowych info dzięki Tobie. Zdecydowałem, że zamiast przekazywać w widoku w dół do komponentów pary Prezenter-EventBus będę przekazywał refenerację do widoku, w którym znajdują się dane komponenty i dzięki temu w każdym komponencie będę miał dostęp view.prezenter i view.eventBus.

Jeszcze nad jednym kombinuję. Jak opakowywałeś dane w własny obiekt eventu to tworzyłeś dla każdego osobnego przypadku nową klasę eventu ? Jeśli np. mam sytuację, gdzie klikam button 'clear' i mają mi się 2 pola tekstowe wyczyścić to mam zrobić osobną klasę bez żadnego ciała tylko po to, żeby zidentyfikować zdarzenie ?

Udało się konto odzyskać ;)

0

@Shalom

Jeszcze jeden problem po drodze wyniknął.

Jak rozwiązałeś sytuację, kiedy np po kliknięciu buttona wywoływała się w Prezenterze jakaś akcja na modelu, ale potrzebowałeś dostarczyć do tego modelu jakiś danych np. z TextFielda, który znajdował się zupełnie w innej gałęzi niż Twój button ?

Wtedy robiłeś w tej metodzie Prezentera takie długie odwołanie :

view.getPanel().getSubPanel().getTextField().getText();

Bo tak to wszystko ładnie mi się rejestruje w event busie itd.

Druga sprawa, która mi się nie podoba. Im bardziej rozrasta się apka, im więcej mam komponentów, tym wzrasta liczba metod w Prezenterze .. ta klasa się będzie rozrastać do takich rozmiarów ...

0

Nie, wszystko w modelu push. Prezenter nie pobiera danych z Widoku tylko je dostaje. W sytuacji o której mówisz lepiej po stronie widoku obsłużyć pobranie danych (bo komponenty i tak mają do Widoku dostęp). Nie unikniesz wtedy tego kaskadowego wywołania żeby pobrać dane.
Mógłbyś niby użyć do tego znów Eventów, ale debugowanie tego potem to były koszmar ;]

0

Czyli jedynie taka różnica, że zrobię w widoku

addRecordButton.addActionListener( e ->{ 
 String s1 = getView().getPanel().getSubPanel().getTextField1().getText();
 String s2 = getView().getPanel().getSubPanel().getTextField2().getText();
 getView().getPresenter().addRecord( s1, s2 ) 
} );

1

Absolutnie! Bój ty się Boga takie rzeczy pisać. Więcej niż 1 kropka w linii to jest horror i zakaz używania komputera przez tydzień. To się zresztą nazywa "przeciekaniem warstw" ;] Rób ładne delegowane metody a nie takie przejście przez pół systemu. Nie mówiąc już o tym że powinieneś wyciągnać te dane NA RAZ w jakims ładnym DTO.

TextFieldsData data = getView().getTextFieldsData();
//
TextFieldsData getTextFieldsData(){
    return getPanel().getTextFieldsData();
}

itd

0

No tak, zapomniałem o tym delegowaniu ..

A słuchaj, jeśli w ostatniej operacji, czyli kiedy już mamy uaktualnić widok po operacji na modelu i w prezenterze postuje na eventBusie :

public void actionInModel()
	{
		model.action();
		
		view.getEventBus().post ( new Action1Event() );
		view.getEventBus().post ( new Action2Event() );
	}

To mogę zrobić tak jak tu w przykładzie, że wywołuje 2 rózne akcje ? Bo np chcę coś uaktualnić w TextArea i to niech będzie pierwszy post i chcę również wykonać akcję, którą już wywołuje po zrobieniu czegoś innego, ale tutaj również muszę ją wykonać ?

AAA szybko to zaraz zmienię bo mam dwie kropki ! :D

2

Masakra to jest to co tutaj wyprawiasz. Rozumiesz po co w ogóle robi sie takie rzeczy jak interfejsy? Wiesz co to enkapsulacja? Po co Prezenter na wiedzieć jakie są szczegóły implementacji Widoku? o_O Po co mu wiedzieć że jest tam jakis event bus? A jak będziesz zmieniał technologię Widoku to nagle przepiszesz cały system bo wszystko będzie ze sobą sklejone? o_O
Prezenter posyła dane do Widoku, koniec, kropka.
Tak maja wyglądać metody Prezentera:

public void someAction(SomeDataDto data){
    ResultData result = someService.doSomething(data);
    view.handleResultData(result);
}

Oczywiście serwisów możesz wołać więcej ale idea jest chyba jasna... Prezentera ma nie obchodzić jak zaimplementowany jest Widok, tak samo jak Model/klasy Serwisowe powinny być zupełnie niezależne od reszty systemu.

0

No dobra, ale widok ma mieć tylko jedną metodę handleResultData ( .. ) ? Tam mogę przekazywać argument każdego typu, więc tak się chyba nie da ..

0

@Bambo nie no jasne że ma mieć ich więcej. Tak samo jak SomeDataDto też pewnie będzie kilka rodzajów i tak samo ResultData. To był tylko przykład.

1 użytkowników online, w tym zalogowanych: 0, gości: 1