Wątek przeniesiony 2017-08-20 23:32 z Java przez Shalom.

Dziedziczenie pól to zło? Dlaczego?

0

Hej!

W swoim artykule nt. wykorzystywania UID jako id obiektu https://4programmers.net/Forum/1316159 zastosowałam klasę po której miałyby dziedziczyć wszystkie encje JPA ukazaną poniżej:

import lombok.Getter;
import lombok.ToString;
 
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;
 
@Getter
@ToString
@MappedSuperclass
public abstract class SimpleEntity implements Serializable {
 
    @Id
    @Column(updatable = false, nullable = false, unique = true)
    private final UUID id = UUID.randomUUID();
 
    @Version
    private Integer version;
 
    private LocalDateTime creationDate;
 
    private LocalDateTime modificationDate;
 
    @PrePersist
    private void prePersist() {
        modificationDate = creationDate = LocalDateTime.now();
    }
 
    @PreUpdate
    private void preUpdate() {
        modificationDate = LocalDateTime.now();
    }
 
    @Override
    public boolean equals(Object that) {
        return this == that || that instanceof SimpleEntity
                && Objects.equals(id, ((SimpleEntity) that).id);
    }
 
    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }
}

I jest to ładne, cwane i zapewnia nam ładne ID , które wyjaśniłam w podlinkowanym wyżej artykule. Tymczasem dzisiaj natknęłam się na taki film:

w którym to Pan mówi dziedziczyć należy zachowania klas a straszną głupotą jest dziedziczenie pól. dziedziczenie nie powstało po to żeby dziedziczyć pola. zachowania wspólne się dziedziczy a na pewno nie pola . Więc w takim razie dlaczego nie wolno dziedziczyć pól i jakie minusy ma dziedziczenie pól jak w przykładzie podanym przeze mnie wyżej (klasa SimpleEntity). Pytam, bo odkryłam nową rzecz i całkowicie tego nie rozumiem dlaczego dziedziczenie pól jest be? Czy ten gość przesadza czy ma rację?

0

Czytałaś stacka na ten temat ;p ?

4

nie bo 4programmers > stackvoerflow

0

Wtf? Przecież dziedziczenie powstało właśnie po to żeby wspólne POLA mieć w wyższej warstwie abstrakcji xd prostokąt ma obwód? Ma. Koło ma obwód? Ma. No to wniosek - "obwód" to pole klasy Figura, szkolny przykład

3

Wtf? Przecież dziedziczenie powstało właśnie po to żeby wspólne POLA mieć w wyższej warstwie abstrakcji xd prostokąt ma obwód? Ma. Koło ma obwód? Ma. No to wniosek - "obwód" to pole klasy Figura, szkolny przykład

Bez sensu i po co komu pole z obwodem prostokąta lub kwadratu, będziesz podawał obwód przez konstruktor i na jego podstawę obliczał boki ? xD

1

Po pierwsze to w tej super klasie pola powinny być chyba protected.

Dziedziczenie to zło.

  1. Narusza hermetyzację.
  2. Zmusza autora do przeczytania całego kodu klasy bazowej.
  3. Jest bardzo sztywną relacją - wykluczającą dynamiczne zmiany.
  4. Przy dziedziczeniu bardzo trudno uniknąć złamania zasady Liskov.
  5. Wprowadza dodatkowe komplikacje przy metodach equals / hashCode. Komplikuje kolejność inicjalizacji pól klasy itp.

Nie potrafię znaleźć jednak argumentu, że dziedziczenie pól jest złe.
Przeglądam Internety i nic nie mogę znaleźć, choć teza się czasem przewija.
Na chłopski rozum co mi przychodzi to być może następujące uogólnienie:

Dziedziczenie jest złe. --> Tylko dziedziczenie zachowań (interfejsów) ma sens. --> Dziedziczenie pól w takim razie nie ma sensu.

EDIT: Może ktoś bardziej doświadczony się odezwie :) Chociaż oni podobno już tylko funkcyjnie w Scali jadą :P

0

Nie chce mi się oglądać prawie 2 godzinnego nagrania, a nie widzę tu w wątku podanej wprost argumentacji tłumaczącej dlaczego dziedziczenie pól to zło.

