Rzutowanie w górę, w jakim celu ?

0

Witam. Moje pytanie dot. tworzenia obiektów z jednoczesnym rzutowaniem ich w górę (albo w dół. nie pamiętam jak to się nazywało ;-).

Załóżmy, że mamy takie oto dwie klasy:

public class A {
public String nap1 { return "nap1"; }
public String nap2 { return "nap2"; }
}

public class B extends A {
public String nap3 { return "nap3"; }
}

Tworząc obiekt w taki sposób:

A nowy = new B();

będę miał dostęp tylko do metod z klasy A + ew. przesłoniętych w klasie B.

W jakim celu to się robi ? Czy nie lepiej tworzyć tak:

B nowy = new B();

i mięć pełen dostęp do tego co daje nam klasa A i klasa B ?

Przykład z życia to taki:

Map<Object, Object> map = new HashMap<Object, Object>();

dlaczego tutaj pozbywamy się nowych funkcjonalności jakie daje klasa HashMap ?

0

np. polimorfizm. W Javie i C# Object przechowuje rowniez dane o typie, takze mozna przywrocic funkcjonalnosc (poczytaj sobie o Reflection)

0
krwq napisał(a):

np. polimorfizm. W Javie i C# Object przechowuje rowniez dane o typie, takze mozna przywrocic funkcjonalnosc (poczytaj sobie o Reflection)

Wiem o tym, ale dlaczego robi się np.

Map<Object, Object> map = new HashMap<Object, Object>();

zamiast:

HashMap<Object, Object> map = new HashMap<Object, Object>();

Trochę nie logiczne, że traci się dostęp do nowych składowych/metod poprzez rzutowanie w górę...

Dlaczego wykonuje się te operacje ? Rozumiem, że jeżeli mamy klasę Pracownik i klasy tj. Dyrektor, Sprzatacz itp., które dziedziczą po klasie Pracownik i chcemy w danej metodzie skorzystać z metody która zwraca zarobki danego pracownika to wywołujemy getPlaca() ew. przesłoniętej metody i mamy wynik, tutaj jasne jest. Ale po co rzutować w górę przy HashMap ? Bo do tego głównie pije...

0

Przyczyna jest prosta - uzależnienie od abstrakcji. Często jest tak, że Twoich metod nie interesuje rzeczywisty typ obiektu, tylko jego interfejs. Pozwala to na łatwą podmianę implementacji w przyszłości.

0
mychal napisał(a):

Przyczyna jest prosta - uzależnienie od abstrakcji. Często jest tak, że Twoich metod nie interesuje rzeczywisty typ obiektu, tylko jego interfejs. Pozwala to na łatwą podmianę implementacji w przyszłości.

Mógłbyś podać prosty przykład ?

0

Sam go już podałeś. Np. Co byś zrobił, gdyby nagle zaszła potrzeba zmiany HashMap na np.IdentityHashMap? Musiałbyś zmienić wszystkie metody, które jako argument przyjmowały Twój HashMap!! Istna katorga. Gdybyć używał interfejsu(Map) nie miałbyś tego problemu - zmieniłbyś tylko jedną linijkę - tę z wywołaniem konstuktora.

1

Ech to jest dość proste. Wyobraź sobie że napisałeś program który korzysta i przetwarza listy z danymi. Możesz więc w metodach użyć List<T> albo jakiejś konkretnej implementacji np. ArrayList<T>. A teraz wyobraź sobie że wpadłeś na to że LinkedList<T> jest lepsze od ArrayList<T> bo często dodajesz nowe elementy i array list się ślimaczy. W pierwszym przypadku zmieniasz tylko miejsce tworzenia listy. W drugim przypadku masz setki miejsc do zmienienia...
Zauważ że często te dziedziczące po sobie klasy nie mają wcale dodatkowych metod, tylko inną implementację! Poczytaj co to wzorzec Strategy (strategia) i wzorzec Template Method (metoda szablonowa).

Używając wszędzie interfejsów zamiast konkretnych implementacji klas jesteś niezależny od tych implementacji i mozesz je podmienić w dowolnej chwili.

0
Shalom napisał(a):

