Czy dobrze używam słowa kluczowego static?

0

Witam, chciałem poćwiczyć sobie zastosowanie słowa static w metodach i polach i napisałem prosty program, który zapisuje temperaturę, ustawia jednostkę (F, C) i ewentualnie zamienia jednostki. Zastanawiam się, czy w dobry sposób zrobiłem metody statyczne (czy powinny jednak być non-static) i czy ogólnie są jakieś błędy w kodzie, albo cos co mógłbym poprawić, żeby kod był czytelniejszy?

import java.util.InputMismatchException;
import java.util.Scanner;

public class Main {
    static Scanner input = new Scanner(System.in);
    public static void main(String[] args) {
        Menu menu = new Menu();
        try {
            menu.checkWeather(input);
        } catch (InputMismatchException e) {
            System.out.println("Podano zły znak!");
        }
    }
}

class Weather {
    private final double temperature;
    static char unit = '0';

    Weather(double temperature) {
        this.temperature = temperature;
    }

    static double converterFtoC(double temperatureF) {
        unit = 'C';
        double temperatureC;
        temperatureC = (double)5 / 9 * (temperatureF - 32);
        return temperatureC;
    }

    static double converterCtoF(double temperatureC) {
        unit = 'F';
        double temperatureF;
        temperatureF = temperatureC * 1.8 + 32;
        return temperatureF;
    }

    static void setUnitF() {
        unit = 'F';
    }

    static void setUnitC() {
        unit = 'C';
    }
}

class Menu {
    private double temperature;
    void checkWeather(Scanner input) {
        System.out.print("Jaka jest dzisiaj pogoda? ");
        temperature = input.nextDouble();
        Weather weather = new Weather(temperature);
        menu(input);
    }

    void menu(Scanner input) {
        int choice;
        do {
            System.out.println("Co chesz zrobić?");
            System.out.println("1. Ustawić jednostkę na stopnie C");
            System.out.println("2. Ustawić jednostkę na stopnie F");
            if (Weather.unit == 'C' || Weather.unit == 'F') {
                System.out.println("3. Zamienic z C na F");
                System.out.println("4. Zamienic z F na C");
            }
            System.out.println("Wyjśc z programu (wpisz 0)");
            System.out.print("Twoj wybor: ");
            choice = input.nextInt();

            switch (choice) {
                case 1:
                    Weather.setUnitC();
                    System.out.println(STR."Dzisiaj jest \{temperature} stopni \{Weather.unit}");
                    break;
                case 2:
                    Weather.setUnitF();
                    System.out.println(STR."Dzisiaj jest \{temperature} stopni \{Weather.unit}");
                    break;
                case 3:
                    System.out.print(STR."Pomyślnie zamieniono \{temperature} stopni \{Weather.unit} na ");
                    temperature = Weather.converterCtoF(temperature);
                    System.out.println(STR."\{temperature} stopni \{Weather.unit}");
                    break;
                case 4:
                    System.out.print(STR."Pomyślnie zamieniono \{temperature} stopni \{Weather.unit} na ");
                    temperature = Weather.converterFtoC(temperature);
                    System.out.println(STR."\{temperature} stopni \{Weather.unit}");
                    break;
                default:
                    choice = 0;
                    break;
            }
        } while (choice != 0);
    }
}
1

Nie jestem biegły w Java, bardziej siedzę w .Net, niemniej założenia powinny być podobne.
Ogólnie static wykorzystujesz w miejscach (klasach, metodach, polach), z których będziesz często korzystał.
Zmniejsza to ilość kodu ponieważ nie musisz inicjować klasy.
Metody converterCtoF i converterFtoC są ok, jesli chodzi o wykorzystanie static.
Jeśli chcesz zerknąć jak to zrobić dobrze to klasa Math jest dobrym przykładem.
Co do kodu, na pewno możesz uprościć
static void setUnitF() {
unit = 'F';
}

static void setUnitC() {
unit = 'C';
}

tworząc jedną metodę i jedynie przekazując unit jako parametr.

Parametr temperatue przekazany przez konstruktor w klasie Weather jest nigdzie nie wykorzystywany.

Linie System.out.println(STR."Dzisiaj jest {temperature} stopni {Weather.unit}") i System.out.println(STR."{temperature} stopni {Weather.unit}"), robią to samo można to uwspólni i wywalić poza pętlę.
W case 1 i case 2 dodać ewentualnie "Dzisiaj jest" w System.out.print

2

Ogólnie static jest tylko ok jezeli jest używany w klasach/metodach które nie mają stanu, nie są logiką biznesową i są jakimiś "utility" które się raczej nie zmieniają i jest potrzeba ich współdzielenia w wielu modułach.

W twoim przypadku jest dużo chaosu w kodzie i podejrzewam że chciałeś używać temperature jako coś co determinuje wyświetlaną pogodę z metod klasy Weather. W takim przypadku użycie static jest błędne i powinieneś przesłać do metody menu utworzony obiekt Weather, a metody zmienić na zwykle i operowac na temperature z klasy Weather a nie Main.

Jeżeli natomiast klasa Weather miała by być typowym utility class, czy np. Służyć do transformacji temperatury z C na F itd. I być używana w różnych modułach biznesowych to static by było jak najbardziej ok.