Z polami w Javie jest taki problem, że nie da się nadpisać pola z klasy nadrzędnej - można co najwyżej stworzyć pole o takiej samej nazwie i mieć potem problemy z przesłanianiem zmiennych.

Przykład:
http://ideone.com/kDycY9

import java.util.*;
import java.lang.*;
import java.io.*;
import static java.lang.System.out;
 
class Ideone
{
	static class Base {
		int i;
	}
 
	static class Derived extends Base {
		int i;
	}
 
 
	public static void main (String[] args) throws java.lang.Exception
	{
		Derived derived = new Derived();
		Base base = derived;
		derived.i = 5;
		base.i = 6;
		out.println(derived.i);
		out.println(base.i);
		out.println(((Base) derived).i);
		((Base) derived).i = 4;
		derived.i = 3;
		out.println(derived.i);
		out.println(base.i);
		out.println(((Base) derived).i);
	}
}

Scala nie utrudnia dziedziczenia pól. Wręcz przeciwnie - pozwala na ich wielodziedziczenie (z traitów). Podejście Scali do pól jest jednak inne niż w Javie, gdyż zdefiniowanie pola w Scali definiuje z automatu gettera (i potencjalnie settera), a dostęp do pól z klas Scalowych odbywa się tylko z użyciem tych automatycznie stworzonych metod dostępowych.

Konkretniej to jest tak:

class KlasaBazowa {
  // tutaj stworzone zostanie pole 'stała', ale również metoda 'stała()' która zwraca zawartość pola 'stała'
  val stała = 5;

  // tutaj stworzone zostanie pole 'zmienna', ale również metoda 'zmienna()' zwracająca zawartość pola 'zmienna' oraz metoda 'zmienna_=(zmienna: Int)' służąca do zmiany zawartości pola
  var zmienna = 5;
}

class KlasaDziedzicząca extends KlasaBazowa {
  // tutaj zostanie stworzone nowe pole 'stała', a metoda 'stała()' zostanie nadpisana nową
  override val stała = 6;

  // tutaj zostanie stworzone nowe pole 'zmienna', a metody 'zmienna()' i 'zmienna_=(zmienna: Int)' zostaną nadpisane nowymi
  override var zmienna = 6;
}

class KlasaUżywająca {
  val instancja = new KlasaDziedzicząca();

  // aktualizacja pola 'zmienna' odbywa się przez wywołanie metody 'zmienna_=(8)'
  instancja.zmienna = 8;

  // użycie pola 'zmienna' odbywa się poprzez wywołanie metody 'zmienna()'
  println(instancja.zmienna);
}

Scalowe pola mają więc semantykę zbliżoną do metod wirtualnych. Trzeba pamiętać jednak o kolejności inicjalizacji (która w Scali jak i w Javie odbywa się od klas najogólniejszych, czyli najwyżej w hierarchi, aż do tych najbardziej specyficznych, czyli najniżej w hierarchii), którą da się zaobserwować (zwykle w wyniku błędu w kodzie, ale sporadycznie może to być robione świadomie).

Podobieństwo pól do metod w Scali jest na tyle duże, że można zaimplementować bezargumentową metodę abstrakcyjną za pomocą pola, np:

abstract class KlasaBazowa {
  def abstrakcyjneCóś: Int
}

class KlasaDziedzicząca extends KlasaBazowa {
  val abstrakcyjneCóś = 5
}

Powyższy trik to częsty idiom w Scali. Z kolei nadpisywanie pól w Scali jest ryzykowne (ze względu na wspomnianą kolejność inicjalizacji), więc jest niezbyt częste, a ja sam staram się tego unikać.

1

Nie mogę wiedzieć na pewno, co akurat ten gość miał na myśli. Ale moim zdaniem chodzi tu raczej o pola zmienne, niż niezmienne.

Innymi słowy o pola, w których klasa trzyma sobie "notatki" na temat swojego własnego stanu, potrzebne do poprawnego działania.

Te "notatki", nawet w rodzinie klas, powinny raczej pozostać rzeczą prywatną.

W przeciwnym wypadku rośnie ryzyko, że klasa pochodna niechcący popsuje coś, co już raz dobrze działało u rodzica - albo odwrotnie, zmiany w klasie bazowej zepsują działanie klas pochodnych. Taki szczególny przypadek fragile base class.