Ech to jest dość proste. Wyobraź sobie że napisałeś program który korzysta i przetwarza listy z danymi. Możesz więc w metodach użyć List<T> albo jakiejś konkretnej implementacji np. ArrayList<T>. A teraz wyobraź sobie że wpadłeś na to że LinkedList<T> jest lepsze od ArrayList<T> bo często dodajesz nowe elementy i array list się ślimaczy. W pierwszym przypadku zmieniasz tylko miejsce tworzenia listy. W drugim przypadku masz setki miejsc do zmienienia...
Zauważ że często te dziedziczące po sobie klasy nie mają wcale dodatkowych metod, tylko inną implementację! Poczytaj co to wzorzec Strategy (strategia) i wzorzec Template Method (metoda szablonowa).

Używając wszędzie interfejsów zamiast konkretnych implementacji klas jesteś niezależny od tych implementacji i mozesz je podmienić w dowolnej chwili.

Ok, dzięki wam Panowie za pomoc. Tylko jeszcze jedna nieścisłość, bo...
skoro zarówno ArrayList oraz LinkedList dziedziczą z tej samej klasy bazowej i różnią się jedynie implementacją, czyli rozumiem różnym sposobem (algorytmami) wstawiania, wybierania, usuwania... to dlaczego nie można szybko/bezpośrednio przekonwertować ArrayList na LinkedList i vice versa ?

0

Ha, akurat w tym przypadku te klasy nie mają takich samych funkcjonalności. LinkedList implementuje Deque, a ArrayList nie.

dlaczego nie można szybko/bezpośrednio przekonwertować ArrayList na LinkedList i vice versa

Bo nie zawsze jedna i ta sama implementacja jest optymalna?

Korzystanie z interfejsu jest przecież wygodniejsze. Po co zmieniać ArrayList na LinkedList w X miejscach w kodzie, zamiast zmieniać new ArrayList() na new LinkedList() w jednym miejscu?

Poza tym, co jeśli np jedna biblioteka używałaby ArrayList, a druga LinkedList, a ty chciałbyś używać obu bibliotek naraz? Konwertowałbyś za każdym razem te listy do odpowiedniej postaci? Zakładając oczywiście, że autorzy bibliotek kierowaliby się twoim pomysłem.

0
Wibowit napisał(a):

[...]
Korzystanie z interfejsu jest przecież wygodniejsze. Po co zmieniać ArrayList na LinkedList w X miejscach w kodzie, zamiast zmieniać new ArrayList() na new LinkedList() w jednym miejscu?
[...]

A co w przypadku własnych klas które implementują dodatkowe metody i w przypadku rzutowania w górę je tracę ?

0

No wiadomo, każdy obiekt można zrzutować do java.lang.Object i stracić wszystkie funkcjonalności. Nie o to chodzi - chodzi o to, żeby rzutować na coś najbardziej ogólnego, ale takiego co posiada wymagane przez nas funkcjonalności.

Jeśli chodzi o mapy czy listy to naprawdę sporadycznie mam potrzebę użycia czegoś bardziej specyficznego niż java.util.List czy java.util.Map.

Poczytaj: http://en.wikipedia.org/wiki/Interface_segregation_principle

0
Wibowit napisał(a):

No wiadomo, każdy obiekt można zrzutować do java.lang.Object i stracić wszystkie funkcjonalności. Nie o to chodzi - chodzi o to, żeby rzutować na coś najbardziej ogólnego, ale takiego co posiada wymagane przez nas funkcjonalności.

Jeśli chodzi o mapy czy listy to naprawdę sporadycznie mam potrzebę użycia czegoś bardziej specyficznego niż java.util.List czy java.util.Map.

Poczytaj: http://en.wikipedia.org/wiki/Interface_segregation_principle

dzięki

0

Effective Java 2nd Edition, Item 52: Refer to objects by their interfaces.
Polecam przeczytać ten rozdział, znajdziesz tam dwie strony argumentacji dlaczego lepiej używać ogólnych interfejsów.

0
adammmm napisał(a):

Effective Java 2nd Edition, Item 52: Refer to objects by their interfaces.
Polecam przeczytać ten rozdział, znajdziesz tam dwie strony argumentacji dlaczego lepiej używać ogólnych interfejsów.

miodzio, dzięki za cynk ;-)

0

Jest jeszcze jedna rzecz, dotycząca klas. Czasem używając klasy pokazujesz coś więcej, niż tylko interfejs. Dajesz jakieś mocniejsze obietnice, niż to się może wydawać. Bo każda implementacja ma swoje "zachowanie". A nie tylko implementowany interfejs. Czasem problem wyskakuje nawet wtedy, gdy nie chcesz zmienić typów, a tylko "cokolwiek poprawić". Podam przykład, akurat z realnego życia, żeby wyjaśnić, co mam na myśli. I też map dotyczy.

