Dziedziczenie po stringu / Stringu

0

Moje myślenie jest w obszarze Javy i C#. Jak wiemy, w obu stringi są klasami finalnymi i formalnie nie podlegają dziedziczeniu.

Jak myśleć o takich bytach, które są nadal stringiem/Stringiem ale prezentują inne funkcjonalności.
na przykład taki hipotetyczny kod:

class Pesel : String {
}

Czy jest Peselem? owszem, jest. a czy jest Stringiem? tak

co jest jakościowo różne od uprawianej kompozycji

class Pesel {
 String pesel;
}

czy to samo

class Email : String {
}

vs

class Email {
  string email;
}

czyli dalej jest kompatybilna w kontekstach, gdzie jest oczekiwany string, ale jest zarazem czymś jakościowo innym.

Co myślicie?

1

A jak widzisz to przy ORM? Wychodzi na to, że musiałby być typ danych dla każdej tabeli albo kolumny. Przy jakimkolwiek większym systemie byłoby kilkadziesiąt a nawet kilkaset typów.

0
PerlMonk napisał(a):

A jak widzisz to przy ORM? Wychodzi na to, że musiałby być typ danych dla każdej tabeli albo kolumny. Przy jakimkolwiek większym systemie byłoby kilkadziesiąt a nawet kilkaset typów.

Dobre pytanie ...
więc o ile nie będzie specyficznego mapingu, by zadziałał defaultowy maping na string'a - wszak to nadal string.
Wartość dodatkowego typu by była mniej więcej taka jak adnotacji

@Legth(13)
String pesel;

na podstawie czegoś w dziedziczeniu, co tą 13kę poda
:)

proszę następne

2

A jak widzisz to przy ORM?

Możesz a nawet powinieneś mieć oddzielny model domenowy od modelu bazodanowego, no chyba że masz aplikacje która jest czystym CRUDem. A dodatkowe typy sa na propsie.

Co myślicie?

Myślę że nie powinno się dziedziczyć, nawet jak możesz tylko zastosować kompozycję.
W Kotlinie/Scali można nawet pojechać bardziej hardcorowo:

sealed class Email(protected val value: String) {

    companion object {
        fun of(value: String): Email {
            return if (isValidEmail(value)) ValidEmail(value)
            else InvalidEmail(value)
        }
    }

    abstract fun isValid(): Boolean

    abstract fun requiredValidValue(): String

    fun value(): String {
        return value
    }

    class ValidEmail(value: String) : Email(value) {

        override fun isValid(): Boolean {
            return true
        }

        override fun requiredValidValue(): String {
            return value
        }
    }

    class InvalidEmail(value: String) : Email(value) {

        override fun isValid(): Boolean {
            return false
        }

        override fun requiredValidValue(): String {
            throw IllegalStateException("The value is not valid")
        }
    }

    override fun equals(other: Any?): Boolean {
        return when (other) {
            other === this -> true
            other === null -> false
            other is Email -> (other as Email).value == value
            else -> false
        }
    }

    override fun hashCode(): Int {
        return value.hashCode()
    }
}

fun isValidEmail(email: String): Boolean {
    return validator.isValid(email) && email.length <= MAX_EMAIL_LENGTH
}