Enkapsulacja obowiązuje nawet w hierarchii dziedziczenia. Szczegóły implementacji nie powinny przeciekać, a dziedziczenie pól (zmiennych) tym właśnie grozi.

5

Ja rozumiem że chodzi tutaj o to, żeby nie robić pól typu package/protected, tzn takich bezpośrednio dostępnych z klasy podrzędnej i ma to sens. Klasa dziedzicząca nie powinna zależeć od wewnętrznej implementacji klasy, z której dziedziczymy. To sie nazywa tzw "Fragile Base Class Problem", tzn nagle kosmetyczna zmiana w klasie bazowej staje sie niemożliwa bo wysadza całą hierarchie, bo gdzieśtam zależymy od szczegółów klasy bazowej.
Nie różni sie to specjalnie od zwykłej idei hermetyzacji -> powinniśmy uzywać obiektu tylko za pomocą jego interfejsu i absolutnie nie za pomocą grzebania w jego wewnętrznym stanie. I tak samo kiedy dziedziczymy -> w klasie pochodnej używamy metod klasy bazowej a nie odnosimy się bezpośrednio do pól.

3

Według mnie nie ma jedynej słusznej odpowiedzi, czy jest złe czy nie. Po prostu to zależy od kontekstu i od tego jakie opcje mamy do rozwiązania konkretnego problemu. Stwierdzenie, że coś jest złe/dobre, to ocenianie, a do tego trzeba mieć jakieś kryteria, a nie po prostu przekonanie.

Przed wprowadzeniem widoczności pola w podklasie trzeba się zastanowić jakie będą konsekwencje i czy są akceptowalne. Jakie kryteria przyjąć? Np. zasady SOLID i ilość złamanych zasad. Jeśli rozwiązanie#1 nie łamie zasad SOLID, a rozwiązanie#2 łamie 1 czy 2 zasady, to z perspektywy projektowej warto wybrać 1.

Tworząc komercyjnie, pracujemy przy dodatkowych ograniczeniach typu czas/pieniądze/istniejące interfejsy/etc. Nie zawsze możemy sobie pozwolić na rozwiązanie#1, które jest lepsze jeśli chodzi o oprogramowanie, ale wymaga większych nakładów czasowych i finansowych.

0

Zgadzam się z @yarel
Są sytuacje, w których dziedziczy się pola.

Ale. Na pewno lepszym pomysłem jest dziedziczenie tylko metod, ponieważ pewne mechanizmy mogą kiedyś ulec zmianie w klasie bazowej. I wtedy z dziedziczeniem pola może być taka sama sytuacja jak z upublicznieniem pola. Czyli klasa potomna nagle zacznie robić coś, czego robić nie powinna. W związku z tym zawsze lepiej od początku tworzyć klasę bazową tak, żeby pola nie były dziedziczone. Natomiast są sytuacje, w których można to zrobić i pewnie nigdy niczego to nie zaburzy.

1

@Juhas: o_O pola dziedziczy się ZAWSZE. Jedyne co można ograniczyć to ich WIDOCZNOŚĆ.

4
Shalom napisał(a):

@Juhas: o_O pola dziedziczy się ZAWSZE. Jedyne co można ograniczyć to ich WIDOCZNOŚĆ.

Oficjalna dokumentacja stosuje to praktyczne rozumienie "dziedziczenia prywatnych pól".

http://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html -

A subclass does not inherit the private members of its parent class.

i to w więcej niż jednym miejscu:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.2

Members of a class that are declared private are not inherited by subclasses of that class.

Oczywiście:

if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass

ale to inna sprawa.

Uważam zatem że nie masz racji, zgodnie z https://stackoverflow.com/a/4716335/168719

Dziedziczenie jest po prostu rozumiane jako "oddanie do bezpośredniej dyspozycji".

2

@V-2 to jest trochę kwestia kosmetyki, bo chodzi tutaj o rozróżnienie między obiektem a klasą. W tym ujęciu klasa pochodna faktycznie nie dziedziczy prywatnych pól klasy bazowej, ale obiekt tejże klasy jak najbardziej, co zresztą wynika wprost z Zasady Podstawiania Liskov.

4

