Pytanie o poprawną zamianę kodu ze strukturalngo na obiektowy.

0

Cześć wszystkim odwiedzającym ten wątek.

Biorę udział w bootcampie i przybywam do was z pytaniem. A więc do sedna, mam za zadanie zamienić poniższy kod ze strukturalnego na obiektowy.

import java.util.*;
import java.lang.*;
import java.io.*;
 
class Application
{
	public static void main (String[] args) throws java.lang.Exception
	{
		String name = "Adam";
		double age = 40.5;
		double height = 178;
 
		if(name != null) {
			if(age > 30 && height > 160) {
				System.out.println("User is older than 30 and higher then 160cm");
			} else {
				System.out.println("User is younger than 30 or lower than 160cm");
			}
		}
	}
}

Nie wydaje się trudny, ale mam strasznie wymagającego mentora. Na chwilę obecną napisałem coś takiego:

package com;

public class Main {

    public static void main(String[] args) {

        Validator validator = new Validator();
        validator.checkName();
    }
}

class User{

    private String name;
    private double age;
    private double height;

    public User(String name, double age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public double getAge() {
        return age;
    }

    public double getHeight() {
        return height;
    }
}

class Validator{

    User user = new User("Adam", 40.5, 178);

    public void checkName(){
        if (user.getName() != null) {
            checkAge();
        }
    }

    public void checkAge(){

        if (user.getAge() > 30) {
            checkHeight();
        }
        else {
            System.out.println("User is younger than 30 or lower than 160cm");
        }
    }
    public void checkHeight(){
        if (user.getHeight() > 160){
            System.out.println("User is older than 30 and higher then 160cm");
        }
        else {
            System.out.println("User is younger than 30 or lower than 160cm");
        }
    }
}

I teraz moje pytanie, czy Waszym zdaniem to jest poprawnie zmieniony kod? I jeśli nie, to jakie macie sugestie? Dzięki za każdą pomoc!

4

A to Twój nauczyciel nie powinien ci tego sprawdzić i powiedzieć co jest nie tak?

Moim zdaniem nie powinni się bezpośrednio w klasie robić User user = new User("Adam", 40.5, 178); od tego masz np. konstruktor, albo jakaś metode pomocniczą.
checkAge() który sprawdza checkHeight()? Nie no. Te metody powinny być osobno, a potem w jakiejś nowej metodzie robisz sprawdzenie jak w 1 kodzie.

3
  1. Walidator powinien mieć metodę checkX(User user) a nie że hardkodujesz usera tworząc walidator, bo jaki to ma niby sens?
  2. Nie wiem czemu twój walidator dla checkAge woła checkHeight, to przecież bez sensu
5

Po pierwsze, to pytanie czy chcesz zamienić go naprawdę na obiektowy kod; czy tylko na kod który jest podzielony na klasy?

Ponieważ w obiektowym kodzie nie ma miejsca na takie klasy jak Validator (lub klasy których nazwy kończą się na -er albo -or, jak "manager", "controller", etc.). Po drugie, Twój User to zwykły JavaBean który jest zupełnie niepoprawny w obiektowym kodzie.

Prawdziwie obiektowy kod wyglądałby jakoś tak

  • Wersja A, w której opis jest poza kontrolą usera
    class Application {
        public static void main (String[] args) {
            User user = new User("Adam", 40.5, 178);
            if (user.adult()) {
                System.out.println("User is older than 30 and higher then 160cm");
            } else {
                System.out.println("User is younger than 30 or lower than 160cm");
            }
        }
    }
    
    class User {
        private final String name;
        private final double age;
        private final int height;
    
        public boolean adult() {
          return age > 30 && height > 160;
        }
    }
    
  • Wersja B, w której user kontroluje opis
    class Application {
        public static void main (String[] args) {
            User user = new User("Adam", 40.5, 178);
            System.out.println(user.description());
        }
    }
    
    class User {
        private final String name;
        private final double age;
        private final int height;
    
        public boolean description() {
            if (age > 30 && height > 160) {
                return "User is older than 30 and higher then 160cm";
            } else {
                return "User is younger than 30 or lower than 160cm";
            }
        }
    }
    
0

@TomRiddle: to gdzie w takim razie ma się znajdowac kod walidujacy cokolwiek?

3
axelbest napisał(a):

@TomRiddle: to gdzie w takim razie ma się znajdowac kod walidujacy cokolwiek?

"Walidacja" to głupi, niedokładny, nieprecyzyjny, niejednoznaczny, niespecyficzny termin, z rozmytymi granicami podatny na interpretację tego kto ją implementuje. Przykładowo "walidacja" parametrów http to coś zupełnie innego niż "walidacja" pół formularza, a to z kolei jest coś zupełnie innego niż "walidacja" parametrów metody lub zapytania SQL. W najlepszym wypadku słowo "walidacja" to mało znaczące uproszczenie, stosowane pomiędzy osobami które wiedzą o czym mówią i domyślą się szczegółów z kontekstu. Dla ekspertów, to wygodne uproszczenie. Dla początkujących albo średniozaawansowanych to spora zmyłka, mogąca pomieszać idee i koncepty.

Więc odpowiadając na Twoje pytanie

axelbest napisał(a):

@TomRiddle: to gdzie w takim razie ma się znajdowac kod walidujacy cokolwiek?

...najpierw trzeba się zastanowić co tak na prawdę chcesz osiągnąć? I od razu mówię że jeśli pytasz o "walidację czegokolwiek", to najpewniej będą różne rozwiązania, ponieważ mówisz o różnych problemach.

