Czy „interfejs statyczny” to dobra praktyka?

13

Niedawno zauważyłem, że istnieje możliwość posiadania statycznych metod w interfejsach. Podobnie jak w przypadku statycznych pól interfejsu, istnieje ciekawe zachowanie: nie są one dziedziczone.

Nie jestem pewien, czy jest to przydatne w rzeczywistych interfejsach, które mają zostać zaimplementowane. Umożliwia to jednak programistom tworzenie interfejsów, które są po prostu kopertami dla elementów statycznych, takich jak np. Klasy narzędzi.

Prostym przykładem jest po prostu koperta dla globalnych stałych. W porównaniu z klasą można łatwo zauważyć brakującą płytkę kotłową, public static finalponieważ są one zakładane (co czyni ją mniej gadatliwą).

public interface Constants {
    String LOG_MESSAGE_FAILURE = "I took an arrow to the knee.";
    int DEFAULT_TIMEOUT_MS = 30;
}

Możesz także zrobić coś bardziej złożonego, na przykład ten pseudo-wyliczenie kluczy konfiguracji.

public interface ConfigKeys {
    static createValues(ConfigKey<?>... values) {
        return Collections.unmodifiableSet(new HashSet(Arrays.asList(values)));
    }

    static ConfigKey<T> key(Class<T> clazz) {
        return new ConfigKey<>(clazz);
    }

    class ConfigKey<T> {
        private final Class<T> type;
        private ConfigKey(Class<T> type) {
            this.type = type;
        }
        private Class<T> getType() {
            return type;
        }
    }
}

import static ConfigKeys.*;
public interface MyAppConfigKeys {
    ConfigKey<Boolean> TEST_MODE = key(Boolean.class);
    ConfigKey<String> COMPANY_NAME = key(String.class);

    Set<ConfigKey<?>> VALUES = createValues(TEST_MODE, COMPANY_VALUE);

    static values() {
        return VALUES;
    }
}

W ten sposób można również utworzyć „klasę” narzędzi. Jednak w narzędziach często przydatne jest stosowanie prywatnych lub chronionych metod pomocniczych, co nie jest całkiem możliwe w klasach.

Uważam, że jest to miła nowa funkcja, a zwłaszcza fakt, że elementy statyczne nie są dziedziczone, jest interesującą koncepcją, która została wprowadzona tylko do interfejsów.

Zastanawiam się, czy możesz uznać to za dobrą praktykę. Podczas gdy styl kodu i najlepsze praktyki nie są aksjomatyczne i jest miejsce na opinię, myślę, że zwykle istnieją uzasadnione powody, aby poprzeć opinię.

Bardziej interesują mnie powody (nie) używania takich wzorców.


Zauważ, że nie zamierzam implementować tych interfejsów. Są jedynie kopertą ze względu na ich statyczną zawartość. Zamierzam użyć tylko stałych lub metod i ewentualnie użyć importu statycznego.

Vlasec
źródło
1
Moja szarpiąca kolana reakcja na to jest taka, że ​​może to prowadzić tylko do złych rzeczy. Mimo że publiczne metody statyczne to w zasadzie tylko funkcje globalne z przestrzenią nazw. Wydaje się, że zezwolenie na dowolny kod implementacji w interfejsie jest niezgodny z jego przeznaczeniem, ponieważ brak definicji kontraktu. Mówię, pozostaw częściową implementację klasom abstrakcyjnym. Będę musiał przeczytać, dlaczego to zostało dodane.
Adrian
Muszę teraz iść, ale napiszę o tym jutro. Krótko mówiąc, wcześniej interfejs miał wyraźny cel, który był wyraźnie oddzielony od celu klasy AbstractClass. Teraz nakładają się one na siebie w sposób, który narusza agnostyczną definicję interfejsu. Ponadto istnieją przypadki, w których nie można już dziedziczyć z wielu interfejsów, jeśli implementują one domyślne metody. Istnieją więc wyjątki od dziedziczenia wielu interfejsów, w których wcześniej nie było. Ponieważ Java drastycznie odstąpiło od wspólnej definicji interfejsu ...
Adrian
może także wprowadzać w błąd nowych programistów, którzy starają się właściwie zrozumieć podstawowe koncepcje OOP, takie jak interfejsy, AbstractClases, kiedy używać kompozycji zamiast dziedziczenia, itp.
Adrian
Aliester, mówisz o defaultmetodach. Mówię o staticmetodach i polach. Nie są one dziedziczone, więc nie przerywają wielokrotnego dziedziczenia.
Vlasec

Odpowiedzi:

1

Prostym przykładem jest po prostu koperta dla globalnych stałych.

Umieszczanie stałych w interfejsach jest nazywane, Constant Interface Antipatternponieważ stałe są często jedynie szczegółem implementacji. Dalsze wady zostały podsumowane w artykule Wikipedii na temat interfejsu Constant .

W ten sposób można również utworzyć „klasę” narzędzi.

Rzeczywiście, dokument Java stwierdza, że ​​metody statyczne mogą ułatwić organizowanie metod pomocniczych, na przykład podczas wywoływania metod pomocniczych z metod domyślnych.

Markus Pscheidt
źródło
1
Nie sądzę, aby była to odpowiedź na postawione pytanie, ponieważ można zrobić obie te rzeczy bez tej nowej funkcjonalności.
Adrian
Ach, więc z dokumentacji wynika, że ​​jest to metoda pomocnicza. Jest to jednak również publiczne, co jest temu sprzeczne. Może więc ma na celu pomóc klasom implementującym interfejs. Ale nie są dziedziczone, więc musisz użyć importu statycznego, aby poczuć się jak odziedziczony. Cóż, dziękuję za sprawdzenie w dokumentacji.
Vlasec
2
Nie ma absolutnie nic złego w umieszczaniu stałych w interfejsach, jeśli te stałe są w jakiś sposób częścią interfejsu API opisanego przez interfejs. Jedyną rzeczą, która jest antypatternem, jest wstawianie stałych do interfejsów bez metod i zmuszanie klas do implementacji interfejsu tylko w celu odwoływania się do stałych bez prefiksu klasy. Jest to nadużycie interfejsów, a od czasu wprowadzenia importu statycznego całkowicie przestarzałe.
Michael Borgwardt,
1

Jak stwierdzono w pytaniu, interfejs nie jest przeznaczony do implementacji (ani tworzenia instancji). Możemy więc porównać to z klasą, która ma prywatnego konstruktora:

  • Klasy nie można rozszerzać bez super()wywoływania, ale interfejs można „zaimplementować”
  • Nie można utworzyć instancji klasy bez odbić, a interfejs można łatwo utworzyć jako anonimową klasę tak prostą: new MyInterface() {};

To porównanie jest korzystne dla klasy, ponieważ pozwala na większą kontrolę. To powiedziawszy, klasa nie jest również przeznaczona do przechowywania rzeczy statycznych, można by oczekiwać, że będzie miała instancje. Cóż, nie ma idealnej opcji.

Dziwne jest również dziedziczenie po „interfejsie statycznym”:

  • Klasa „implementująca” dziedziczy pola, ale nie dziedziczy metod statycznych
  • Rozszerzony interfejs nie dziedziczy żadnych elementów statycznych
  • (Podczas gdy klasa rozszerzająca klasę dziedziczy każdego nieprywatnego członka ... a ponieważ nie można jej rozszerzyć za pomocą interfejsu, jest mniej miejsca na zamieszanie)

Mogą to być powody, aby uznać to za zły wzorzec i nadal używać klas statycznych w tych przypadkach. Jednak dla kogoś uzależnionego od cukru syntaktycznego może to być kuszące nawet z wadami.

Vlasec
źródło
... W każdym razie konwencje kodowania zespołu mogą rozwiązać te problemy. Dla mnie, chociaż nie wiąże ręki programisty prawie tak mocno, jak klasa z prywatnym konstruktorem, nadal niesie mniejsze ryzyko niż setery, a setery są często używane.
Vlasec,
0

Jako @ MarkusPscheidt pomysł ten jest zasadniczo rozszerzeniem antipatternu Constant Interface . To podejście było początkowo szeroko stosowane, aby umożliwić dziedziczenie jednego zestawu stałych przez wiele różnych klas. Powodem, dla którego uznano to za antypattern, były problemy, które napotkano przy stosowaniu tego podejścia w rzeczywistych projektach. Nie widzę powodu, aby sądzić, że problemy te dotyczą wyłącznie stałych.

Prawdopodobnie co ważniejsze, naprawdę nie ma takiej potrzeby. Zamiast tego używaj importu statycznego i wyraźnie mów o tym, co importujesz i skąd.