Tak to tłumaczy odpowiedź na SO... Ja i nad tym bym się zastanawiał, czy w ogóle można mówić, że obiekt (jako instancja) cokolwiek dziedziczy; czy to nie jest tylko skrót myślowy na określenie tego, że jego klasa coś dziedziczy. Ale to są oczywiście takie dywagacje semantyczno-definicyjne. Każdy powinien rozumieć, że do prywatnych pól rodzica nie ma z poziomu kodu dostępu... ale też nie dziwić się, że zobaczy je w debuggerze jak sobie rozwinie zawartość obiektu-dziecka.

0

Też słyszałem, że nie powinno dziedziczyć się pól - nie znam jednak argumentów za tym przemawiających. Czy ma ktoś linka do podobnej dyskusji na stackoverflow? Może tam będą jakieś konkretne argumenty?

0
wiewiorek napisał(a):

Też słyszałem, że nie powinno dziedziczyć się pól - nie znam jednak argumentów za tym przemawiających.

Na tych polach przechowywany jest stan obiektu. Stan ten jest wewnętrzną sprawą obiektu i może być przez ten obiekt modyfikowany w odpowiedzi na komunikaty z "zewnątrz".

Jak dajemy dostęp do pól obiektu innym, to tracimy kontrolę nad tym, gdzie ten stan jest zarządzany (tzn. czy w klasie bazowej, czy może w podklasie, czy trochę tu, trochę tam). Utrata kontroli może prowadzić do sytuacji, gdy stan obiektu ulegnie "zepsuciu" i zmieni się zachowanie operacji.

2

Ja tam uwielbiam, gdy pola klasy bazowej są prywatne, nie ma dostępu do żadnych zależności, w dziecku niczego nie da się normalnie nadpisać, i trzeba jechać refleksją albo kopiować całą klasę po to tylko, żeby zmienić jedną małą rzecz.
Naprawdę, myślę wtedy, że autor jest nieskończenie wybitnym programistą. ;)

0

@somekind Twój post dowodzi, że dziedziczenie powinno być zakazane.

Jak już pisałem wcześniej, gdy dziedziczysz po klasie to MUSISZ ją przeczytać i zrozumieć (wujek Bob to dowodzi). Dlatego gdy już zrozumiesz klasę bazową to albo nie dotykasz tego pola private i wiesz dlaczego, albo zmieniasz na protected.

Pomysł z refleksją to płacz pokoleń, które po Tobie to będą debugować.

1
karolinaa napisał(a):

dlaczego dziedziczenie pól jest be? Czy ten gość przesadza czy ma rację?

nie da sie nie dziedziczyc pol :) dziedziczenie jest ok o ile jest wynikiem przemyslen a nie lenistwa

nie100sowny napisał(a):
  1. Narusza hermetyzację.

nie powinno tak byc, wystarczy ze metody w klasie pochodnej nie maja efektow ubocznych i tyle :)

  1. Zmusza autora do przeczytania całego kodu klasy bazowej.

to oznacza ze dziedziczenie nie powinno wchodzic w gre, tzn klasa bazowa nie zostala stworzona z zalozeniem ze bedzie z niej dziedziczone

  1. Jest bardzo sztywną relacją - wykluczającą dynamiczne zmiany.

dlatego wlasnie imo powinno sie oznaczac wszystkie klasy jako final, chyba ze tworzymy klase celowo pod katem dziedziczenia z niej

  1. Przy dziedziczeniu bardzo trudno uniknąć złamania zasady Liskov.

racja, dlatego wlasnie dziedziczenie powinno byc raczej ostatecznoscia niz domyslnym rozwiazaniem

  1. Wprowadza dodatkowe komplikacje przy metodach equals / hashCode. Komplikuje kolejność inicjalizacji pól klasy itp.

imo jesli klasa wymaga equals/hashCode to znaczy ze powinna byc final i nie miec extends

Nie potrafię znaleźć jednak argumentu, że dziedziczenie pól jest złe.

ehh, zawsze dziedziczy sie pola. zla jest ich modyfikacja w klasie pochodnej.

Dziedziczenie jest złe. --> Tylko dziedziczenie zachowań (interfejsów) ma sens

to jest dobra regula dla newbies ;) po prostu trzeba zachowac zdrowy rozsadek i tyle

somekind napisał(a):