  • Walidacja typu, np spodziewałeś się int, a dostałeś string.
    • jeśli dane pochodzą z interfejsu programistycznego, to gdzieś jest błąd, ponieważ w kodzie obiektowym nie powinno być takiego designu.
    • jeśli dane pochodzą z interfejsu użytkownika, to tak na prawdę problemem jest odpowiednie oprogramowanie zasobów które udostępniasz. Kluczem tutaj jest przestawienie swojego mindsetu, i zrozumienie że (dla przykładu, HTTP), kiedy ktoś requestuje nieistniejący zasób, to 404 które widzisz to nie jest "błąd" albo "nieudana walidacja". To po prostu status code, poprawna odpowiedź na żądanie o takich cechach.
  • Walidacja merytoryczna, np chciałeś wysłać paczkę do Polski, a ktoś podał miasto Frankfurt. Again, nie walidacja tylko poprawne obsłużenie przypadku "miasto do którego wysyłamy paczki" vs "miasta do których nie wysyłamy pączek".
  • Walidacja danych z interfejsu programistycznego, których nie da się wyrazić typem, np ktoś podał ujemny integer, tam gdzie spodziewane są tylko dodanie (a w języku którym piszesz nie ma unsigned int). Albo zrób specjlany obiekt PositiveInt, albo dodaj ifa który rzuca InvalidArgument bezpośrednio w metodzie. Bardziej obiektowy byłby PositiveInt.
  • Walidacja danych formularza w aplikacjach webowych - zupełnie inny problem, wymaga wyświetlania widoku, przesyłu danych, części wspólnej wszystkich pół i pół niespełniających kryteriów, i odpowiedzi z wystarczającymi szczegółami żeby wyrenderować widok który ilustruje niespełnienie kryteriów. Klienci nazywają to "Walidacją", ale tak na prawdę to jest po prostu implementacja interfejsu użytkownika, żeby obsłużyć wszystkie możliwe przypadki korzystania z interfejsu.

Jak widzisz, kluczem jest zadanie odpowiedniego pytania, zastanowienie się co tak na prawdę chcesz osiągnąć.

PS: @axelbest tak po prawdzie, przyjacielu, to nie wiem. Nie mogę Ci nic poradzić, nie znam szczegółów Twojego pytania. Życzliwie poradzę Ci żebyś kierował się rozumem, analizował za i przeciw, i nie kieruj się regułkami albo ustalonymi schematami, podchodził do tematu z głową.

1

@TomRiddle:

Żadne metody isCorrect() nie mają prawa bytu w obiektowym kodzie. Czy wpuścić kogoś do klubu powinna się nazywać user.allowedInClubs().

Zaraz zaraz, po pierwsze isCorrect() jest jako przykład - może oznaczać, czy dane są spójne.

Po drugie pewien poziom abstrakcji jest wymagany, bo tak samo można się przyczepić do user.allowedInClubs(), że nie wymienia konkretnego klubu z nazwy, i dla każdego klubu powinna być osobna metoda.

To są właśnie typowe problemy określania granic co jest kodem obiektowym a co nie, i co jak należy coś robić a jak nie. Niektórym od tego pęka żyłka z nerwów.

IMO Warto się po prostu trzymać sprawdzonych wzorców projektowych, a nie spędzać godziny w celu napisania idealnego kodu obiektowego, który będzie idealny tylko dla jednej osoby, bo przyjdzie druga osoba i zacznie się czepiać o jakieś szczegóły. Nie warto się tak męczyć.

0

@TomRiddle:

Ten sam problem co z walidacją. Posługujesz się terminem "integralność danych", ale przecież dla różnych obiektów ta "integralność" będzie znaczyła coś innego

To jest proste - albo obiekt ma dane spójne / integralne albo nie. Obiektu nie obchodzi jaka będzie reakcja na poprawną integralność lub brak integralności. Czasem potrzebujesz metod szczegółowych, czasem ogólnych.

Pomyśl np. o generatorze mapy w grze i umieszczaniu na tej mapie różnych obiektów. Zanim obiekt zostanie umieszczony na mapie, generator sprawdza czy obiekt jest integralny, czyli ma wszystkie dane spójne. Jeżeli nie jest to wywala fatal error, albo robi coś innego.

Przecież bez wysokiego poziomu abstrakcji robienie tego typu rzeczy byłoby masakrą.

I teraz tę metodę możesz użyć w wielu miejscach nie tylko przy generowaniu mapy, gdzie generator dodatkowo oprócz integralności danych obiektu będzie sprawdzać inne rzeczy - czy np. obiekty nie nachodzą na siebie w niedozwolony sposób etc.

2

Pomyśl np. o generatorze mapy w grze i umieszczaniu na tej mapie różnych obiektów. Zanim obiekt zostanie umieszczony na mapie, generator sprawdza czy obiekt jest integralny, czyli ma wszystkie dane spójne. Jeżeli nie jest to wywala fatal error, albo robi coś innego.

W obiektowym paradygmacie to nie generator ma wywalić błąd albo robić "coś innego", tylko te "różne obiekty" mają decydować o tym co się z nimi dzieje. W świecie obiektowym to te obiekty same z siebie powinny rzucić wyjątek, jeśli dostaną niespójne dane, najlepiej możliwie wcześnie (jeśli błąd jest związany z typami to w konstruktorze, jeśli to merytoryczny błąd to w wywołaniu metody do odczytu tej danej).

Słuchaj, nikt Ci paradygmatu nie narzuca. Możesz sobie pisać generatory mapy w paradygmatach proceduralnych, strukturalnych, deklaratywnych, funkcyjnych i projektować designy jakie chcesz. Ale jeśli będziesz się kierował takimi zasadami, jakie tu opisałeś, to nie stworzysz kodu obiektowego, tylko kod w jakimś innym paradygmacie, co oczywiście możesz zrobić. Nie podoba Ci się - wybierz inny paradygmatu. Z tego co piszesz to paradygmat proceduralny jest najbliższy temu co opisujesz.

Kończę już tą rozmowę, mam nadzieję że wskazałem Ci wiele drzwi: możesz próbować porzucić własne uprzedzenia i próbować wejść do świata obiektówki; możesz uznać że obiektówka to nie Twoja bajka i wybrać inny paradygmat; możesz przeczytać więcej kodu i poznać więcej programów i podejść zanim wybierzesz coś dla siebie. Ja już jestem zmęczony tym że próbujesz mnie przekonać, żebym zaczął walidować Twój punkt widzenia. Jeśli masz jakieś pytania o obiektówkę, to pisz śmiało, ale nie chcę się więcej kłócić o to, jak masz nazywać Twoje funkcje. Twoja decyzja, pamiętaj tylko żeby kierować się rozumiem podczas programowania. Życzę powodzenia.

0

@TomRiddle: Fajna odpowiedź, ciekawie się czytało. Z podanych przypadków wybieram:

Walidacja danych formularza w aplikacjach webowych - zupełnie inny problem, wymaga wyświetlania widoku, przesyłu danych, części wspólnej wszystkich pół i pół niespełniających kryteriów, i odpowiedzi z wystarczającymi szczegółami żeby wyrenderować widok który ilustruje niespełnienie kryteriów. Klienci nazywają to "Walidacją", ale tak na prawdę to jest po prostu implementacja interfejsu użytkownika, żeby obsłużyć wszystkie możliwe przypadki korzystania z interfejsu.
Załóżmy, że walidujemy jakiś numer identyfikacyjny NIP/REGON/PESEL/(dowolny tax ID). W istniejącym już systemie dane te są przechowywane jako string. Tak więc w Polsce będzie to NIP o ile jest to konto firmowe, PESEL jak prywatne i TaxID jeśli to firma zagraniczna. Nie skupiam się tutaj na idealnym projekcie, tylko mówię o zastanym kodzie. Wyżej powołałem się na Twój przykład, że dane pochodza z formularza, ale rozwinę to o API, dane mogą pochodzić z innych systemów. Niektóre z nich nie zawsze przechowują poprawne dane. Nie chcę też tworzyć super-przegiętych encji/obiektów, napakowanych logiką. Mam tez w systemie archiwalnych użytkowników, których dane na 100% są niepoprawne względem wzorca (np ich PESEL zawiera spacje,myślniki, gdzie w tym systemie wszystko ma być jednym ciągiem znaków, a np NIP oddzielamy myślnikami). Te błędne dane użytkownicy poprawią sobie sami, gdy wejdą w swój profil/panel klienta, wtedy dostaną komunikat typu "popraw NIP [poprawny format to xxx-xxx-xx-xx]".

Tak więc mamy opisany problem :)
W moim podejściu zrobiłbym trzy metody sprawdzające poprawność pesel/nipu/tax_id, na bok odstawiam kwestie tego jak implementowałyby jakiś interfejs albo czy to byłaby jedna klasa walidująca z trzema metodami... Mimo wszystko walidacja tego typu danych moim zdaniem powinna znajdować się poza walidowanym obiektem, bo zakładam że gdy utworzony zostanie taki obiekt to będzie on poprawny, co będzie zasługa walidacji przed jego utworzeniem. Czy jest sens tworzenia obiektu, który będzie miał niepoprawne dane? Moim zdaniem nie. Czy obiekt mógłby sam sprawdzać poprawność danych? Jak najbardziej - w sytuacji, gdy dane od użytkownika/api są poprawne, a dochodzi do sytuacji jak np przekroczenie jakiejś wartości w wyniku obliczeń na wartościach wewnątrz obiektu. Uprościłbym to do tego, że obiekty niemutowalne mogłyby być walidowane z zewnątrz, a w zależności od potrzeb obiekty mutowalne mogą sprawdzać się same.

