Składowe static - mechanizm obiektowy, czy nie?

0

Taki trochę filozoficzny temat, stąd postuję w tym dziale. Dzisiaj na seminarium u nas zrobił się z tego prawie flame (dobrze, że nie było zbyt dużo czasu).

Chodzi mi o takie static jak w Java, C#, C++, Delphi - tj. oznaczające składowe wspólne dla całej klasy.
Czy stwierdzenie, że jest to mechanizm stojący w sprzeczności z podstawowymi założeniami OOP, i że jest raczej dziedzictwem paradygmatu proceduralnego, jest naprawdę takie kontrowersyjne? A może jest po prostu nieprawdziwe?

0

Paradygmat programowania obiektowego mówi o:
abstrakcji
hermetyzacji
polimorfizmie
i dziedziczeniu

Czy użycie static przeczy któremuś z tych punktów?

0

No, dziedziczenia tu nie ma absolutnie. Polimorfizmu również.
Co do hermetyzacji to static z definicji jest dostępne co najmniej dla wszystkich instancji danej klasy (nawet składowe statyczne private), więc nie ma jej.
Do tego często korzystanie ze statycznych składowych raczej łamie hermetyzację obiektów niż ją wspomaga.
A abstrakcja? Gdzie tu jakaś abstrakcja? Static jest zawsze konkretne.

0

Czy użycie static przeczy któremuś z tych punktów?

A nie?

0

Jeśli damy jakąś składową static w naszej klasie to automatycznie blokujemy wyprowadzenie z niej innej klasy, a jak wiemy klasy powinny być przystosowane do ponownego użycia i rozbudowy(np. poprzez dziedziczenie). Dlaczego blokujemy ? Otóż, aby rozbudować klasę musimy znać jej implementację, ponieważ musimy wiedzieć jaki wpływ na działanie obiektów klasy dziedziczącej będzie miała modyfikacja składowej statycznej przez jakieś inne obiekty klasy bazowej, czyli jest już sprzeczność z hermetyzacją i dziedziczeniem. W przypadku, gdy mamy utrudnione dziedziczenie z polimorfizmem dynamicznym też będzie problem.

Królik napisał(a)

Co do hermetyzacji to static z definicji jest dostępne co najmniej dla wszystkich instancji danej klasy (nawet składowe statyczne private), więc nie ma jej.

Jeśli byśmy potrzebowali na przykład modeli izotopów jakiegoś atomu to statyczna składowa określająca liczbę protonów, do której dalibyśmy tylko akcesor nie stanowiła by problemu.

0

Tylko ze nie ma co tak negować sensu static. Czasami potrafi być przydatne. Spójrz tylko ile tego jest w samym .NET i fajnie sie tego potem uzywa. Kwestia zdrowego rozsądku i żeby wiedzieć kiedy static będzie przydatne, a kiedy nie powinno sie go użyć.

0

Tak, to jest programowanie proceduralne. I co z tego?
Nie widzę powodu, dla którego klasy czy metody pomocnicze, które nie operują na elementach związanych z instancją klasy nie mogły być static.

0

Wszelakie statyczne składowe można z powodzeniem zastąpić wstrzykiwaniem zależności, a samo wstrzykiwanie powinno być zrobione już na poziomie języka. Niestety obecnie żaden popularny język nie posiada takiej funkcjonalności. A szkoda, bo byłoby to genialne rozwiązanie. Mega konfigurowalność, testowalność, rozszerzalność, zarządzalność itd bez większego wysiłku.

0

t0m_k to o czym, mówisz można równie dobrze wpasować pod przykład, gdy wirtualna chroniona metoda modyfikuje składową prywatną a ta z kolei ma wpływ na "coś tam". Przesłaniając w klasie potomnej tę metodę wirtualną również musimy znać implementacje klasy bazowej. To nie jest wina składowych statycznych tylko projektu interfejsu klasy.

0

Nie wydaje mi się, żeby to stało w sprzeczności z paradygmatem obiektowym. Co jeśli mamy stałą, która ma być dostępna dla wszystkich klas w projekcie? Hermetyzowanie tego a potem wstrzykiwanie jest totalnie bez sensu. Paradygmat obiektowy mówi, żeby hermetyzować co ma sens hermetyzować a nie wszystko na siłę.