Ja tam uwielbiam, gdy pola klasy bazowej są prywatne, nie ma dostępu do żadnych zależności, w dziecku niczego nie da się normalnie nadpisać, i trzeba jechać refleksją albo kopiować całą klasę po to tylko, żeby zmienić jedną małą rzecz.
Naprawdę, myślę wtedy, że autor jest nieskończenie wybitnym programistą. ;)

zawsze mozesz zrobic w klasie bazowej sprawdzic wlasciwy typ this i zmienic spersonalizowac zachowanie pod katem dziecka ;)

Jak już pisałem wcześniej, gdy dziedziczysz po klasie to MUSISZ ją przeczytać i zrozumieć (wujek Bob to dowodzi).

bzdura

2
nie100sowny napisał(a):

Jak już pisałem wcześniej, gdy dziedziczysz po klasie to MUSISZ ją przeczytać i zrozumieć (wujek Bob to dowodzi). Dlatego gdy już zrozumiesz klasę bazową to albo nie dotykasz tego pola private i wiesz dlaczego, albo zmieniasz na protected.

Nie zmienię modyfikatora pola na protected w zewnętrznej bibliotece, chyba że skopiuję kod albo zrobię forka. Pierwsze jest nieeleganckie, drugie jest nadmiernym wysiłkiem. Dlatego tak lubię programistów, którzy piszą w ten sposób.

Jak dla mnie są dwie możliwości na poziomie projektowym:

  • albo pozwalamy dziedziczyć ze swojej klasy, i wtedy wszystkie pola ustawiamy od razu jako protected, albo dajemy jakiś inny dostęp do nich (np. przez metodę).
  • albo nie wspieramy dziedziczenia z klasy, wtedy klasę plombujemy i nikt z niej nie dziedziczy.

Pomysł z refleksją to płacz pokoleń, które po Tobie to będą debugować.

Jeśli pomyślałeś, że ja chcę używać refleksji do kodu, który posiadam, to tak naprawdę nie pomyślałeś.

0
katelx napisał(a):

dlatego wlasnie imo powinno sie oznaczac wszystkie klasy jako final, chyba ze tworzymy klase celowo pod katem dziedziczenia z niej

Według mnie też klasy, które nie są nastawione pod dziedziczenie powinny być final i według najlepszych hakerek też.
Tak samo bodajże Joshua Blocha pisał o tym w Effective Java 2.

katelx napisał(a):

imo jesli klasa wymaga equals/hashCode to znaczy ze powinna byc final i nie miec extends

@katelx aaa klasa SimpleEntity z przykładu z pierwszej strony mojego wątku?

0
katelx napisał(a):
  1. Wprowadza dodatkowe komplikacje przy metodach equals / hashCode. Komplikuje kolejność inicjalizacji pól klasy itp.

imo jesli klasa wymaga equals/hashCode to znaczy ze powinna byc final i nie miec extends

np. java.lang.Object ? ;)

2
somekind napisał(a):

Nie zmienię modyfikatora pola na protected w zewnętrznej bibliotece, chyba że skopiuję kod albo zrobię forka. Pierwsze jest nieeleganckie, drugie jest nadmiernym
wysiłkiem. Dlatego tak lubię programistów, którzy piszą w ten sposób.

Taka prostacka reguła mówi, że jeśli zewnętrzna biblioteka wymaga abyś używał dziedziczenia po zdefiniowanych w niej klasach - to nie jest już to bilblioteka, ale framework.
I dlatego też frameworki są do d...y, a bilblioteki rządzą :-)

1

Osobiście bardzo lubię podejście z Pythona, gdzie nie ma prywatnych pól, a jedynie oznacza się je przez nazewnictwo. Prywatne pola i klasy final fajnie brzmią na kartce, ale w rzeczywistości i tak kończy się to hakami przy użyciu refleksji. Jak ktoś chce mieć hermetyzację, to niech realizuje ją przez interfejsy, a nie przez ograniczanie wszystkiego; dzięki temu gdy ja zacznę dobierać się do jakichś wewnętrznych składowych (co raczej w końcu nastąpi), to przy nowej wersji biblioteki i zmianie bebechów mój kod przynajmniej wywali się na etapie kompilacji, a nie w czasie wykonania, bo refleksja nie znalazła pól. No ale to korporacyjny kod, w nim różnymi hakami trzeba sobie radzić.