JimmyJames
źródło
Tak, importowanie statyczne wydaje się lepszym pomysłem niż dziedziczenie stałych i działa ono ze statycznymi elementami obu klas i interfejsów. I jest widoczny tylko w klasie / interfejsie.
Vlasec,
@Vlasec Racja, ale nie trzeba tego robić w interfejsie. Jedyna różnica między robieniem tego z klasą a interfejsem polega na tym, że można dziedziczyć z więcej niż jednego interfejsu. Standardową praktyką dla takiej klasy użyteczności jest ustawienie konstruktora na prywatny, aby zapobiec tworzeniu instancji lub rozszerzeniu. Nie ma żadnej korzyści z robienia tego w interfejsie nad klasą. Po prostu otwiera drzwi do niewłaściwego użycia.
JimmyJames
Widzę wartość w stałych na interfejsach. Interfejsy mają tendencję do definiowania metod, a metody czasami akceptują stałe jako parametry. Np interface ColorFilter {Image applyGradient(int colorSpace, int width, byte[] pixels); }. : Mogę sobie wyobrazić, że jest kilka różnych stałych RGB, RGBA, CMYK, GREYSCALE, INDEXEDitd.
Stijn de Witt
@StijndeWitt Nie byłoby to najgorsze, ale możesz w tym celu użyć interfejsu enum lub zagnieżdżonej w interfejsie. Prawdziwym problemem jest wykorzystanie tego do wprowadzenia statyki w zakres wielu klas poprzez dziedziczenie. Takie podejście jest wyraźnie gorsze niż stosowanie importu statycznego.
JimmyJames
0

Powiedziałbym, że jeśli jesteś zainteresowany prawidłowym użyciem nowej funkcji, spójrz na kod w JDK. Domyślne metody interfejsów są bardzo szeroko stosowane i łatwe do znalezienia, ale metody statyczne na interfejsach wydają się bardzo rzadkie. Niektóre przykłady obejmują java.time.chrono.Chronology, java.util.Comparator, java.util.Map, i całkiem kilka interfejsów w java.util.functioni java.util.stream.

Większość przykładów, na które patrzyłem, obejmuje użycie tych interfejsów API, które prawdopodobnie będą bardzo powszechne: konwersja między różnymi typami w interfejsie API czasu, na przykład w celu porównań, które prawdopodobnie będą często wykonywane między obiektami przekazywanymi do funkcji lub obiektami zawartymi w kolekcje. Moja na wynos: jeśli istnieje część interfejsu API, która musi być elastyczna, ale możesz na przykład powierzyć 80% przypadków użycia kilkoma ustawieniami domyślnymi, rozważ użycie metod statycznych na interfejsach. Biorąc pod uwagę, jak rzadko ta funkcja wydaje się być używana w JDK, byłbym bardzo konserwatywny, kiedy korzystałbym z niej we własnym kodzie.

Twoje przykłady nie przypominają tego, co widzę w JDK. W tych konkretnych przypadkach prawie na pewno powinieneś stworzyć taki, enumktóry implementuje pożądany interfejs. Interfejs opisuje zestaw zachowań i tak naprawdę nie ma biznesu utrzymującego kolekcję jego implementacji.

ngreen
źródło
-1

Zastanawiam się, czy istnieją powody, aby z niego nie korzystać.

A może kompatybilność ze starszymi maszynami JVM?

Czy ogólnie uważasz to za dobry pomysł?

Nie. Jest to zmiana niezgodna wstecz, która dodaje Zilch do ekspresji języka. Dwa kciuki w dół.

Czy są jakieś sytuacje modelowe, w których zdecydowanie zalecamy zajęcia?

Zdecydowanie zalecam używanie klas do przechowywania szczegółów implementacji. Interfejs naprawdę ma po prostu powiedzieć „ten obiekt obsługuje operacje XYZ” i nie ma to nic wspólnego z żadnymi statycznymi metodami, które możesz pomyśleć o umieszczeniu deklaracji interfejsu. Ta funkcja przypomina rozkładany fotel z wbudowaną mini lodówką. Pewnie, że ma pewne zastosowania niszowe, ale nie przyciąga wystarczająco dużo wagi, aby zasługiwać na mainstream.

AKTUALIZACJA: Proszę, nie więcej głosów negatywnych. Najwyraźniej niektórzy myślą, że statycznymi metodami w interfejsach są kolana pszczoły i niniejszym kapituluję. Tak szybko usunę tę odpowiedź, ale dołączone do niej komentarze są interesującą dyskusją.

DepressedDaniel
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy
Gdybym przegłosował twoje pytanie, zrobiłbym to, ponieważ nasze dwie pierwsze odpowiedzi nie są prawdziwymi odpowiedziami. Nowa wersja języka oferuje nowe narzędzia ułatwiające pracę, co jest celem tych nowych wersji. Trzecia odpowiedź jest trochę zagubiona w ruinach dwóch pierwszych.
Vlasec,