0

samo wstrzykiwanie powinno być zrobione już na poziomie języka

Scala ma coś takiego - nazywają to "cake pattern", no i nie ma w języku static. Ale nie jest na razie językiem popularnym, przynajmniej nie tak jak Java, C++ czy nawet Python. Pewnie jak by dobrze poszukać by się znalazło też kilka innych niszowych języków.

Nie o to mi jednak chodzi, czy static jest dobry czy zły (bo to temat na osobny flame), ale tylko o samo zaklasyfikowanie go jako mechanizm (nie)obiektowy.
W trakcie dyskusji padło zupełnie niewinne stwierdzenie, że static nie jest mechanizmem obiektowym i wywołało to dosyć agresywny atak jednej z uczestniczek seminarium - tzn. argumentacja była po linii, że większość języków obiektowych ma static, i że jest to oczywista oczywistość, że należy to do OOP, i niemal że w ogóle to jakieś kosmiczne bzdury są :D

Co jeśli mamy stałą, która ma być dostępna dla wszystkich klas w projekcie?

Czyli stałą globalną. OOP nie zakazuje mieć stałych / zmiennych globalnych, ale IMHO jest to mechanizm z języków proceduralnych.
W czystym C też mogłeś zrobić stałą globalną dostępną dla wszystkiego w projekcie za pomocą odpowiedniego #define. Nie widzę w tym nic z OOP.

0

A ja widzę, bo np. statyczna stała składowa należy do konkretnej klasy co poprawia czytelność i łatwość utrzymania. Poza tym to że coś nie jest z OOP nie znaczy, że stoi z nim w sprzeczności.

0

Scala ma coś takiego - nazywają to "cake pattern", no i nie ma w języku static. Ale nie jest na razie językiem popularnym, przynajmniej nie tak jak Java, C++ czy nawet Python. Pewnie jak by dobrze poszukać by się znalazło też kilka innych niszowych języków.

Wiem co to jest ten cake pattern, ale to jest wstrzykiwanie na etapie kompilacji do bajtkodu. A mi chodzi o dynamiczne wstrzykiwanie, dynamiczne konfigurowanie, etc - podrzucam jakieś pliki .class albo podmieniam i już program zachowuje się inaczej.

A ja widzę, bo np. statyczna stała składowa należy do konkretnej klasy co poprawia czytelność i łatwość utrzymania. Poza tym to że coś nie jest z OOP nie znaczy, że stoi z nim w sprzeczności.

Niestety JVM nie przerabia nawet final prymityw na final static prymityw. Pewnie z powodu refleksji - za jej pomocą można zmieniać nawet stałe. Z drugiej strony stałe ogólnego przeznaczenia można wrzucać do osobnych klas. Wtedy, jeśli będziemy sobie tworzyć za każdym razem klaskę ze stałymi to, dzięki Escape Analysis, zużycie pamięci i szybkość będą takie same jak w przypadku stałych. Przykładowy kod:

import java.io.IOException;

class PureOOP {

    private final double value = 0.346;

    public double getValue() {
        return value;
    }
}

class Mixed {

    public static final double value = 0.346;
}

public class Main {

    final long Iterations = 10000000000l;
    final boolean UsePureOOP = true;

    void run() throws IOException {
        if (UsePureOOP) {
            double value = 0.;
            for (long i = 0; i < Iterations; i++) {
                value += new PureOOP().getValue();
            }
            System.out.println(value);
        } else {
            double value = 0.;
            for (long i = 0; i < Iterations; i++) {
                value += Mixed.value;
            }
            System.out.println(value);
        }
    }

    public static void main(String[] args) throws Exception {
        new Main().run();
    }
}

Powtarzam jednak, że porządnie zrobione dynamiczne wstrzykiwanie zależności rozwiązałoby masę problemów (nie tylko wyeliminowałoby statyczne składowe, ale także np refleksję, a może i inne wynalazki), zachowując wysoką wydajność.

0

Cos ten Twoj 'PureOOP' nie do konca pure z tym prymitywem double.
Pokaz mi prosze kod ktory za pomoca refleksji zmienia wartosc stalej. Przez stala rozumiem np public static final int FOO = 17;