Chociaż tu chyba powinienem poprawić design :(

5

Moim zdaniem absolutnie nie. Pesel NIE JEST stringiem. Pobieranie długości peselu albo np. stripowanie białych znaków z peselu z punktu widzenia domeny nie ma żadego sensu. Podobnie jeśli chodzi o emaila. Moim zdaniem kluczowe jest myśleć o zachowaniu i zastosowaniu obiektu a nie o jego strukturze. To ze dwie klasy mają takie same pola NIE oznacza że istnieje między nimi jakiekolwiek powiązanie.
Weźmy np. klase Punkt2d i Wektor2d. Niby obie mają współrzędne, ale jednak to są dwa zupełnie oddzielne byty, o zupełnie innym zachowaniu i innym interfejsie i bez sensu byłoby np. zrobić zeby Wektor dziedziczył z Punktu tylko dlatego ze i tu i tu masz x i y.

1

maksymalna (w peselu jedyna) długość

@AnyKtokolwiek
pesel ma nie tylko maksymalną liczbę cyfr, ale ma bardziej złożone reguły walidacji. Dodałbym metodę isValid i na podstawie niej zwracał albo ValidPesel albo InvalidPesel. W bazie danych najwyzej trzymałbym tylko constraina na ilość znaków

A dziedziczenia na ogół powinno się unikać, zwłaszcza w przypadku gdy to model danych.

0
scibi92 napisał(a):

maksymalna (w peselu jedyna) długość

@AnyKtokolwiek
pesel ma nie tylko maksymalną liczbę cyfr, ale ma bardziej złożone reguły walidacji. Dodałbym metodę isValid i na podstawie niej zwracał albo ValidPesel albo InvalidPesel. W bazie danych najwyzej trzymałbym tylko constraina na ilość znaków

A dziedziczenia na ogół powinno się unikać, zwłaszcza w przypadku gdy to model danych.

  1. Skierowanie na model danych nie pojawiło się ode mnie. Zakładając wątek nie było to dla mnie najważniejsze. Raczej klarowność API bez mnożenia mało inteligentnych override jak toString(0 etc
  2. Constrain z czegoś musi powstać
2

Pesel nie jest stringiem i nigdy nie był.
String jest pewną reprezentacją pesela lub sposobem jego przechowywania, tak samo jak long lub char[ ] lub BigInteger.
Jeśli będziesz traktować byt na równi z jego reprezentacją to możesz w którymś momencie uzyskać Brundlefly, co wiadomo jak się kończy.

1

Raczej klarowność API bez mnożenia mało inteligentnych override jak toString()

A czy możesz mieć subpesel? Nie. Czy możesz łączyć pesele? Nie. Więc class "Pesel extends String" nie ma sensu (i nawet sie nie skompiluje)
Czy kwota pieniędzy w rachunku bankowwym może mieć ponad 2 cyfry po przecinku? Nie. Dlatego class Amount extends BigDecimal nie ma sensu (tu się niestety skompiluje :( ).

0

Tak na marginesie ... margines dlatego, że moją intencją byłą zgoda / polemika z zakazem dziedziczenia ...
(oczywiście, że wątek jest typowo "sobotnio-niedzielny")

Zobaczyłem przed oczyma duszy mej takie coś:

public class Pesel implements CharSequence, Comparable<Pesel>, Serializable {

    @MaxLength(13)
    String pesel;

    public Pesel(String pesel) {
        this.pesel = pesel;
    }

    public String getPesel() {
        return pesel;
    }

    @Override
    public int length() {
        return pesel.length();
    }

    @Override
    public char charAt(int index) {
        return pesel.charAt(index);
    }

    @Override
    public CharSequence subSequence(int arg0, int arg1) {
    }

    @Override
    public int compareTo(Pesel arg0) {
    }
}

Jak wasze poczucie ortodoksyjności?
(na marginesie marginesu, CharSequence to niesłusznie niedowartościowany interfejs. W fw który uprawiam, Apache Wicket, chłopcy robią z tego ciekawe zastosowania)

a wracając do sobotniej filozofii: zakaz dziedziczenia to dobrze, czy źle?

0
  1. CharSequence tu raczej nie ma sensu
  2. To @MaxLength(13) też nie. Powinieneś mieć metodę validującą stringową reprezentację peselu i metodę isValidPesel dla obiektu pesel.
2

Jak miałbym już coś takiego robić jako obiekt domenowy to raczej coś w rodzaju:

public final class Pesel implements Comparable<Pesel>, Serializable {

    @MaxLength(13)
    String value;

    public Pesel(String value) {
        this.value = value;
    }

    public Pesel(Pesel source) {
        this.value = source.toString();
    }

    @Override
    public String toString() {
        return value;
    }

    public boolean isValid() {
    //...
    }

    public int getBirthDate() {
    //...
    }

    public Gender getGender() {
    //...
    }

    public boolean isMillennial() {
    //...
    }

    @Override
    public int compareTo(Pesel arg0) {
    }
}

Ale to taka sobotnio-niedzielna propozycja.

1
AnyKtokolwiek napisał(a):

Jak wasze poczucie ortodoksyjności?

Mylisz chyba ortodoksyjność z konserwatyzmem, czyli programowaniem proceduralnym w tym przypadku.

Niby jest tam gdzieś class, ale to wciąż jest Pascal - masz tylko strukturę na dane, a wszystkie operacje muszą być realizowane na zewnątrz. Zamiast kodu, który pokazałeś, to już lepiej mieć po prostu string i na nim operować, bo ta klasa i tak nie robi niczego innego poza byciem opakowaniem na stringa.

Gdyby chcieć programować obiektowo, to klasa powinna zapewniać abstrakcję oraz hermetyzację, czyli być tworem kompletnie modelującym określony byt ze świata, przy jednoczesnym ukryciu nieistotnych szczegółów. Począwszy od usunięcia tego głupiego length. Po co ktoś miałby sprawdzać długość numeru PESEL, skoro z góry wiadomo ile ona wynosi?
Zresztą, @vpiotr pokazał jak to mniej więcej powinno wyglądać.

a wracając do sobotniej filozofii: zakaz dziedziczenia to dobrze, czy źle?

W tym przypadku dziedziczenie nie ma sensu, bo niczego nie daje. W ogólności się przydaje - ale częściej do modelowania przepływu niż danych.

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