Cały czas odnoszę się do wypowiedzi Ponieważ w obiektowym kodzie nie ma miejsca na takie klasy jak Validator, rozumiem, że pewne rzeczy lepiej jest robić za pomocą interfejsów, dzięki czemu sprawdzić coś możemy odpowiednio implementując to, lecz skoro sprawdzamy coś w kilku miejscach, to po co implementować to samo dla kilku różnych klas, skoro możemy stworzyć klasę Validator i użyć jej w implementacji sprawdzającej?

Moim zdaniem w obiektowym kodzie jest miejsce na Validator :D

BTW. to idealny temat na rozmowy przy piwku :) 🍺

0
TomRiddle napisał(a):

Pomyśl np. o generatorze mapy w grze i umieszczaniu na tej mapie różnych obiektów. Zanim obiekt zostanie umieszczony na mapie, generator sprawdza czy obiekt jest integralny, czyli ma wszystkie dane spójne. Jeżeli nie jest to wywala fatal error, albo robi coś innego.

W obiektowym paradygmacie to nie generator ma wywalić błąd albo robić "coś innego", tylko te "różne obiekty" mają decydować o tym co się z nimi dzieje. W świecie obiektowym to te obiekty same z siebie powinny rzucić wyjątek, jeśli dostaną niespójne dane, najlepiej możliwie wcześnie (jeśli błąd jest związany z typami to w konstruktorze, jeśli to merytoryczny błąd to w wywołaniu metody do odczytu tej danej).

No ale przecież dokładnie o to chodzi. To metoda checkDataIntegrity() to wykonuje, i obiekt wewnętrznie decyduje czy jest spójny czy nie, a nie generator.

Generatora nie obchodzi to w jaki sposób obiekt to robi, on po prostu sprawdza sobie spójność obiektów.

Czy to koniecznie musi być metoda private / protected niedostępna publicznie?

Obiekt równie dobrze może mieć wywołanie metody checkDataIntegrity() w każdym setterze, i jeżeli dane są nie spójne to coś tam robi.

Nie rozumiem Twojego problemu, i dlaczego generator miałby nie sprawdzać, czy obiekty są spójne czy nie, bez względu na to, czy obiekty same z siebie to robią w innych momentach, czy nie robią.

Ten bezsensowny spór to właśnie przykład na czepianie się szczegółów, a efekt jest taki, że projekt który ja realizuję w 2 tygodnie sam, zespół programistów męczy pół roku, bo co chwile są kłótnie czy to jest wystarczająco obiektowe i idealne, czy nie.

0

@TomRiddle: a weźmy na przykład, funkcję draw() czy tutaj nazwa jest zbyt mało mówiąca? No bo można by się przyczepić, że nie wiadomo co to rysuję i lepiej byłoby nazwać np drawBox(), z kolei nie miałoby to sensu dla dwóch różnych obiektów Box i Square które wołają ją polimorficznie, tak samo jak w przypadku wzorca method template czy wielu innych opartych na polimorfizmie.

Inny przykład:

boolean validateUser(User user) {
   return isAdult(user) && isWoman(user) && hasPhoneNumber(user);
}

Czy nazwa validateUser w tym kontekście jest dobra?