Jak mogę coś poradzić, to na początku póki się dopiero uczysz, to w ogóle nie używaj static. Dopiero jak się nauczysz pożądnie OOP, to wtedy zrozumiesz kiedy static jest ok, a kiedy nie. Jak od razu podczas nauki zaczniesz mieszać static z OOP to nic się nie nauczysz i w twoich programach będzie jeden wielki chaos.

0

Dzięki za podpowiedź. Tak mi się właśnie wydawało, że trochę to nie zastosowania na tym etapie. Chciałem znaleźć jakieś proste zastosowanie i gdzieś zobaczyłem pomysł właśnie z zamianą C na F i zrobiłem to, żeby mniej więcej zobaczyć jak działa static i bardziej zrozumieć zastosowanie. Jeszcze chciałbym zapytać o polimorfizm, klasy zagnieżdżone, anonimowe i agregacje, bo są to też zagadnienia, których średnio rozumiem zastosowanie i chodzi mi o to czy warto teraz próbować robić programy, w których zrozumiem, kiedy tego używać, czy to też wyjdzie później?

1

Dobrym przykładem do czego mozna by użyć static w twoim programie mogla by być klasa TemperatureUtil gdzie byś miał statyczne metody do operacji zamiany miar temperatury. A w Weather tylko użyć tych statycznych metod z TemperatureUtil.

Dzięki temu wydzielasz reużywalny kod i możesz go użyć w innych miejscach w aplikacji gdzie jest potrzebna zamiana temperatur.

Innym przykladem użycia static w taki sposób może być:

  • Np. Dostajesz w swojej aplikacji dane w jakims nietypowym formacie i piszesz metodę która go zamienia na Np. Json, no i musisz jej użyć w wielu miejscach gdzie te dane są odbierane. To wydzielasz ten kod do statycznej metody i możesz używać wszędzie beż potrzeby tworzenia obiektów.

Ogólnie to tu jest dobry artykuł na ten temat:
https://www.baeldung.com/java-static-methods-use-cases

1

tl;dr;

Źle.

Odpowiedź

@Maciek_SK8: Ogólnie to nie używaj staticów raczej nigdy. W normalnym kodzie raczej nie ma dla nich miejsca, wszędzie jest lepiej użyć metod i zmiennych instancyjnych. Moim zdaniem jedynym sensownym miejscem dla static'ów, to jest "alias dla konstruktora", np jakbyś zamiast new Angle(90) chciał zrobić Angle.fromDegrees(90), i wtedy taka statyczna metoda powinna zwrócić instancję. Poza tym jednym use-casem, nie ma praktycznie żadnego powodu żeby używać staticów. Jeśli tak jak mówił @MaaJin masz miejsce w którym często używasz jakiegoś kawałka kodu, to to co powinno się zrobić to albo wydzielić nowy obiekt, albo opakować go w abstarkcję, w miejscach w których go używasz.

Ludzie mają ochotę tylko dlatego używać staticów, bo mają jakiejś miejsce w kodzie do którego trudno coś przekazać (jakiś kontroler, jakaś zakopana klasa) - co znaczy że design jest słaby - i mają wybór - albo przekazać parametry, albo dodać statica. I niestety większość ludzi wybiera statica co jest szybkie na krótką metę, ale dodaje niejawne powiązania i zwiększa tight-coupling przez co cały design staje się jeszcze gorszy. Tak na prawdę metody statyczne i pola statycznie koncepcyjnie przypominają funkcje i zmienne globalne (tak, można dodać private, ale to nie zmienia wiele). Ewentualnie czasem jak ktoś ma niską zdolność do dobrego projektowania kodu to wydaje mu się że taki static jest "dobrym miejscem", mimo że użycie go nie ma żadnych zalet a ma same wady.

Co do Twojego kodu z pierwszego posta:

  1. Nie ma powodu żeby trzymać Scanner w polu static. Lepiej byłoby go wsadzić do main().
  2. main() niestety musi być static, bo tak Java wymusza, ale poza tym miejscem, raczej wszystko powinno być metodami instancyjnymi.
  3. Klasa Weather nie ma sensu za bardzo. To co tak na prawdę zrobiłeś, to napisałeś kod tak jakby był ze zmiennymi globalnymi, tylko opakowanymi w klasę. Bardzo słaby design.
  4. Rozumiem że próbowałeś znaleźć sobie jakiś use case dla metod statycznych, ale niestety to nie jest to.

Co do odpowiedzi przedmówców:

Luciferrrro napisał(a):

Ogólnie static jest tylko ok jezeli jest używany w klasach/metodach które nie mają stanu, nie są logiką biznesową i są jakimiś "utility" które się raczej nie zmieniają i jest potrzeba ich współdzielenia w wielu modułach.

Nadal to nie ma sensu. Użycie statica w takich miejscach nie ma żadnych zalet, a ma za to wady.

Luciferrrro napisał(a):

W twoim przypadku jest dużo chaosu w kodzie i podejrzewam że chciałeś używać temperature jako coś co determinuje wyświetlaną pogodę z metod klasy Weather. W takim przypadku użycie static jest błędne i powinieneś przesłać do metody menu utworzony obiekt Weather, a metody zmienić na zwykle i operowac na temperature z klasy Weather a nie Main.

