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 ?

3

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.

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