Ja uważam, że spór jest bez sensu, bo to zależy od konkretnego przypadku (a z tego co widzę, dyskusja dotyczy już ogółu) abstrakcji i oboje macie rację.

EDIT: jako że wątek dotyczy OOP to można by ten problem rozwiązać nadając po prostu odpowiednią nazwe klasy ale nadal patrząc na kod od strony która wywołuję polimorficznie metodę, nie widzimy jej nazwy bo odwołujemy się do interfejsu

1
TomRZ napisał(a):
TomRiddle napisał(a):

Pomyśl np. o generatorze mapy w grze i umieszczaniu na tej mapie różnych obiektów. Zanim obiekt zostanie umieszczony na mapie, generator sprawdza czy obiekt jest integralny, czyli ma wszystkie dane spójne. Jeżeli nie jest to wywala fatal error, albo robi coś innego.

W obiektowym paradygmacie to nie generator ma wywalić błąd albo robić "coś innego", tylko te "różne obiekty" mają decydować o tym co się z nimi dzieje. W świecie obiektowym to te obiekty same z siebie powinny rzucić wyjątek, jeśli dostaną niespójne dane, najlepiej możliwie wcześnie (jeśli błąd jest związany z typami to w konstruktorze, jeśli to merytoryczny błąd to w wywołaniu metody do odczytu tej danej).

No ale przecież dokładnie o to chodzi. To metoda checkDataIntegrity() to wykonuje, i obiekt wewnętrznie decyduje czy jest spójny czy nie, a nie generator.

No rozumiem, ale pomimo zachowania abstrakcji i enkapsulacji i polimorfizmu, to jest nadal proceduralny/strukturalny paradygmat. Spieszę z wyjaśnieniem.

Obiekt wewnętrznie decyduje czy jest spójny czy nie, generator woła tą funkcję, i jeśli dostanie false to reaguje jakąś akcją na tą niespójność. I to jest dokładny powód czemu to nie jest obiektowy kod. Nie może być obiektowy.

Żeby był obiektowy, musiałbyś zrobić tak żeby to sam obiekt się zwalidowal czy jest spoiny czy nie, i to ten obiekt (albl jego delegat) na to zareagował, nie generator. Bez tej zmiany kod nie może być obiektowy.

Swoją drogą ten generator też nie brzmi jak obiektowy twór.

Ten bezsensowny spór to właśnie przykład na czepianie się szczegółów, a efekt jest taki, że projekt który ja realizuję w 2 tygodnie sam, zespół programistów męczy pół roku, bo co chwile są kłótnie czy to jest wystarczająco obiektowe i idealne, czy nie.

O nie, nie, nie. Ja się nie zamierzam w takie rozmowy pchać. Ja nikomu nie będę mówił czy ma pisać tak czy siak! Ja tylko nakierowuje na to czym jest a czym nie jest paradygmat obiektowy. To czy ktoś go chce używać to nie moja Brocha i proszę mnie do tego nie mieszać!

1
pedegie napisał(a):

@TomRiddle: a weźmy na przykład, funkcję draw() czy tutaj nazwa jest zbyt mało mówiąca? No bo można by się przyczepić, że nie wiadomo co to rysuję i lepiej byłoby nazwać np drawBox().

Moim, skromnym zdaniem, według zasad obiektowości nazwy new Box().draw() oraz new Square().draw() są jak najbardziej ok.

boolean validateUser(User user) {
   return isAdult(user) && isWoman(user) && hasPhoneNumber(user);
}

Czy nazwa validateUser w tym kontekście jest dobra?

Moim zdaniem nie, i to z trzech powodów.

Po pierwsze, implementacja powinna wyglądać tak user.isAdult() && user.isWoman() && user.hasPhoneNumber();.

Po drugie, metoda zwraca bool, więc jak już coś, to powinna się nazywać isValid().

Po trzecie i najważniejsze, ta metoda sama w sobie wyjęta z kontekstu nic nie znaczy. "valid user" ale względem kogo i do czego? Idę o zakład, że gdziekolwiek nie chcesz użyć tej metody, to chcesz jej użyć do jakiejś logiki biznesowe albo domenowej, i da się nazwać tą funkcje, tak żeby było wiadomo co znaczy. Np nie wiem isHotAndRuchable(), albo może isEligibleForMating(). Albl coś podobnego.

Ja uważam, że spór jest bez sensu, bo to zależy od konkretnego przypadku (a z tego co widzę, dyskusja dotyczy już ogółu) abstrakcji i oboje macie rację.

Moim zdaniem spór dotyczy powszechnej opinii że "kod procedularny" + "klasy" + "solid" + "enkapsulacja" + "polimorifzm" == "kod obiektowy". Ja próbuje wytłumaczyć że tak nie jest! Podobnie jak samo używanie funkcji w kodzie, nie znaczy że kod jest funkcyjny.

EDIT: jako że wątek dotyczy OOP to można by ten problem rozwiązać nadając po prostu odpowiednią nazwe klasy ale nadal patrząc na kod od strony która wywołuję polimorficznie metodę, nie widzimy jej nazwy bo odwołujemy się do interfejsu

Nie, zmiana nazwy klasy również nie sprawiłaby że kod byłby obiektowy, ponieważ tak na prawdę te nazwy nie są istotne aż tak. Istotne jest to czy tożsamość obiektu zależy od tego "czym jest", a nie od tego "co robi". A niestety tożsamość wszelkiej maści validatorów, comparatorów, controllerów, mapperów, managerów zależy od tego co robią, a więc nie są obiektowe. Są procedualne.

Obiektowość rządzi się bardzo konkretnym zasadami, nie wystarczy po prostu wsadzić solid, enkapsulacji, polimorfizmu i bam, gotowy kod obiektowy.

0
axelbest napisał(a):

@TomRiddle: Fajna odpowiedź, ciekawie się czytało.

Dzięki.

W moim podejściu zrobiłbym trzy metody sprawdzające poprawność pesel/nipu/tax_id, na bok odstawiam kwestie tego jak implementowałyby jakiś interfejs albo czy to byłaby jedna klasa walidująca z trzema metodami... Mimo wszystko walidacja tego typu danych moim zdaniem powinna znajdować się poza walidowanym obiektem, bo zakładam że gdy utworzony zostanie taki obiekt to będzie on poprawny, co będzie zasługa walidacji przed jego utworzeniem.

