[Pytanie] Kiedy stosować klas wewnętrznych.

0

Witam kolegów i koleżanki. Jako, że staram się cały czas poszerzać swoją wiedzę na temat tego języka, toteż po jakimś czasie (jak małemu dziecku) zaczyna przychodzić mnóstwo różnych pytań.

Dlatego chciałbym zadać na dzień dzisiejszy pierwsze pytanie.

Java umożliwia utworzenie klas wewnątrz innej klasy

public class klasaZewnetrzna{
private class KlasaWewnetrzna{
}
} 

Wiem dokładnie, że zaletą klasy wewnętrznej jest bezpośredni dostęp do np. zmiennych lokalnych klasy zewnętrznej.
Wiem też jak się stosuje, jednak moje pytanie brzmi:

Dlaczego stosujemy klasy Wewnątrz innych klas. Co więcej mogą nam dać od tworzenia zwykłych metod prywatnych.
Kiedy najlepiej stosować takie klasy, a kiedy wręcz uciekać. Jeżeli jeszcze można to poproszę o jakieś cenne wskazówki, albo odnośniki do artykułów (zakładam, że na pewno znajduje się gdzieś artykuł opisujący ten element w realiach OOP, jednak zawsze trafiałem na treści informujące jak ich użyć a nie jak i kiedy je wykorzystywać).

P.S. Jeżeli ktoś jeszcze poleci ogólnie jakieś materiały dotyczące głębszego omówienia zasad OOP, to bardzo ale to bardzo ładnie poproszę.

Pozdrawiam

1

Wyobraź sobie klase, ktora przechowuje dane w jakiejs swojej wlasnej, dziwnej strukturze. I obiekty skladajace sie na te strukture sa typu, ktory nigdzie indziej nie bedzie uzywany a przydaloby sie, zeby mial tez dostep do klasy "zarzadzajacej" - wtedy typem tych obiektow moglbys uczynic klase wewnetrzna.
Ponadto - jesli chcesz sobie w klasie utworzyc np jakis watek, to klasa tego watku w niektorych sytuacjach tez moze byc klasa wewnetrzna.
Ogolnie zastosowania same nasuna Ci sie do glowy, gdy bedziesz mial potrzebe z nich skorzystac ;) Ponadto uwzglednij to, ze klasa wewnetrzna moze byc np publiczna i statyczna (niezalezna od klasy zewnetrznej).

2

Najlepiej na przykładzie. Ściągnij źródła JDK i przyjrzyj się implementacją interfejsu java.util.Map. Masz tam klasę wewnętrzną Entity, która jest odpowiedzialna za bezpośrednie przechowywanie danych w mapie. Klasa wewnętrzna ma za zadanie przechowywać elementy implementacji, które:

  1. są specyficzne dla danej implementacji i nie będą wykorzystywane przez inne klasy:
  2. struktury danych
  3. elementy algorytmu wymagające wydzielenia w myśl SRP
  4. wydzielać dodatkową odpowiedzialność z klasy nadrzędnej, ale bez ujawniania jej poza tą klasą

Dodatkowo klasy wewnętrzne mogą dostarczać pewnych "logicznych" składników. Przykładowo we frameworku Vaadin interfejsami wewnętrznymi są Listenery. Każdy klikalny element, przycisk, checkbox, itp., ma w sobie zdefiniowane interfejsy do obsługi zdarzeń. Twórcy wyszli z założenia, że w ten sposób łatwiej jest zarządzać kodem oraz można uniknąć "głupich" błędów w stylu podpięcia nie tego listenera co trzeba. Klasa wewnętrzna może zatem służyć do wymuszania stosowania konkretnego typu i tym samym pozwalać na weryfikację poprawności kodu w trakcie kompilacji.

Przykładowo użycie typu enum (klasa wewnętrzna jest po prostu przypadkiem specyficznym, to odnosi się generalnie do typów wewnętrznych) w celu określenia płci:

public class Person {

	public static enum Type {
		human, deamon;
	}

	public static enum Sex {
		male, female;
	}

	private final String name;

	private final Type type;

	private final Sex sex;

	public Person(String name, Type type, Sex sex) {
		super();
		this.name = name;
		this.type = type;
		this.sex = sex;
	}

	public Type getType() {
		return type;
	}

	public Sex getSex() {
		return sex;
	}

	public String getName() {
		return name;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Person))
			return false;
		Person p = (Person) obj;
		return p.name.equals(name) && p.type == type && p.sex == sex;
	}
}

Jeżeli pleć była by przekazywana jako np. String to nie było by metody na sprawdzenie czy przekazano M, K, F, Mężczyzna, Male, Kobieta, Female, bez uruchomienia programu. Użycie typu wewnętrznego wymusza przekazanie prawidłowego argumentu.

0

Bardzo fajnie wytłumaczyliście znaczenie klas wewnętrznych. Przyznam szczerze, że właśnie tego szukałem.

P.S. Koziołek twoją stronę sobie podpinam pod ulubione. Bardzo ciekawe artykuły.

0