0
Wibowit napisał(a)

Powtarzam jednak, że porządnie zrobione dynamiczne wstrzykiwanie zależności rozwiązałoby masę problemów (nie tylko wyeliminowałoby statyczne składowe, ale także np refleksję, a może i inne wynalazki), zachowując wysoką wydajność.

Jak masz taka doglebna wiedze na ten temat, to zamiast pierdzielic podzies sie linkami gdzie jest to udowodnione, lub chociazby opisane przez kogos z sensem, gdzie jest wiecej niz jakas teoria w jednym zdaniu. Najlepiej jakis Twoj papier, skoros taki znawca.

0

OK, zamień sobie double na Double :P
Nie pisałem o stałych statycznych, tylko stałych w ogóle. Pole private final cośtam możesz sobie spokojnie zmienić za pomocą refleksji. Zresztą tak czy siak ta konwersja z private final prymityw na private static final prymityw by nie mogła mieć miejsca w Javie, bo pola nie są wirtualizowane. Klasa.pole zawsze prowadzi do tej samej zmiennej, obojętne jakie jest drzewo dziedziczenia tej klasy.

Jak masz taka doglebna wiedze na ten temat, to zamiast pierdzielic podzies sie linkami gdzie jest to udowodnione, lub chociazby opisane przez kogos z sensem, gdzie jest wiecej niz jakas teoria w jednym zdaniu. Najlepiej jakis Twoj papier, skoros taki znawca.

To może podaj mi przykład w którym wstrzykiwanie zależności by nie wystarczyło? W zasadzie to przeoczyłem pewne rzeczy jak np zmiana pól prywatnych czy listowanie pól, ale to pierwsze łamie zasady hermetyzacji, a drugie przydaje się tylko przy łączeniu Javy z językami skyptowymi/ pochodnymi XMLa, których jestem przeciwnikiem. Zawsze jednak można dokonać statycznej analizy bajtkodu i zrobić własnego ClassLoadera.

Zalet jest wiele, np testowalność - zamiast używać statycznych klas można ustawić we wstrzykiwaczu, że dana klasa ma być singletonem. Dzięki temu program w produkcji działa tak, jakby klasa była statyczna, ale możemy sobie przekonfigurować wstrzykiwacza, np ustawić aby klasa była singletonem tylko w zasięgu obecnego przypadku testowego. Dzięki temu można odpalić kilka przypadków testowych naraz, w jednym procesie, co jest niemożliwe w przypadku klas statycznych z globalnym mutowalnym stanem. Jeśli chodzi właśnie o testowalność to na necie jest pełno artykułów namawiających do używania wstrzykiwania zależności.

0
Wibowit napisał(a)

Nie pisałem o stałych statycznych, tylko stałych w ogóle. Pole private final cośtam możesz sobie spokojnie zmienić za pomocą refleksji. Zresztą tak czy siak ta konwersja z private final prymityw na private static final prymityw by nie mogła mieć miejsca w Javie, bo pola nie są wirtualizowane. Klasa.pole zawsze prowadzi do tej samej zmiennej, obojętne jakie jest drzewo dziedziczenia tej klasy.

Pokaz prosze kod ktory zmienia wartosc zmiennej public final. Daruje juz Ci ten private.
Skoro pola sa 'wczesnie wiazane' to wlasnie byloby mozliwa zmiana ze static na nie-static i odwrotnie, gdyby tylko to mialo sens - przeciez znaczenie takich pol jest inne.

Przyznam sie ze reszty nie czytalem, bo nie warto, bredzisz o podstawach.

0
Wibowit napisał(a)

zachowując wysoką wydajność.

Chodzilo mi glownie o to stwierdzenie. Fakt ze lepiej sie testuje i inne takie jest jasny. Bardzo mnie natomiast interesuje ten ostatni fragment, no ale skoro sie tak pewnie wypowiadasz to moze nam powiesz jak tego dokonac.

0

Pokaz prosze kod ktory zmienia wartosc zmiennej public final. Daruje juz Ci ten private.

Jest nawet z private:

import java.io.IOException;
import java.lang.reflect.Field;

class PureOOP {

    private final Double value = 0.346;