I dziękuję za opisanie problemu, według Twojego opsiu, Twój kod wygląda jak kolejny przykład proceduralnego kodu. Nie zroum mnie źle, nie chcę Ci mówić co masz robić, albo jaki paradygmat masz wybrać. Nie chcę też się kłócić który jest lepszy, to Twoja decyzja. Ja tylko mówię że nazwać tego obiektowym kodem nie możesz (no chyba że dla Ciebie czarne to białe ;) ).

Uważam że Twój kod jest procedularny z co najmniej dwóch powodów.

Mówisz że masz jeden obiekt Pesel oraz trzy implementacje Validatora, które go walidują zależnie od potrzeb.

Więc po pierwsze, to znaczy, że Twój "obiekt pesel" jest niczym innym jak tylko strukturalnym, "głupim" (excuse the expression) pojemnikiem na dane. Niczym nie różni się od struktury z C. A to nie jest poprawny obiekt w OOP.

Po drugie, te trzy implementacje walidatora nie "walidują" siebie, tylko sprawdzają stan innego obiektu, w Twoim wypadku pesel. I to jest kolejne złamanie zasad Obiektowości, ponieważ te walidatory w świecie obiektówki powinny jedynie walidować siebie (co ma mało sensu, bo wgl taki design w obiektowym paradygmacie ma mało sensu).

Czy jest sens tworzenia obiektu, który będzie miał niepoprawne dane? Moim zdaniem nie.

Nie, ale dlatego że w Twoim przypadku nie ma czegoś takiego jak niepoprawne dane.

Czy obiekt mógłby sam sprawdzać poprawność danych? Jak najbardziej - w sytuacji, gdy dane od użytkownika/api są poprawne, a dochodzi do sytuacji jak np przekroczenie jakiejś wartości w wyniku obliczeń na wartościach wewnątrz obiektu. Uprościłbym to do tego, że obiekty niemutowalne mogłyby być walidowane z zewnątrz, a w zależności od potrzeb obiekty mutowalne mogą sprawdzać się same.

To nie byłby kod obiektowy.

Cały czas odnoszę się do wypowiedzi Ponieważ w obiektowym kodzie nie ma miejsca na takie klasy jak Validator, rozumiem, że pewne rzeczy lepiej jest robić za pomocą interfejsów, dzięki czemu sprawdzić coś możemy odpowiednio implementując to, [...]

To też nie byłby kod obiektowy

Moim zdaniem w obiektowym kodzie jest miejsce na Validator :D

Nie ma miejsca, dlatego że, jak już powiedziałem, w Twoim przykładzie nie ma czegoś takiego jak "niepoprawne" dane. To, że tak myślisz, jest prawdopodobnie wynikiem tego że słyszałeś takie uproszczenie, i to warunkuje jak projektujesz aplikacje i jak myślisz o danych. Gdybyś zorientował się że określenie "walidacja", jest co najwyżej wygodnym uproszczeniem (co najgorzej, dużą zmyłką i pułapką), to zauważyłbyś czym Twoje dane tak na prawdę są. Poza tym, pamiętaj "data doesn't speak for itself".

Ja w Twoim przykładzie widzę takie oto elementy.

  • Formularz który jest w stanie dodawać numery PESEL o standardowym formacie, same cyfry.
  • Istniejące zasoby w bazie, które mają inny format, nazwijmy go LegacyFormat (lub może HyphenFormat, lub NonStandardFormat, albo jakąkolwiek inną klasę do opisu tego, w jakim formacie jest zapisany pesel), i które mogą być odczytane i wyświetlone.

Kluczem do obiektowości tutaj jest przedstawienie formatu również jako obiekt, np tak:

new Pesel(new StandardFormat("999"))
new Pesel(new LegacyFormat(" 9-99"))

A czemuż mamy przedstawić format jako obiekt, a nie jako implementację walidatora? Dlatego że w obiektówce, właśnie chodzi o to żeby przedstawiać takie rzeczy jako obiekty. Stąd nazwa "paradygmat obiektowy" ;)

BTW. to idealny temat na rozmowy przy piwku :) 🍺

Maybe some day.

1
TomRiddle napisał(a):

Nie, zmiana nazwy klasy również nie sprawiłaby że kod byłby obiektowy, ponieważ tak na prawdę te nazwy nie są istotne aż tak. Istotne jest to czy tożsamość obiektu zależy od tego "czym jest", a nie od tego "co robi". A niestety tożsamość wszelkiej maści validatorów, comparatorów, controllerów, mapperów, managerów zależy od tego co robią, a więc nie są obiektowe. Są procedualne.

Obiektowość rządzi się bardzo konkretnym zasadami, nie wystarczy po prostu wsadzić solid, enkapsulacji, polimorfizmu i bam, gotowy kod obiektowy.

Składnie piszesz i umiesz argumentować, ale nie ze wszystkim się zgodzę.

OOP gdy się rozwinęło do (moje własne ujęcie) "drugiej fazy" z np wzorcami, szeroko zostały zaakceptowane obiekty "czasownikowe" *), czynnościowe, które nazywasz proceduralnymi

Druga moja myśl, to różnica rygorystycznego / eleganckiego spojrzenia "uniwersyteckiego" z bardziej akceptującym mieszanie stylów "w przemyśle". Zarówno w odniesieniu do paradygmatu obiektowego, jak i funkcyjnego też.

*) sądzę, ze dla wszystkich jest jasne szkolne / przedszkolne podanie OOP, czyli coś co jest, to "obiekt", coś, co robi, to jest metoda. Rzeczownik -> obiekt, czasownik -> metoda

0
AnyKtokolwiek napisał(a):
TomRiddle napisał(a):

OOP gdy się rozwinęło do (moje własne ujęcie) "drugiej fazy" z np wzorcami, szeroko zostały zaakceptowane obiekty "czasownikowe" *), czynnościowe, które nazywasz proceduralnymi

Jeśli mówiąc "czasownikowe" masz na myśli nazwy obiektów z czasownikiem w nazwie, np TrailingString, to tak.