Jeszcze dodatkowo klasy wewnętrzne umożliwiają dziedziczenie
implementacji z innych klas (zarówno zewnętrznych jak i wewnętrznych).
To daje kolosalne możliwości dziedziczenia wielobazowego. Jeżeli gdzieś
przeczytałeś, że w Javie nie ma takiego dziedziczenia, to klasy
wewnętrzne właśnie to umożliwiają, przy czym możliwości te są o wiele
większe od czegokolwiek, co można uzyskać np. w języku C++. W sumie
implementacja klas wewnętrznych, które dziedziczą nie różni się prawie
wcale od implementacji dziedziczenia wielobazowego. Różnica polega tylko
na tym, że te pierwsze mają precyzyjnie określone interakcje i prawa
dostępu między sobą, a klasą zewnętrzną, a w C++ potrzeba interpretacji
z mnóstwem zasad i wyjątków do pamiętania. Możliwości są o tyle większe,
że klasy wewnętrzne mogą mieć również swoje klasy wewnętrzne, co przy
klasycznym dziedziczeniu wielobazowym jest w ogóle nie do pojęcia bo w
C++ trzeba by było to nazwać jakimś dziedziczeniem wielowymiarowym
(zamiast prostej listy).

0

@Olamagato, właśnie mi uzmysłowiłeś, że w ten sposób już od pewnego czasu tworzę miksiny. Interfejs+ klasa wewnętrzna z domyślną implementacją.

0

Hm na pewno to jest równoważne wielodziedziczeniu? Zmęczony jestem, może pokażecie o co wam chodzi?

0

Kiedyś pisałem o tym w tym wątku: http://4programmers.net/Forum/Java/135742-Interfejs_z_implementacj%C4%85_funkcji?hl=wielobazowe.
Poczytasz i sprawdzisz. :)
Taka równoważność zachodzi jeżeli klasa będzie zawierać publiczne, chronione lub prywatne pola zainicjowane obiektami swoich publicznych klas wewnętrznych i te klasy wewnętrzne nie będą miały kolejnych swoich klas wewnętrznych z jakimś dziedziczeniem np.:

class Base {/*...*/}

class Base2 {/*...*/}

class Base3 {/*...*/}

class Base4 {/*...*/}

//dziedziczenie publiczne, chronione i prywatne
class Derived extends Base
{
	public class Derived2 extends Base2 {/*...*/};

	public class Derived3 extends Base3 {/*...*/};

	public class Derived4 extends Base4 {/*...*/};

	protected Derived2 derived2 = new Derived2();
	private Derived3 derived3 = new Derived3();
	public Derived4 derived4 = new Derived4();

	/*...*/
	{
		Base3 test3 = this.derived3
	}
}

Derived object = new Derived();

class DerivedDerived extends Derived
{
	public void test()
	{
		Base2 test2 = object.derived2;
	}

}

//...
	{
		Base test1 = object;	//access granted
		Base2 test2 = object.derived2; //not access: protected field
		Base3 test3 = object.derived3; //not access: private field
		Base4 test4 = object.derived4; //access granted
	}

Jeżeli klasy wewnętrzne w sensie definicji będą chronione lub prywatne, albo będą miały dalsze klasy wewnętrzne z dziedziczeniem, to wykraczamy od tego miejsca daleko poza możliwości języka C++ (i większości innych języków z dziedziczeniem wielobazowym).
Możliwości są ogromne.

0

Hmmm, dla mnie to są delegaty :D

Używa się tego znacznie inaczej niż normalnego wielodziedziczenia, takiego jak np w Scali. Na każdym poziomie dziedziczenia trzeba napisać od nowa gettery (bo pola nie są wirtualizowane, jeżeli A extends B i obie klasy definiują pole i, to A.i jest innym polem niż B.i). Albo zamiast tego na każdym poziomie trzeba pisać delegaty do tych instancji, jeśli to główna klasa będzie implementować wszystkie interfejsy.

Jesteś pewien że w C++ nie da się robić takich rzeczy? W końcu niestatyczna klasa wewnętrzna to zwykła klasa tylko z implicite referencją do klasy zewnętrznej. W C++ spokojnie da się to zrobić, jest też słówko kluczowe friend do zaprzyjaźniania klas.

0

Używa się inaczej ponieważ w Javie jest zupełnie inna metoda uzgadniania interfejsów klas. Co do delegowania - tak, jednak dzięki temu naprawdę trudno się pogubić i popełnić jakiś paskudny błąd, których tak wiele w C++ jest na porządku dziennym. Gettery i settery są ceną za brak możliwości wystąpienia konfliktu. W każdym razie Java umożliwia w ten sposób dziedziczenie wielu implementacji.
Co do tego czy w C++ nie jest to możliwe? Jest (teoretycznie), ale ilość siwych lub wyrwanych włosów będzie wprost nieproporcjonalna do złożoności takiego kodu.
To trochę tak jak bawić się tablicami "wielowymiarowymi" w C/C++ - nikt realnie tego nie robi (poza studentami :)) bo wady przewyższają wszelkie zalety.

0

No to i tak jakoś wielkiej rewolucji z tego nie ma, przynajmniej nie w takiej postaci w jakiej to Java udostępnia. Nadal 100x bardziej opłaca się używać Scali i potem co najwyżej wołać klasy Scalowe z kodu Javowego. Sporo bajtkodu jest wtedy automatycznie generowane i nie ma też wtedy dodatkowej referencji do wyłuskania.

Nie wspomnieliście tu chyba o najważniejszej funkcji klas wewnętrznych - są one namiastką domknięć. Namiastką, ponieważ funkcje w Javie nie są obiektami i nie da się dostać z zewnątrz, ani wewnątrz do ich lokalnych zmiennych. W rzeczywistości jeśli robimy anonimową klasę wewnętrzną to możemy dostać się do pól finalnych z tworzącej domknięcie funkcji, tyle że to jest robione poprzez skopiowanie tych finalnych zmiennych do nowego obszaru pamięci skojarzonego z domknięciem.

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