    public double getValue() {
        return value;
    }
}

class Mixed {

    public static final Double value = 0.346;
}

public class Main {

    final long Iterations = 10000000000l;
    final boolean UsePureOOP = false;

    void run() throws IOException {
        if (UsePureOOP) {
            double value = 0.;
            for (long i = 0; i < Iterations; i++) {
                value += new PureOOP().getValue();
            }
            System.out.println(value);
        } else {
            double value = 0.;
            for (long i = 0; i < Iterations; i++) {
                value += Mixed.value;
            }
            System.out.println(value);
        }
    }

    public static void main(String[] args) throws Exception {
        PureOOP pureOop = new PureOOP();
        Field pureField = PureOOP.class.getDeclaredField("value");
        pureField.setAccessible(true);
        pureField.set(pureOop, 5.);
        System.out.println(pureOop.getValue());

        Field mixedField = Mixed.class.getField("value");
        mixedField.setAccessible(true);
        mixedField.set(null, 5.);
        System.out.println(Mixed.value);
        //new Main().run();
    }
}

Static final nie można zmieniać. Ale normalne zmienne final już tak.

Chodzilo mi glownie o to stwierdzenie. Fakt ze lepiej sie testuje i inne takie jest jasny. Bardzo mnie natomiast interesuje ten ostatni fragment, no ale skoro sie tak pewnie wypowiadasz to moze nam powiesz jak tego dokonac.

Miałem seminarium "Trendy w IT z Goole" na uczelni i na jednym spotkaniu koleś opowiadał jak w pewnym projekcie w Google przechodzili na wstrzykiwanie. Oczekiwali nieznacznej straty wydajności, a tymczasem okazało się że nieznacznie zyskali.

Skoro JVM spokojnie radzi sobie z głęboką dewirtualizacją metod, jak pokazałem np tutaj: Dlaczego foreach jest gorsze od for (beta) to czemu miałby sobie nie poradzić z dewirtualizacją wstrzykiwacza? Gdyby wstrzykiwacz był zaszyty w JVM, to jego optymalizacja byłaby jeszcze lepsza.

Skoro pola sa 'wczesnie wiazane' to wlasnie byloby mozliwa zmiana ze static na nie-static i odwrotnie, gdyby tylko to mialo sens - przeciez znaczenie takich pol jest inne.

"Wcześnie wiązane" czyli? Chodzi o inlining stałych? A co jeśli stała nie jest w rzeczywistości stałą:

private final int pole = random()

Z punktu widzenia języka Java to jest stała, jednak na static nie możesz przerobić tego pola, gdyż w każdej instancji taka stała jest inna. Jak już mówiłem i tak to jest niemożliwe w Javie (nawet gdyby nie można było zmieniać stałych), bo pola w Javie nie są wirtualizowane. No chyba, że statycznie (przerobić) - przy ładowaniu klasy zawierającej stałą.

0
Wibowit napisał(a)

"Wcześnie wiązane" czyli?

No i o czym tu dyskutowac...

0

Ma to sens. Mozna to zauwazyc np w takiej sytuacji:

class F {
public void m(String s) {}
public void m(Object s {}
}

...
Object s = "string";
new F().m(s);

Ktora metode wywolasz? Powodem jest wlasnie wczesne wiazanie metody na podstawie typow parametrow w czasie kompilacji (tutaj zachaczamy o multiple dispatch, ale nie o tym mowa). Dokladnie taka sama zasada jak wiazanie pol i metod statycznych. Wczesne wiazanie znaczy 'wiazanie na etapie kompilacji'.
Tak tak jestem trollem, a Wibovit podal same konkrety, nie rzuca slow na wiatr. Jego kod rowniez nie rzuca wyjatku...

0

Przez niewirtualizowanie pól chciałem określić takie zachowanie:

class A {
    final int i = 5;
}

class B extends A {
    final int i = 6;
}

public class Main {