Dygresja: kiedyś mieliśmy problem z Microsoft Workflow Foundation, gdzieś w jakiejś klasie było prywatne pole typu Dictionary (konkretna implementacja, a nie interfejs). Okazało się, że WF źle synchronizował dostęp do tego pola i mieliśmy przez to sporo problemów. Niestety nie dało się tego naprawić przez podmianę słownika na synchronizowany, bo pole miało zły typ, żadna refleksja tu nie pomogła, a MS już nie rozwijał tego produktu i nie było szans na normalną poprawkę. Ostatecznie musieliśmy zakładać blokady kilka poziomów wyżej, dzięki czemu kod zaczął działać poprawnie, ale wydajność bardzo spadła.

0
jarekr000000 napisał(a):

Taka prostacka reguła mówi, że jeśli zewnętrzna biblioteka wymaga abyś używał dziedziczenia po zdefiniowanych w niej klasach - to nie jest już to bilblioteka, ale framework.

Owszem, we frameworkach to konieczność, żeby w ogóle cokolwiek w oparciu o framework zrobić. Tylko tu chodzi o coś innego - potrzebę użycia kawałka zewnętrznego kodu w inny sposób niż zaimplementowali jego twórcy. Niezły przykład tego podał @Afish wyżej.

I dlatego też frameworki są do d...y, a bilblioteki rządzą :-)

Frameworki trzeba umieć pisać. Framework powinien być nakładką dodającą funkcje, a nie fasadą ukrywającą wszystko to, z czego sam korzysta.

2
Afish napisał(a):

Osobiście bardzo lubię podejście z Pythona, gdzie nie ma prywatnych pól, a jedynie oznacza się je przez nazewnictwo. Prywatne pola i klasy final fajnie brzmią na kartce, ale w rzeczywistości i tak kończy się to hakami przy użyciu refleksji.

Ja nie wiem, co wy robicie, że to dla was chleb powszedni, aby rozłamywać cudze abstrakcje refleksją. Już jakiś czas w tym zawodzie robię - i nie na kartce - a w zasadzie nie jestem w stanie sobie przypomnieć takiego przypadku, żebym stanął przed taką koniecznością.

Oczywiście nie twierdzę, że nie mogą się zdarzyć, jak ten opisany przez ciebie problem z WF - w porządku. (Zaryzykowałbym stwierdzenie, że jest to w zasadzie zasłużona boska kara za używanie Workflow Foundation, Sharepointów itp. grzesznych molochów).

Natomiast tak czy siak są to raczej sytuacje skrajne i wysnuwać z nich jakieś ogólne architektoniczne konkluzje, że najlepiej robić wszystko protected - a może i na zaś public - to podobna paranoja, jak u mojej starej ciotki, co wszędzie nosi chlebak z latarką kieszonkową i zapasem leków na wypadek ataku terrorystycznego. (Tak, wiem, że pewnego strasznego dnia wyjdzie, że to ona miała rację).

0
V-2 napisał(a):

Już jakiś czas w tym zawodzie robię - i nie na kartce - a w zasadzie nie jestem w stanie sobie przypomnieć takiego przypadku, żebym stanął przed taką koniecznością.

Życie. Jeden przez całą karierę ani razu nie zrobi zrzutu pamięci, a inny codziennie będzie tak musiał debugować, jeden użyje refleksji raz do roku, a inny będzie musiał haczyć na porządku dziennym.

0
Afish napisał(a):
V-2 napisał(a):

Już jakiś czas w tym zawodzie robię - i nie na kartce - a w zasadzie nie jestem w stanie sobie przypomnieć takiego przypadku, żebym stanął przed taką koniecznością.

Życie. Jeden przez całą karierę ani razu nie zrobi zrzutu pamięci, a inny codziennie będzie tak musiał debugować, jeden użyje refleksji raz do roku, a inny będzie musiał haczyć na porządku dziennym.

Oczywiście. To zależy od wybranej technologii, umiejętności, charakteru i jakości projektu itd. niezliczonych czynników. No i właśnie dlatego nie powinieneś z miejsca zakładać, że punkty widzenia niepotwierdzone przez twoje prywatne doświadczenie "fajnie brzmią na kartce" - czyli że mają zastosowanie już wyłącznie w teorii. Bo mogą świetnie sprawdzać się w praktyce całkiem sporej grupy ludzi; może nawet większej liczebnie.

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