Tak.

Luciferrrro napisał(a):

Jeżeli natomiast klasa Weather miała by być typowym utility class, czy np. Służyć do transformacji temperatury z C na F itd. I być używana w różnych modułach biznesowych to static by było jak najbardziej ok.

Nie byłoby jaknajbardziej okej. Takie oderwanie "transformacji temperatury" od całego programu to jest niezdrowe podejście. Na pewno taka transformacja znajduje się w jakimś konkretnym miejscu w kodzie, w którym spełnia określone zadanie, i takie obliczenie powinno być w tym miejscu w którym jest użyte, w odpowiedniej klasie przeznaczonej do tego celu, związanego z domeną i tym co program ma faktycznie robić - a nie w jakimś oderwanym kawałku, który jak się czyta to nie wiadomo po co jest.

Miałoby to może sens, gdybyś pisał bibliotekę transformującą jednostki, i chciał wystawić entry-point. Ale w normalnych aplikacjach static to złe wyjście.

Luciferrrro napisał(a):

Jak mogę coś poradzić, to na początku póki się dopiero uczysz, to w ogóle nie używaj static. Dopiero jak się nauczysz pożądnie OOP, to wtedy zrozumiesz kiedy static jest ok, a kiedy nie. Jak od razu podczas nauki zaczniesz mieszać static z OOP to nic się nie nauczysz i w twoich programach będzie jeden wielki chaos.

To też jest prawda.

MaaJin napisał(a):

Ogólnie static wykorzystujesz w miejscach (klasach, metodach, polach), z których będziesz często korzystał.

Niech Cię ręka boska broni.

MaaJin napisał(a):

Zmniejsza to ilość kodu ponieważ nie musisz inicjować klasy.

I tym samym dodajesz ciasne powiązanie.

MaaJin napisał(a):

Jeśli chcesz zerknąć jak to zrobić dobrze to klasa Math jest dobrym przykładem.
Co do kodu, na pewno możesz uprościć
static void setUnitF() {
unit = 'F';
}

static void setUnitC() {
unit = 'C';
}

tworząc jedną metodę i jedynie przekazując unit jako parametr.

I tym samym łamiąc enkapsulację klasy, co znaczy że jak implementacja klasy się zmieni, to wszystkie miejsca które jej używają też trzeba bedzie poprawić. Brawo.

0

@Riddle Dzięki za wytłumaczenie, w miarę rozumiem o co chodzi. Zrozumiałem najważniejsze, czyli, żeby unikać staticow.

Maciek_SK8 napisał(a):

Jeszcze chciałbym zapytać o polimorfizm, klasy zagnieżdżone, anonimowe i agregacje, bo są to też zagadnienia, których średnio rozumiem zastosowanie i chodzi mi o to czy warto teraz próbować robić programy, w których zrozumiem, kiedy tego używać, czy to też wyjdzie później

Dałbyś radę wytłumaczyć też jeszcze zastosowanie tych zagadnień, o które wcześniej pytałem? Też nie ogarniam, kiedy tego używać

1
Maciek_SK8 napisał(a):

@Riddle Dzięki za wytłumaczenie, w miarę rozumiem o co chodzi. Zrozumiałem najważniejsze, czyli, żeby unikać staticow.

Maciek_SK8 napisał(a):

Jeszcze chciałbym zapytać o polimorfizm, klasy zagnieżdżone, anonimowe i agregacje, bo są to też zagadnienia, których średnio rozumiem zastosowanie i chodzi mi o to czy warto teraz próbować robić programy, w których zrozumiem, kiedy tego używać, czy to też wyjdzie później

Dałbyś radę wytłumaczyć też jeszcze zastosowanie tych zagadnień, o które wcześniej pytałem? Też nie ogarniam, kiedy tego używać

No więc polimorfizm to właściwie najważniejsza rzecz z tych które wymieniłeś, i to moim zdaniem jest Must-Have które musisz poznać. Pozwala zwiększać jakość kodu w różne sposoby (głównie z uwagi na Open/Close i Dependency Inversion).

Polimorfizm to w skrócie korzystanie z dwóch różnych implementacji za pomocą jednego interfejsu programistycznego (np dwie różne funkcje które posiadają taki sam zestaw argumentów i zwracany typ). Jest to bardzo potężna strategia, z uwagi na to że miejsce które chce coś zrobić, nie musi dokładnie wiedzieć jak to zrobić - co sprawia że kod jest wyższej jakości.

Pozostałe elementy, jak klasy zagnieżdżone i anonimowe, to można powiedzieć że jest praktycznie cukier składniowy, i nie ma aż takiego znaczenia. Nie dają Ci niczego czego nie można zrobić bez nich.

Natomiast co do Tego:

Maciek_SK8 napisał(a):

i chodzi mi o to czy warto teraz próbować robić programy, w których zrozumiem, kiedy tego używać, czy to też wyjdzie później

To jest bardzo słaby pomysł żebyś po prostu próbował na własną rękę napisać coś na siłę korzystając z tych elementów. Znasz powiedzenie - jak masz młotek, to wszystko wygląda jak gwóźdź. Nie staraj się dobrać problem pod rozwiązania, tak jak teraz robisz - tylko pisz sobie programy normalnie, i jak natrafisz na jakąś potrzebę, to wtedy się doszkalaj.