Co do tego części "zostały zaakceptowane", to niestety muszę Ci powiedzieć że środowisko programistów raczej nie jest jednorodne, i jeśli jakaś metodologia dostanie się do społeczeństwa które nie jest tak obdarzone krytycznym myśleniem jak twórca takiej metodologii, to może dojść do bardzo łatwego wypaczenia tej metodologii, tak że zostają tylko te łatwe/wygodne jej aspekty, a jej trudne elementy są pomijane. Ludzka natura - robić coś co jest proste, nie robić tego co nie jest i nie zastanawiać się nad tym.

Stąd mamy cały szereg wypaczeń:

  • Coverage poniżej 90% to największe zło, więc napiszmy więcej pokrycia. Nie testów, nie wykrywaczy bugów - pokrycia
  • Dostajemy NullPointerException - nie projektujmy lepszej struktury, po prostu dodajmy if (a != null)
  • Duplikacja to zło - nie wydzielajmy klas które są reużywalne - zadeklarujmy masę publicznych stałych, i reużywajmy ich.

Etc. jestem pewien że sam mógłbyś mnożyć przykłady tego, jak czasem bardzo dobre pomysły i praktyki nie tylko w programowaniu ale we wszystkich dziedzinach życia są wypierane przez gorsze praktyki, które nonetheless są gorsze, ale prostsze; przez ludzi którzy nie znają różnicy pomiędzy nimi. Tak więc argument że coś zostało zaakceptowane i jest popularne, nie znaczy wcale że to jest dobre. PHP jest tego najlepszym przykładem :>

Druga moja myśl, to różnica rygorystycznego / eleganckiego spojrzenia "uniwersyteckiego" z bardziej akceptującym mieszanie stylów "w przemyśle". Zarówno w odniesieniu do paradygmatu obiektowego, jak i funkcyjnego też.

Ależ ja tego absolutnie nie przeczę. Jeśli chcesz sobie używać jakiegoś paradygmatu X w swoich aplikacjach to proszę Cię bardzo, pisz nawet i w Brainfucku :D Droga wolna. Ale proszę, nie wprowadzaj dezinformacji i nie nazywaj kodu który clearly nie jest obiektowy, obiektowym.

Oczywiście możesz sobie mówić, że mieszasz paradygmaty w swoim projekcie. Możesz też sobie to argumentować jak chcesz. Ale zauważ, że pomieszane paradygmaty to ciągle nie jest ani proceduralny, ani funkcyjny, ani obiektowy kod, tylko jak sam zauważyłeś kod z pomieszanymi paradygmatami.

*) sądzę, ze dla wszystkich jest jasne szkolne / przedszkolne podanie OOP, czyli coś co jest, to "obiekt", coś, co robi, to jest metoda. Rzeczownik -> obiekt, czasownik -> metoda

Nie sądzę, że to jest dla wszystkich jasne. Może znają regułkę/powiedzenie, ale nie sądzę że spora część programistów zdaje sobie sprawę z tego co to oznacza, a przynajmniej jaka była idea w obiektówce. Niestety najbardziej popularną bolączką nadal jest to żę "kod proceduralny" + "enkapsulacja" + "solid" + "polimorfizm" = "kod obiektowy" :/

Again, chciałbym wszystkim podziękować za tą rozmowę

...ale niestety zaczyna ona się zamieniać w miejsce w którym ludzie wygłaszają swoje opinie, i nie szukają nowych horyzontów i nowych dziedzin (może @pedegie i @axelbest są wyjątkiem).

Tak czy tak, nie chcę marnować życia żeby się kłócić z ludźmi w internecie. Chętne mogę komuś pomóc wejść w obiektówkę jeśli ktoś chce, ale nie mam zamiaru walidować wypowiedzi ludzi, którzy chcą potwierdzenia poprawności swoich opinii. Dziękuję za uwagę i życzę powodzenia.

2

Wiadomość do wszystkich..

Być może część z was, kiedy powiedziałem że w obiektówce nie ma miejsca na walidatory, odebrała to tak, jakbym mówił że w obiektowym kodzie nie ma miejsca na walidację. Nie to miałem na myśli, spieszę z wyjaśnieniem.

Chciałem jedynie zailustować, że w 100% przypadków za słowem "walidacja" kryje się bardzo konkretna logika biznesowa. Nie mówimy o niej wprost, ponieważ jest to mnie wygodne (wymaga więcej słów, wiem trudne), ale niestety to jak mówimy o projekcie warunkuje to jak go implementujemy. Łatwiej jest pomyśleć: "zrobię walidajce i po problemie", albo "zwrócę true/false i po problemie", albo "rzucę wyjątek i po problemie", ale to jest ciemna strona mocy - łatwa, szybka i prosta, ale prowadzi do bólu i cierpienia. Celem moich wcześniejszych wiadomości było to, że w paradygmacie obiektowym, to co nazywacie "walidacją", powinno być traktowane jak każdy inny rodzaj logiki biznesowej, a to co nazywane jest "niepoprawnymi danymi", było rozróżniane na podstawie tego jakie są różnice w tych danych.

  • Zamiast "correct integer"/"invalid integer" - użyj "NumericString"/"NonNumericString"
  • Zamiast "correct mail"/"invalid mail" - użyj "EmailAddress"/"MalformedEmailAddress"
  • Zamiast "correct password"/"invalid password" - użyj user.identifiedBy("password"), albo "UserCredentials"/"GuestCredentials"
  • Zamiast "correct city"/"incorrect city" - użyj "DeliveryCity"/"OutOfScopeCity"

Ideą podejścia obiektowego jest to, że dostajemy obiekt, i używamy go. Nie przekazujemy go do naszych wytworów jak mappery, controllery czy walidatory. Po prostu używamy go, tak jak jest.

0

@TomRiddle:

co powiesz o wzorcach. Żeby poruszać się w tej mgle, powiedzmy że chodzi o 21 wzorców od GoF
Olbrzymia większość jest "czynnosciowa"

(nie, nie jestem religijnym apostołem wzorców - bez sekciarstwa)

1
AnyKtokolwiek napisał(a):

@TomRiddle:

co powiesz o wzorcach. Żeby poruszać się w tej mgle, powiedzmy że chodzi o 21 wzorców od GoF. Olbrzymia większość jest "czynnosciowa"

Noi co z tego że są nazwane "czynnościowe"?

(I am a) Chain of responsibility,
(I am a) Command
(I am an) Interpreter
(I am an) Iterator
(I am a) Mediator
(I am an) Observer
(I am a) Visitor

So? Żaden z nich nie nazywa się "(I manage) - Manager" albo "(I validate) Validator".

Więc nie rozumiem pytania?

0

Wow, widzę, że spory odzew dziękuję za niego! Na chwilę obecną napisałem coś takiego:

package com;

public class Main {

public static void main (String[] args) {
        
        User user = new User("Adam", 40.5, 178);
        Validator validator = new Validator(user);

        if (validator.checkName()) {
            if (validator.checkAge() && validator.checkHeight()) {
                System.out.println("User is older than 30 and higher then 160cm");
            }
            else {
                System.out.println("User is younger than 30 or lower than 160cm");
            }
        }
    }
}

class User {
    private String name;
    private double age;
    private double height;

    public User(String name, double age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {

        return name;
    }

    public double getAge() {

        return age;
    }

    public double getHeight() {

        return height;
    }
}
class Validator {

    private User user;

    public Validator(User user) {
        this.user = user;
    }

    public boolean checkName() {
        if (user.getName() != null) {
            return true;
        }
        return false;
    }

    public boolean checkAge() {
        if (user.getAge() >  30) {
            return true;
        }
        return false;
    }

    public boolean checkHeight() {
        if (user.getHeight() > 160) {
            return true;
        }
        return false;
    }
}

I wysłałem do sprawdzenia. Natomiast mam problem jeszcze z jednym zadaniem, a nie chce zaśmiecać forum swoimi pytaniami, wiec pozwolę sobie napisać w tym temacie: Interfacy...

Mam za zadanie napisać taki kod: (Treść zadania) "Przed Tobą nietypowe wyzwanie. Tym razem twoim celem będzie napisanie programu, który wyświetla informacje o wykonaniu określonych zadań (ang. quest) przez pewnego rycerza (knight). W tym celu w swojej aplikacji stwórz następujące elementy programu:

Interfejs Quest, który będzie zawierał w metodę process().
Dwie klasy zadań – DeadIslandQuest i EliteKnightQuest, które implementują interfejs Quest. Wykorzystaj polecenie System.out.println().
Stwórz klasę Knight, która w konstruktorze przyjmie różne zadania implementujące interfejs Quest. W klasie Knight stwórz też dowolną metodę, która wywoła metodę process() interfejsu Quest.
Główny program powinien wyświetlić informacje o zakończeniu konkretnego zadania razem z jego nazwą."

Na chwilę obecną mam coś takiego:

package com;

public class Main {

    public static void main(String[] args) {
        Knight knight1 = new Knight(new EliteKnightQuest());
        Knight knight2 = new Knight(new DeadIslandQuest());
        knight1.run();
        knight2.run();

    }
}
class Knight {

    private Quest quest;

    public Knight(Quest quest) {
        this.quest = quest;
    }

    public void run() {
        quest.process();

    }
}

class DeadIslandQuest implements Quest{

    @Override
    public void process() {
        System.out.println("Run");
    }
}
class EliteKnightQuest implements Quest{

    @Override
    public void process() {
        System.out.println("Fight");
    }
}

interface Quest {

    void process();
}

I też jest źle, kompletnie nie czaje o co w takim razie chodzi w tym zadaniu. Dostałem tylko taką wskazówkę od mentora:

  1. " Troche oszukales system. Zalozmy, ze metoda process w klasie DeadIslandQuest wypisze "Tra tra tra tra bum". To teraz chce, aby moj knight wypisal jakie zadanie wykonal (czyli DeadIslandQuest)."

  2. "Wskazowka: dodaj do interfejsu metode String getName()
    Co robi rycerz (Knight)? Walczy a nie biegnie, czyli fight a nie run ;)"

Dziękuję za wszelką pomoc.

0

Cześć, kolejny dzień kolejne zadanie i problem. Otóż mam napisać dwa programy (jeden już napisałem i wydaje się być ok), a więc pierwszy program z którym mam problem ma za zadaniem liczyć średnią z ocen (średnią liczy), problem tylko w tym, że oceny muszą być wygenerowane randomowo (są) i dodane do listy (przyjąłem, że jest 10 uczniów). Wszystko ładnie się generuje i dodaje do listy, tylko jeszcze w tym zadaniu skrajne oceny (1 i 6) muszą zostać usunięte z listy, a z pozostałych ocen (od 2 do 5) policzyć średnią. I problem mam z usunięciem z listy tych ocen. Ktoś mi pomoże rozwiązać problem?

Poniżej wklejam kod, dokładnie chodzi mi o metodę rateListRemover() w 52 linijce.

package com;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class Main {

    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        RateList rateList = new RateList(list);
        rateList.rateListGenerator();
        rateList.rateListRemover();

        Average average = new Average(list);
        System.out.println("Rozmiar zaktualizowanej listy: " + list.size());
        System.out.println(String.format("Średnia z wszystkich ocen:  %,.2f", average.countAverage()));




        System.out.println();
        //BookList bookList = new BookList();
        //bookList.printPositions();


    }
}
class RateList {

    private Random random = new Random();

    private List<Integer> rateList;

    public RateList(List<Integer> rateList) {
        this.rateList = rateList;
    }

    public List<Integer> rateListGenerator() {

        //List<Integer> rateList = new ArrayList<Integer>();   <--- PÓŹNIEJ USUNĄĆ!!!

        for (int i = 0; i < 10; i++) {
            int rateGenerator = random.nextInt(6) + 1;
            rateList.add(i, rateGenerator);
        }

        for (Integer item : rateList) {
            System.out.println("Ocena: " + item);
        }
        return rateList;
    }

    public List<Integer> rateListRemover() {

        if (rateList.contains(1) || rateList.contains(6)) {
            for (int i = 0; i < rateList.size(); i++) {
                rateList.remove(i);
                System.out.println("Zaktualizowana lista ocen: " + rateList.get(i));
            }
        }

        return rateList;
    }
}