Oprócz HashMap istnieje również LinkedHashMap. Klasa LinkedHashMap nie dodaje nowych metod publicznych, składa jednak obietnicę: zachowuje kolejność wstawiania elementów. Pisząc funkcję foo dajesz znać, że używający mogą polegać na kolejności elementów w mapie.

 
LinkedHashMap<?,?> foo() {
    LinkedHashMap result = ...
    // ...
    return result;
}

I nagle się okazuje, że zmieniając cokolwiek w funkcji foo, robiąc po prostu coś w innej kolejności, rozsypujesz program w innych miejscach. Gdyby funkcja zwracała po prostu Map, to nikomu by nie przyszło do głowy polegać na kolejności kluczy. Wypuszczając w świat LinkedHashMap dałeś taką obietnicę, że teraz nawet ciężko jest namierzyć fragmenty programu, które mogą się zepsuć. W żaden sposób kompilator nie pomoże, bo ta obietnica ("zachowuje kolejność kluczy") nie jest formalnie wcale opisana.

Być może wcale tak silnych gwarancji nie chciałeś dawać. Być może użyłeś tej klasy z zupełnie innego powodu (np użyłeś subklasy LinkedHashMap bo chciałeś pilnować rozmiaru mapy za pomocą chronionej metody removeEldestEntry). Zdradzając typ nie udostępniłeś nowych metod. A jednak udostępniłeś informację o "zwyczajach" - taki "nieformalny wyciek informacji" potwornie związuje Ci ręce, pozwala czasem robić klientowi rzeczy, których nawet się nie spodziewałeś. Dając tylko interfejs masz pewność, że nikt uczciwy nie będzie polegał na żadnym "zwyczaju" / "szczególe implementacyjnym".

0

Uzupełniając kolegę powyżej jest to trochę podobnie jak z hermetyzacją: Dajesz tak mały interfejs (klasę) i tak mało informacji jak jest to możliwe podobnie jak ograniczasz ilość metod publicznych i chronionych w interfejsie/klasie do niezbędnego minimum. Zmieniając później abstrakcyjny interfejs (co może oznaczać też klasę konkretną) na bardziej wyszukany lub dający więcej niż wcześniej, niczego nie psujesz bo kod bazujący na "poprzedniej wersji" nie wykorzysta już tych nowych możliwości po zmianie (chyba, że zostanie napisana jego nowsza wersja bazująca na nowych możliwościach).

Później nauczysz się tworzyć samemu interfejsy/klasy abstrakcyjne gdy mając początkowo jedną klasę będziesz potrzebował zróżnicowania jej do różnych potrzeb. Mi często przydaje się taka konstrukcja, która powstaje właśnie z jednej klasy (tu Medal i Zgryz) z nagłymi potrzebami uabstrakcyjnienia jej:

interface Medal //może być private/protected jeżeli interfejs jest już wewnątrz klasy (również abstrakcyjnej)
{
	enum Typ{ ZŁOTY, SREBRNY, BRĄZOWY; /*...*/ }

	Zgryz przygryzieniePrzez(Sportowiec naPodium);

	final class Akcja
	{
		public static Medal twórz(Typ typ) { /*...*/ }

		public static Medal przypnij(Sportowiec medalista) { /*...*/ } //domyślnie static
	}

	//...
	final class Złoty implements Medal //domyślnie static
	{
		@Override Zgryz przygryzieniePrzez(Sportowiec naPodium) { /*...*/ } //wyraźny ślad zębów
		//...
	}
	final class Srebrny implements Medal //domyślnie static
	{
		@Override Zgryz przygryzieniePrzez(Sportowiec naPodium) { /*...*/ } //mniej wyraźny ślad zębów
		//...
	}
	final class Brązowy implements Medal //domyślnie static
	{
		@Override Zgryz przygryzieniePrzez(Sportowiec naPodium) { /*...*/ } //niewyraźny ślad zębów
		//...
	}
}
//...
interface Zgryz
{
	//... itp.
}

Dzięki czemuś podobnemu kod kliencki w ogóle się nie zmienia, a jednocześnie różnicujesz swoje typy danych.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.