Ewentualnie jak koniecznie chcesz się nauczyć polimorfizmu, to na pewno nie przez siebie wymyślane przykłady, tylko wyszukaj zadania które prezentują problemy do których faktycznie polimorfizm się nada. I od razu mówię - jak znajdziesz tutorial który dla przykładów polimorfizmu daje Ci takie klasy jak: auta, planety, zwierzęta, graczę, to od razu to wyłącz bo to jest chłam.

0
Riddle napisał(a):

Ewentualnie jak koniecznie chcesz się nauczyć polimorfizmu, to na pewno nie przez siebie wymyślane przykłady, tylko wyszukaj zadania które prezentują problemy do których faktycznie polimorfizm się nada. I od razu mówię - jak znajdziesz tutorial który dla przykładów polimorfizmu daje Ci takie klasy jak: auta, planety, zwierzęta, graczę, to od razu to wyłącz bo to jest chłam.

Jeszcze raz dzięki za wszytko, zaraz poszukam zadań na polimorfizm, żeby jeszcze lepiej zrozumieć zastosowanie (sama zasadę działania umiem mniej więcej)

1
Maciek_SK8 napisał(a):

Jeszcze raz dzięki za wszytko, zaraz poszukam zadań na polimorfizm, żeby jeszcze lepiej zrozumieć zastosowanie (sama zasadę działania umiem mniej więcej)

Najlepiej obejrzyj ten filmik: całe, od deski do deski, najlepiej 2 albo 3 razy. Zwłaszcza O/C i D jest o polimorfizmie, ale najlepiej obejrzyj całe.

1

@Riddle patrzac po stacku masz dużo większe doświadczenie niż ja, a temat statycznych elementów też mnie interesuje.

Luciferrrro napisał(a):

Ogólnie static jest tylko ok jezeli jest używany w klasach/metodach które nie mają stanu, nie są logiką biznesową i są jakimiś "utility" które się raczej nie zmieniają i jest potrzeba ich współdzielenia w wielu modułach.

Nadal to nie ma sensu. Użycie statica w takich miejscach nie ma żadnych zalet, a ma za to wady.

Napiszesz coś więcej? o jakie wady chodzi, pomijając ciasne powiazania ofc.

MaaJin napisał(a):

Ogólnie static wykorzystujesz w miejscach (klasach, metodach, polach), z których będziesz często korzystał.

Niech Cię ręka boska broni.

Skrót myślowy, wybacz.
Zasze wykorzystywałem static tworząc klasę, która miała za zadanie przetrzymywać garść statycznych metod, które często były wykorzystywane, mappery, loggery, przeliczenia (nie biznesowe, ale np jakieś konwertery) itp.
Coś w myśl tego co pisał @Luciferrrro
Co w takim podejściu jest nie tak?

MaaJin napisał(a):

Zmniejsza to ilość kodu ponieważ nie musisz inicjować klasy.

I tym samym dodajesz ciasne powiązanie.

To fakt, ale .Net dość często korzysta z static w formie Extension Methods, rozumiem, ze ich wykorzystanie to też wpływ błędnie zaprojektowanego kodu?

MaaJin napisał(a):

Jeśli chcesz zerknąć jak to zrobić dobrze to klasa Math jest dobrym przykładem.
Co do kodu, na pewno możesz uprościć
static void setUnitF() {
unit = 'F';
}

static void setUnitC() {
unit = 'C';
}

tworząc jedną metodę i jedynie przekazując unit jako parametr.

I tym samym łamiąc enkapsulację klasy, co znaczy że jak implementacja klasy się zmieni, to wszystkie miejsca które jej używają też trzeba bedzie poprawić. Brawo.

Tu przyznam nie za bardzo łapię, bo jeśli nie uwspólni tej metody to również zmiana implementacji wpłynie na wszystkie miejsca, które jej używają.

1

No to tak, extension methods w c# to jest cukier składniowy na metodę statyczną - koncepcyjnie niczym się to nie różni, więc nie potraktuję tego jako coś osobnego.

Do pozostałych pytań - na przykładzie posta autora wątku z liczeniem farenheitów na celciusza - to problem tutaj wynika z tego, że zamiana samych tylko prymitywów z jednostki F na C została "wyrwana" z miejsca w którym była użyta, i została wsadzona w statyczną metodę. To jest problem. Dlatego że jak myślisz o samym tylko liczeniu jednostek "w od osobnieniu" (jakby program robił tylko to i nic więcej), to faktycznie nie ma znaczenia czy metoda jest statyczna czy nie. Dlatego dałem przykład w poście wyżej że gdybyś pisał bibliotekę - to nawet miałoby to sens.