    public static void main(String[] args) {
        B b = new B();
        A a = b;
        System.out.println(a.i);
        System.out.println(b.i);
    }
}

Chociaż w sumie teraz widzę, że jeden z moich komentarzy o niemożliwości zamiany pól na static jest nieprawdą - skoro pola, a co za tym idzie stałe, są niewirtualizowane, to spokojnie można by te stałe przerobić na static. Problem w tym, że pola niestatyczne typu final można zmienić przez refleksję, oraz te pola instancji typu final mogą mieć różne wartości w różnych instancjach.

To o czym ty napisałeś to osobny problem.

Wczesne wiazanie znaczy 'wiazanie na etapie kompilacji'.

Gdzie jest ten etap kompilacji w Javie? Kod jest przekompilowywany w kółko, np weźmy przykładowy kod:

List lista = pobierzListę();
for (Object coś : lista) {
  System.out.println(coś);
}

JVM może stworzyć coś takiego:

List lista = pobierzListę();
if (lista instanceof ArrayList) {
  ArrayList listaArrayList = (ArrayList) lista;
  for (Object coś : listaArrayList) {
    System.out.println(coś);
  }
} else {
  przekompilujKod();
}

Taki mechanizm siedzie w JVM. Gdzie tutaj jest etap kompilacji, co oznacza w tym przypadku wczesne i późne wiązanie? Wg mnie te terminy są mało dokładne jeśli chodzi o JVM.

Jego kod rowniez nie rzuca wyjatku...

Zachowanie kodu jest zgodne z moim komentarzem.

Podsumowując, w Javie, żadne z tych rzeczy nie są polimorficzne:

  • pola w klasach,
  • statyczne pola i metody/ klasy,
  • wywoływanie metod (na co zwrócił uwagę jambalaya),
  • pewnie coś tam jeszcze,

C#, mimo że sporo młodszy, to skopiował te błędy. Ale mam nadzieję, że niedługo pojawi się język (i platforma), który niweluje te błędy.

0

Pola, statyczne elementy oraz wiazanie przeciazonych metod do typow parametrow to podobny problem. Wydaje mi sie ze w scali mozna miec 'polimorficzne' dostepy do pol, bo tam mozna to zasymulowac przez wolanie automatycznie wygenerowanej metody? (moge pierdzielic glupoty)

Kiedy jest kompilacja w Javie? Wtedy kiedy nastepuje tlumaczenie kodu zrodlowego na bytecode JVM. W podanym przykladzie w miejscu wywolania metody m powstanie opcode 0xB6 szerzej znany jako invokevirtual, gdzie polimirficznie wywolana zostanie tylko i wylacznie na wzgledem pierwszego parametru (this), natomiast metoda wybrana z przeciazonych zalezna jest calkowicie od typow ktore zna kompilator. Zreszta pobaw sie javap a zobaczysz ladna linjke typu (badz podobna):
invokevirtual #17; //Method test/Foo.m (java/lang/Object)Void
(gdzie #17 jest indeksem na string w poolu klasy Foo wskazujacy nazwe klasy i metody)
mimo ze naprawde parametr jest stringiem. Chocbys sie zejs...starzal to nie zmienisz tego.
Podobne opcody sa invokestatic oraz getfield (doczytasz sobie co oznaczaja ;d) - szczegolnie interesujace jest invokestatic, poniewaz zauwaz ze metoda ktora bedzie wywolana jest dokladnie tak samo okreslona - nazwa klasy, metody oraz typy parametrow. invokevirtual od invokestatic rozni sie przede wszystkim tym ze sciaga sobie dodatkowy parametr (this) i szuka (a moze i nie, ale jako spec od optymalizacji pewnie wiesz najlepiej) w tabeli metod wirtualnych. Co nie zmienia faktu ze przeladowana metoda jest laczona wczesnie, na etapie tworzenia tego bytecodu.
Otoz musze Cie rozczarowac, poniewaz chocby ten bytecode byl nie wiem ile razy przeladowywany przez JVM (w celach optymalizacji warto przeladowac ten sam kod kilkanascie razy, moze w koncu bedzie go mniej?), nie wolno JVM zmienic wiazania tej metody - zajrzyj do specyfikacji - bo by to mogla calkowicie zmienic zachowanie aplikacji podczas runtime. Oczywiscie mozesz sie bawic manipulacja i instrumentacja bytecodu, ale to zupelnie inna brocha.

Jest wiele jezykow ktore radza sobie z 'problemem ktory opisal jambalaya', podejrzyj sobie na wiki haslo 'multiple dispatch'. Na pewno znasz wzorzez Visitor, ktory jest symulacja double dispatch w biednych jezykach. W wiekszosci niszowe, co nie zmienia faktu ze ciekawe.

Aby wypowiedziec sie troche na temat tym razem - static jak dla mnie jest pomylka, uznaje tylko i wylacznie dla stalych. Przy czym w javie wlasciwie tylko prymitywy i literaly string sa stalymi ktore sa inlinowane, a reszta ... takie z nich stale ze nawet nie mozna jako atrybuty anotacji uzyc. O problemach z testowaniem i utrzymywaniem kodu juz powiedzial Vwit. To ze niemal wszystkie jezyki OO zeswalaja na to wynika z tego ze, jak to mowia Niemcy, 'es ist historisch gewachsen'.

0

Bez static newbie nie mogliby kodzić w żadnym języku. ;)

0

Scala automatycznie opakowuje wszystkie pola we własne gettety i settery.

Przy czym w javie wlasciwie tylko prymitywy i literaly string sa stalymi ktore sa inlinowane, a reszta ... takie z nich stale ze nawet nie mozna jako atrybuty anotacji uzyc.

W Javie nie żadnego mechanizmu oznaczania klas czy metod jako "pure" tzn np konstruktor tworzący identyczne obiekty dla identycznych parametrów czy metoda zwracająca identyczne wyniki dla identycznych parametrów. Plus oczywiście brak efektów ubocznych. Czyli można mieć np stałą, której wartość zależy od momentu w którym została obliczona. A więc stałe w Javie to nie są w rzeczywistości stałe - zamiast być wartościami niezmiennymi niezależnie od czasu stworzenia, są wartościami niezmienialnymi od czasu stworzenia.

Jeżeli coś w Javie jest final static, to jest liczone w momencie ładowania klasy. Klasy są ładowane w momencie pierwszego użycia. Dlatego JVM nie może sobie stworzyć pola typu final static kiedy chce, tylko wtedy gdy o tym mówi specyfikacja Javy. Z drugiej strony, prymitywy i Stringi są praktycznie wbudowane w JVMa i są niezmienne niezależnie od czasu stworzenia, więc JVM oblicza sobie stałe tych typów kiedy chce.

Z innych pomyłek, które Java przejęła od poprzedników jest np wartość null, powodująca masę problemów. Np null nie ma typu, więc nie możemy nic na nim sprawdzić.

0

Literaly stringowe dodawane sa do poola w czasie kompilacji. Jako ze sa to prawdziwe wkompilowane literaly mozna ich uzywac np jako wartosci atrybutow anotacji. JVM nie ma az takiej dowolnosci jak Ci sie wydaje, specyfikacja dosc jasno okresla co i kiedy.

Ogolnie troche odbieglismy od tematu, ktory nie byl o Javie ale o staticach. Bardzo podoba mi sie komentarz somekinda - bardzo prawdziwy, niestety i ludzie ktorzy mysla ze sa mastahami rowniez kodza wszystko na staticach.

0
jambalaya napisał(a)

Bardzo podoba mi sie komentarz somekinda - bardzo prawdziwy, niestety i ludzie ktorzy mysla ze sa mastahami rowniez kodza wszystko na staticach.

Czy ja wiem czy niestety? Początkujący, który zna już zmienne, warunki i pętle potrzebuje czasu, żeby się nauczyć myśleć obiektowo. Przez ten czas static rozwiązuje część problemów, które newbie sam sobie tworzy brakiem doświadczenia w OOP. Na pewnym etapie rozwoju przydaje się, tak jak mleczaki. ;)
A jak ktoś nie wyjdzie z tego stadium larwalnego, to cóż - jego problem (i tych, którzy będą po nim utrzymywali kod). Chociaż nigdy nie spotkałem się z kimś, kto by uważał się za dobrego programistę i miał w projekcie wszystkie klasy i metody static.

0

Napisalem ze niestety ludzie ktorzy uwazaja sie za mastahow naduzywaja static, i pozniej nic sie z tym nie da zrobic bez przebudowania pol systemu. Nie mam nic do newbie, sam nim wciaz jestem ;d

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