class Average {

    private List<Integer> rateList;

    public Average(List<Integer> rateList) {
        this.rateList = rateList;
    }

    public float countAverage() {

        int sum = 0;

        for (int i = 0; i < rateList.size(); i++) {
            sum = sum + rateList.get(i);
        }
        return (float) sum / (float) rateList.size();
    }
}



// Drugie zadanie
class Book {
    private String bookName;
    private int publicationYear;

    public Book(String bookName, int publicationYear) {
        this.bookName = bookName;
        this.publicationYear = publicationYear;
    }

    public int getPublicationYear() {
        return publicationYear;
    }

    @Override
    public String toString() {
        return "Książka: " + '"' + bookName + '"' +" rok wydania: " + publicationYear;
    }
}

class BookList {


    public LinkedList<Book> books() {
        LinkedList<Book> booksList = new LinkedList<Book>();

        booksList.add(new Book("Krzyżacy", 1900));
        booksList.add(new Book("Potop", 1886));
        booksList.add(new Book("Dallas'63", 2011));
        booksList.add(new Book("Dziady", 1882));
        booksList.add(new Book("Mężczyźni, którzy nienawidzą kobiet", 2008));

        return booksList;
    }

    public LinkedList<Book> printPositions() {

        LinkedList<Book> booksList = books();
        for (Book item : booksList) {
            if (item.getPublicationYear() > 2000) {
                System.out.println(item);
            }
        }
        return booksList;
    }
}
3

Wystarczy usunąć to co chcesz, zamiast sprawdzać czy są elementy do usunięcia, remove zwróci Ci false dla nieistniejących elementów:

  if (rateList.remove((Integer) 6)){
      //usunięto 6
  } else {
     //brak 6, nic nie usunięto
  }
  if (rateList.remove((Integer) 1){
     //usunięto 1
  }

Rzutowanie (Integer) 6 jest konieczne, ze względu na przeciążoną metodę dla typów prostego, która usuwa element spod indeksu, tutaj usuwamy element o wartości.

0

@cs: Trochę za szybko się ucieszyłem, ale i tak dzięki za odzew i pomoc.Bez tytułu.jpg

Edit: Umieściłem rateList.remove((Integer) 6) i rateList.remove((Integer) 1) bezpośrednio w pętli i chyba działa, bo już 10 razy odpalałem i ani 1, ani 6 się nie pojawiły.

Edit2: niestety nie działa... :(

0

Po co to robisz w pętli? Chcesz usunąć wszystkie 6 i 1?

0
package app;

import java.util.List;
import java.util.Random;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.IntStream.rangeClosed;

public class Application {
    public static void main(String[] args) {
        List<Integer> grades = randomGrades(10);
        printGrades(grades);

        grades = grades.stream().filter(grade -> grade != 1 && grade != 6).collect(toList());
        printGrades(grades);
    }

    private static List<Integer> randomGrades(int count) {
        return rangeClosed(1, count).mapToObj(i -> 1 + new Random().nextInt(6)).collect(toList());
    }

    private static void printGrades(List<Integer> grades) {
        String gradeList = grades.stream().map(String::valueOf).collect(joining(", "));
        double average = grades.stream().mapToInt(grade -> grade).average().orElseThrow();
        System.out.printf("Grades: %s, Average: %f\n", gradeList, average);
    }
}
0

@Ukulelemelelele: Podałem Ci sposób na usuwanie elementów z listy bez testowania czy jest, pętlę powinieneś sam sobie dorobić, jeśli chcesz usuwać wszystkie 6 np.:

while(rateList.remove((Integer) 6)){
}

To co zrobiłeś, to usuwasz 6 i 1 tyle razy ile jest elementów w liście, a powinieneś tyle ile trzeba, a informacją, że już nie ma takiej wartości jest co to zwraca remove
@TomRiddle Doceniam wysiłki w promowaniu OOP, ale OP chyba na razie nie ogarnia działania kolekcji, a proponujesz już streamy.

0

@cs: Wstawiłem w metodę coś takiego:

public List<Integer> rateListRemover() {
        
        for (int i = 0; i < rateList.size(); i++) {
            while (rateList.remove((Integer) 6)) {

            }
            while (rateList.remove((Integer) 1)) {

            }
            System.out.println("Zaktualizowana lista ocen: " + rateList.get(i));
        }

        return rateList;
    }

Uruchamiałem już ze 20 razy, na razie 1 i 6 w zaktualizowanej liście się nie pojawiła. :)

Chcesz usunąć wszystkie 6 i 1?

@TomRiddle: Dokładnie, bo w zadaniu średnia ocen nie może być liczone ze skrajnych ocen, czyli z 1 i 6.
I nie chciałem zaśmiecać forum nowymi tematami, dlatego nie zakładałem nowego wątku. :)

1

@Ukulelemelelele: Po co ta pętla for?!! Po drugie: do obliczenia średniej z pominięciem 6 i 1, nie potrzebujesz ich usuwać, wystarczy ich nie uwzględniać przy liczeniu średniej, np. dodając do metody countAverage zbiór z liczbami do wykluczenia podczas liczenia. No chyba, że w treści zadania jest to wyraźnie zaznaczone wyrzucanie z listy. Po trzecie, jeśli usuwasz więcej różnych elementów, to skorzystaj z iteratora, podczas jednego przejścia po całej kolekcji usuniesz wszystkie żądane elementy.

0

Po co ta pętla for?!!

@cs: Już jej nie ma. :) Znaczy jest, ale tylko dla wyświetlenia nowego zestawu listy.

No chyba, że w treści zadania jest to wyraźnie zaznaczone wyrzucanie z listy.

Jest napisane coś takiego: "Następnie przy pomocy pętli for oblicz średnią ocenę ucznia (średnia arytmetyczna), ale w taki sposób, że pominięte zostaną skrajne oceny – JEDNA najmniejsza i JEDNA największa.", ale teraz zauważyłem, że chyba będzie źle, bo ma być pominięta jedna największa i jedna najmniejsza...

Edit: Zobaczę jaki feedback dostanę.

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