Ale tak nie jest - liczenie tych jednostek w programie jest dodane w konkretnym celu, w konkretnym miejscu - w tym wypadku w interfejsie konsolowym w klasie Menu. Ta klasa liczy jednostki na potrzeby pokazania ich użytkownikowi, więc to ona powinna wykonać to obliczenie. Wtedy taka klasa miałaby bardzo dobrą enkapsulację, inne klasy nie wiedziałyby o niej, dużo łatwiej byłoby ją zmienić, zrefaktorować, łatwiej ją przetestować, wszystko jest łatwiejsze. Taka funkcja mogłaby być prywatną funkcją lub nawet być wpisaną po prostu w kodzie. Co daje więc wyszarpanie tej logiki z miejsca w którym powinna być i wyniesienie jej na zewnątrz, ustawiemoe jej publiczną, i wystawienie szczegóły implementacyjne (np. typów danych) "na zewnątrz"? Moim zdaniem nic pozytywnego, a łamie enkapsulację. Dodatkowo zachęca inne kawałki kodu do użycia tych metod statycznych, tym samym dodając ciasne powiązania.

To co czasem kieruje ludźmi do chęci wyciągnięcia ich, po to żeby jakoś "pokazać" w kodzie że to jest inny byt, ale to jest błąd. Należałoby to ogarnąć testami jednostkowymi napisanymi pod klasę Menu, które sprawdzają czy dla danych danych wejściowych od usera, widzimy konkretne dane wyjściowe.

MaaJin napisał(a):

Zasze wykorzystywałem static tworząc klasę, która miała za zadanie przetrzymywać garść statycznych metod, które często były wykorzystywane, mappery, loggery, przeliczenia (nie biznesowe, ale np jakieś konwertery) itp.

No to jeśli wyciągasz mappery do statycznych funkcji, to oddalasz je od miejsca gdzie się używane i mamy dokładnie ten sam problem o którym pisałem wyżej.

MaaJin napisał(a):

I tym samym łamiąc enkapsulację klasy, co znaczy że jak implementacja klasy się zmieni, to wszystkie miejsca które jej używają też trzeba bedzie poprawić. Brawo.

Tu przyznam nie za bardzo łapię, bo jeśli nie uwspólni tej metody to również zmiana implementacji wpłynie na wszystkie miejsca, które jej używają.

Tak, dlatego powinien ją uwspólnić, ale w taki sposób żeby logika była zaenkapsulowana, i jej zmiana nie wpływała na inne miejsca które jej uzywają - np metodami instancyjnymi.

Jeszcze w sumie dochodzi dodatkowa rzecz, o której warto wspomnieć.

Faktycznie, jeśli mamy funkcję która nie ma stanu i jest prosta, to zawołanie MyClass.myMethod(); nie różni się koncepcyjnie za bardzo od new MyClass().myMethod();. Więc ktoś mógłby na to popatrzeć, stwierdzić "jedno new mniej", i uznać że metody statyczne są fajniejsze. I w sumie miałby rację - tylko co dalej się może stać:

  • Dalej może się stać tak, że będziesz chciał przetestować tą metodę i wstawić fake'ową implementację - mają metodę instancyjną da się to zrobić bardzo prost, mając statyczną - już nie. Trzeba poprawić wszystkie miejsca gdzie jest użyta, co może nie być trywialne.
  • Może się stać tak, że chciałbyś dopisać dekorator do funkcji - mając instancyjną metodę, można to zrobić bardzo prosto - statyczną, nie.

I jeszcze jeden aspekt - taki powiedziałbym "socjalny". Ludzie inaczej patrzą na klasy które trzymają same funkcje statyczne (tzn. util klasy). Chodzi o to, że jeśli mamy klasę "util" która trzyma dwie funkcje do zamiany jednostek, i chcemy dodać w innym miejscu w kodzie, np w CacheSaver tez funkcję która liczy jednostki - to niektórzy programiści czują "chętkę" żeby dodać ją do tej klasy util - mimo że programistycznie one nie mają ze sobą nic wspólnego, nie operują na tych samych danych, i nie są użyte w tym samym miejscu - to sam fakt istnienia takiej "klasy util" zachęca do wsadzania odrębnych kawałków kodu w jednym miejscu, łamiąc SRP. Ile razy widziałeś klase util w której 5 metod pasowało do siebie, ale były też 2 inne które trochę odstawały, ale nie było dobrego miejsca gdzie by je wynieść? To jest właśnie ta wada.

Czy faktycznie nie pisanie tego new jest worth?

Jak bardzo chcesz, to możesz dać kod ze staticami, to mogę go napisać bez staticów, tak że sam go będziesz wolał najpewniej.

0

Akurat zamiana miar temperatury jako static jest ok, bo nie jest to część domeny, tylko stała rzecz jak 2+2=4 w matematyce i nie widzę powodu żeby tworzyć dla takiej rzeczy obiekt.

Choć można by Temperature traktować jak np. Money z jodamoney i na obiekcie mieć metody przeliczania pomiędzy miarami. No ale mi chodziło o to żeby pokazać gdzie static jest ok. A nie żeby projektować aplikację pogodową.

0
Luciferrrro napisał(a):

Akurat zamiana miar temperatury jako static jest ok, bo nie jest to część domeny, tylko stała rzecz jak 2+2=4 w matematyce i nie widzę powodu żeby tworzyć dla takiej rzeczy obiekt.

Ależ oczywiście że zmiana temperatury w tym przypadku to jest część domeny, dlatego że to jest to co robi aplikacja autora @Maciek_SK8. Zerknij na kod źródłowy z pierwszego postu - widać jak na dłoni że cały sens aplikacji to jest praca na temperaturze, więc jaknajbardziej to jest część domeny.

To powiedziwaszy, ja nie mówiłem żeby zrobić osobną klasę na temperature. To przeliczenie powinno być w najbliższym miejscu gdzie jest użyte, czyli w klasie Menu. Nie ma sensu tego wyciągać na zewnątrz.

0

Jak bardzo chcesz, to możesz dać kod ze staticami, to mogę go napisać bez staticów, tak że sam go będziesz wolał najpewniej.

  class Controller
    {
        static void GetUserNameAndSurname()
        {
            var p = new Person { Id = 1, Name = "Jan", Surname = "Kowalski", Age = 30 };
            return p.ConvertToDto();
        }
    }

    class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }

    }

    class PersonDto
    {
        public string Name { get; set; }
        public string Surname { get; set; }
    }

    static class Extensions
    {
        public static PersonDto ConvertToDto(this Person person)
        {
            return new PersonDto { Name = person.Name, Surname = person.Surname };
        }
    }

@Riddle Jeśli możesz to zrobić na przykładzie tego mappera, będę wdzięczny :)

0

Zaczynam powoli ogarniać, chociaż wydaje mi się, że kwestia używania static, to bardziej kwestia zmiany myślenia na temat static.

MaaJin napisał(a):

Zasze wykorzystywałem static tworząc klasę, która miała za zadanie przetrzymywać garść statycznych metod, które często były wykorzystywane, mappery, loggery, przeliczenia (nie biznesowe, ale np jakieś konwertery) itp.

No to jeśli wyciągasz mappery do statycznych funkcji, to oddalasz je od miejsca gdzie się używane i mamy dokładnie ten sam problem o którym pisałem wyżej.

Rzeczywiście wrzucam metodę do mapowania elementu domeny po za domenę, zamiast trzymać te elementy razem.

MaaJin napisał(a):

I tym samym łamiąc enkapsulację klasy, co znaczy że jak implementacja klasy się zmieni, to wszystkie miejsca które jej używają też trzeba bedzie poprawić. Brawo.

Tu przyznam nie za bardzo łapię, bo jeśli nie uwspólni tej metody to również zmiana implementacji wpłynie na wszystkie miejsca, które jej używają.

Tak, dlatego powinien ją uwspólnić, ale w taki sposób żeby logika była zaenkapsulowana, i jej zmiana nie wpływała na inne miejsca które jej uzywają - np metodami instancyjnymi.

Żebym miał jasność,
Uwspólniamy metodę, i parametr wstrzykujemy w tej metodzie czy w nowej instancji, parametr globalny unit ma być ustawiony jako final?

Faktycznie, jeśli mamy funkcję która nie ma stanu i jest prosta, to zawołanie MyClass.myMethod(); nie różni się koncepcyjnie za bardzo od new MyClass().myMethod();. Więc ktoś mógłby na to popatrzeć, stwierdzić "jedno new mniej", i uznać że metody statyczne są fajniejsze. I w sumie miałby rację - tylko co dalej się może stać:

  • Dalej może się stać tak, że będziesz chciał przetestować tą metodę i wstawić fake'ową implementację - mają metodę instancyjną da się to zrobić bardzo prost, mając statyczną - już nie. Trzeba poprawić wszystkie miejsca gdzie jest użyta, co może nie być trywialne.

No tak fakt faktem nie jesteś w stanie zrobić fake instancji klas statycznych (gdy do tego dochodzi to na 100% problemem jest błędna architektura projektu), ale same metody można testować bez ich implementacji. Np. statyczna metoda typy AddValuest(int a, int b) która zwraca int.

I jeszcze jeden aspekt - taki powiedziałbym "socjalny". Ludzie inaczej patrzą na klasy które trzymają same funkcje statyczne (tzn. util klasy). Chodzi o to, że jeśli mamy klasę "util" która trzyma dwie funkcje do zamiany jednostek, i chcemy dodać w innym miejscu w kodzie, np w CacheSaver tez funkcję która liczy jednostki - to niektórzy programiści czują "chętkę" żeby dodać ją do tej klasy util - mimo że programistycznie one nie mają ze sobą nic wspólnego, nie operują na tych samych danych, i nie są użyte w tym samym miejscu - to sam fakt istnienia takiej "klasy util" zachęca do wsadzania odrębnych kawałków kodu w jednym miejscu, łamiąc SRP. Ile razy widziałeś klase util w której 5 metod pasowało do siebie, ale były też 2 inne które trochę odstawały, ale nie było dobrego miejsca gdzie by je wynieść? To jest właśnie ta wada.

I tutaj jest chyba clou sprawy, przyzwyczaiłem się do korzystania z utility class, bo tak było robione w projektach przez bardziej doświadczonych seniorów i w sumie tak było wygodnie, potem pisząc jakieś UT w których nie mogłem testować szerszego kontekstu poprzez DI, niejednokrotnie na to kur***em.

3

Metody i klasy statyczne jak najbardziej sie używa. ridel jak zwykle pieprzy glupoty, szkoda nawet klawiatury żeby to komentować.

1
MaaJin napisał(a):

Zaczynam powoli ogarniać, chociaż wydaje mi się, że kwestia używania static, to bardziej kwestia zmiany myślenia na temat static.

Są języki w których można pisać kod "po prostu", można dodać funkcję "po prostu" i dodać klasę "po prostu", jak np Python, JavaScript, PHP. W niektórych językach z kolei nie można dodać "kodu" po prostu, ale można dodać funkcję i klasę. Mówi się wtedy że funkcja jest "top-level member". Jeszcze w innych, można dodać tylko klasę i nic więcej, jak np w Javie.

Da się oczywiście napisać całą aplikację na samych funkcjach i strukturach danych, nie ma problemu - samymi funkcjami oczywiście też da się osiągnąć enkapsulację - tylko wtedy trzeba mieć inną strukturę wejściową i wyjściową (żeby mogły mieć różne interfejsy). Samymi funkcjami da się też ogarnąć polimorfizm, jeśli w języku da się zrobić domknięcie. (Można filozofować że klasy i funkcje to są dwie strony tej samej monety, ale ja nie o tym).

Ja to widzę tak, że tam gdzie ludzie piszą klasy w pythonie - i chcą dodać funkcję poza klasą, to ją dodają po prostu dodają. Pisząc taki sam program, tylko w Javie - nie da się dodać funkcji poza klasą, więc ludzie wsadzają je do staticów - koncepcyjnie to jest to samo.

MaaJin napisał(a):

Żebym miał jasność,
Uwspólniamy metodę, i parametr wstrzykujemy w tej metodzie czy w nowej instancji, parametr globalny unit ma być ustawiony jako final?

Nie rozumiem pytania. Możesz je zadać jeszcze raz and/or inaczej?

Faktycznie, jeśli mamy funkcję która nie ma stanu i jest prosta, to zawołanie MyClass.myMethod(); nie różni się koncepcyjnie za bardzo od new MyClass().myMethod();. Więc ktoś mógłby na to popatrzeć, stwierdzić "jedno new mniej", i uznać że metody statyczne są fajniejsze. I w sumie miałby rację - tylko co dalej się może stać:

  • Dalej może się stać tak, że będziesz chciał przetestować tą metodę i wstawić fake'ową implementację - mają metodę instancyjną da się to zrobić bardzo prost, mając statyczną - już nie. Trzeba poprawić wszystkie miejsca gdzie jest użyta, co może nie być trywialne.

No tak fakt faktem nie jesteś w stanie zrobić fake instancji klas statycznych (gdy do tego dochodzi to na 100% problemem jest błędna architektura projektu), ale same metody można testować bez ich implementacji. Np. statyczna metoda typy AddValuest(int a, int b) która zwraca int.

Tak czy tak, statici mają same wady i żadnych zalet.

MaaJin napisał(a):

I tutaj jest chyba clou sprawy, przyzwyczaiłem się do korzystania z utility class, bo tak było robione w projektach przez bardziej doświadczonych seniorów i w sumie tak było wygodnie, potem pisząc jakieś UT w których nie mogłem testować szerszego kontekstu poprzez DI, niejednokrotnie na to kur***em.

Wiem dokładnie o czym mówisz, sam miałem kiedyś wiele takich przyzwyczajeń i bardzo ciężko się ich pozbyć.

kzkzg napisał(a):

Metody i klasy statyczne jak najbardziej sie używa. ridel jak zwykle pieprzy glupoty, szkoda nawet klawiatury żeby to komentować.

Sure. "Pisze się" też funkcje na 50 linijek, nazywa klasy Manager, "dodaje się" tight-coupling, i "stosuje się" całą masę złych praktyk.

@kzkzg Zgadzam się że wielu ludzi używa staticów - z tym nie dyskutuję. To co ja piszę, to to że moim zdaniem to jest mega słabe. Osobiście obserwuje że ludzie korzystają ze staticów z sentymentu, a nie dlatego że faktycznie coś to dodaje. Ewentualnie ktoś już ma skopany design, i jest zmuszony dodać statica, bo nie da się inaczej czegoś zrobić - ale jak jesteś w takiej sytuacji, to masz na prawdę duży problem.

Ja osobiście nie używam staticów, chyba że nie miałbym innego wyjścia.

0
Riddle napisał(a):

Ja osobiście nie używam staticów, chyba że nie miałbym innego wyjścia.

Jestem nowy na forum, ale od razu widzę, że masz podejście do tematów "elektrodowe", "nie i chu...". Wiele frameworków/bibliotek używa staticów i dzięki temu są czytelne i proste w użyciu, tylko trzeba to robić z głową. Np. factory methods, buildery, mapery można je pisać w oddzielnej klasie i tworzyć obiekt takiego factory/buildera, ale często nie ma to sensu, bo tylko nawalisz klas i zrobisz wszystko mniej czytelne i się wypalisz zawodowo pisząc codziennie kilkaset klas boilerplate kodu, a w 99% przypadków nikt nigdy nie wróci do twojej klasy KupaFactory żeby dodać jakąś skomplikowaną logikę tworzącą nowe rodzaje kupy i wystarczy napisać Kupa.createKupa(xxx). A jak ten 1% się zdarzy to zrefactorowanie statycznej kupy na oddzielną klasę, to nie koniec świata.

0
Luciferrrro napisał(a):
Riddle napisał(a):

Ja osobiście nie używam staticów, chyba że nie miałbym innego wyjścia.

Jestem nowy na forum, ale od razu widzę, że masz podejście do tematów "elektrodowe", "nie i chu...".

Nie sądzę, bo jestem otwarty na argumenty. Pokaż mi, gdzie moje rozumowanie jest nieprawdziwe to zmienię zdanie.

Luciferrrro napisał(a):

Wiele frameworków/bibliotek używa staticów i dzięki temu są czytelne i proste w użyciu, tylko trzeba to robić z głową.

Przykład takiej biblioteki?

Poza tym, już pisałem posty wczesniej - że faktycznie jak chcesz dodać pojedynczy entry-point do biblioteki, to nawet to jeszcze ma sens - jeśli te metody statyczne są tylko zewnętrznym interfesem. Napisałem to w tym poście: Czy dobrze używam słowa kluczowego static?

nawet się zacytuję:

Riddle napisał(a):

Nie byłoby jaknajbardziej okej. Takie oderwanie "transformacji temperatury" od całego programu to jest niezdrowe podejście. Na pewno taka transformacja znajduje się w jakimś konkretnym miejscu w kodzie, w którym spełnia określone zadanie, i takie obliczenie powinno być w tym miejscu w którym jest użyte, w odpowiedniej klasie przeznaczonej do tego celu, związanego z domeną i tym co program ma faktycznie robić - a nie w jakimś oderwanym kawałku, który jak się czyta to nie wiadomo po co jest.

Miałoby to może sens, gdybyś pisał bibliotekę transformującą jednostki, i chciał wystawić entry-point. Ale w normalnych aplikacjach static to złe wyjście.

Ale dodaję też, że to jest tylko dlatego sensowne - bo kod jest wyrwany od użycia. W normalnych aplikacjach, gdzie to my sami jesteśmy użytkownikami naszych metod, to traci sens.

Luciferrrro napisał(a):

Np. factory methods, buildery, mapery można je pisać w oddzielnej klasie i tworzyć obiekt takiego factory/buildera, ale często nie ma to sensu, bo tylko nawalisz klas i zrobisz wszystko mniej czytelne i się wypalisz zawodowo pisząc codziennie kilkaset klas boilerplate kodu,

Ale przecież to co ja proponuję nie dodaje większej ilości klas. Ja tylko mówię - tą klasę którą napisałeś że ma statici - napisz ją tak żeby nie miała. Ilość klas pozostaje taka sama. Ewentualnie zrób inline tego kodu jesli ma jedno użycie - wtedy jest nawet mniej klas.

Luciferrrro napisał(a):

a w 99% przypadków nikt nigdy nie wróci do twojej klasy KupaFactory żeby dodać jakąś skomplikowaną logikę tworzącą nowe rodzaje kupy i wystarczy napisać Kupa.createKupa(xxx). A jak ten 1% się zdarzy to zrefactorowanie statycznej kupy na oddzielną klasę, to nie koniec świata.

Ja nie proponuję pisania boilerplate'ów 😐

Ja proponuję: nie odrywać logiki od miejsca w których jest użyta, trzymaj miejsca które operują na tych samych danych blisko siebie - tak że nie musisz nigdy mieć utilsów, mapperów, builderów. Projektuj program pod use'case, a nie pod jakiś sentyment wydzielania logiki poza domenę.

Piszesz żę dobre miejsce na statyczne metody to: factory methods, buildery i mappery. Moim zdaniem to jest zły pomysł:

  • Zamiast factory method (jeśli to nie jest fabryka abstrakcyjna), to umieść jako przeładowany konstruktor - ewentualnie możesz mieć alias na konstruktor w postaci statycznych metod, tak jak już pisałem niejednorkornie wcześniej, np Angle.fromDegree(90) zamiast new Angle(90).
    • Jeśli jednak masz abstrakcyjną fabrykę, to tak czy tak ona nie może być static.
  • Mappery moim zdaniem nie powinny być statyczne, bo tak jak mówiłem - to wyciąga ich implementację dalej od miejsca w którym są użyte, więc to jest słabszej jakości kod - powinny być najbliżej miejsca w którym są użyte, a wtedy nie muszą być static.
  • To że builder ma mieć statyczne metody, to nie do końca rozumiem jak miałoby działać - skoro buildery ze reguły są chainowane, np builder().withA().withB().withC(). To jedyne co może być static tutaj, to ta pierwsza metoda, czyli Builder.builder(), tylko że ona tak czy tak musi zwrócić instancję, więc ten Builder.builder() i tak musi zrobić return new Builder(), więc równie dobrze możnaby całkowicie pominąć tą metodę statyczną.

Podsumuję moją wypowiedź całkowicie:

  • Czy uważam że statyczne metody "ładniej wyglądają" w kodzie? Tylko estetycznie? No tak, wyglądają ładniejej bo nie trzeba new i nawiasów. Faktycznie Java jest trochę verbose, bo niektóre języki nie mają w ogóle new, jak Python i się da. Więc faktycznie to new jest nadmiarowe, i ładniej by było gdyby go nie było, składniowo.
  • Ale czy uważam że statyczne metody mają jakiekolwiek zalety nad instancyjne metody? No niestety nie. I dlatego ich nie używam.

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