Domyślne metody Java 8 jako cechy: bezpieczne?

116

Czy bezpieczną praktyką jest używanie metod domyślnych jako wersji cech dla ubogich w Javie 8?

Niektórzy twierdzą, że pandy mogą być smutne, jeśli używasz ich tylko ze względu na to, że są fajne, ale nie taki jest mój zamiar. Często przypomina się również, że domyślne metody zostały wprowadzone w celu wsparcia ewolucji API i wstecznej kompatybilności, co jest prawdą, ale nie oznacza to, że używanie ich jako cech jako takich nie jest złe ani zniekształcone.

Mam na myśli następujący praktyczny przypadek użycia :

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

A może zdefiniuj PeriodTrait:

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

Trzeba przyznać, że można by wykorzystać kompozycję (a nawet klasy pomocnicze), ale wydaje się ona bardziej rozwlekła i zagracona i nie pozwala skorzystać z polimorfizmu.

Czy więc można bezpiecznie / bezpiecznie używać metod domyślnych jako podstawowych cech , czy też powinienem się martwić o nieprzewidziane skutki uboczne?

Kilka pytań dotyczących SO jest związanych z cechami Java i Scala; nie o to tutaj chodzi. Nie proszę też tylko o opinie. Zamiast tego szukam autorytatywnej odpowiedzi lub przynajmniej wglądu w teren: jeśli użyłeś domyślnych metod jako cech w swoim projekcie korporacyjnym, czy okazało się, że to bomba czasowa?

youri
źródło
Wydaje mi się, że możesz uzyskać te same korzyści z dziedziczenia klasy abstrakcyjnej i nie musisz się martwić, że pandy będą płakać ... Jedynym powodem, dla którego widzę użycie domyślnej metody w interfejsie, jest to, że potrzebujesz funkcji i nie możesz zmodyfikuj kilka starszego kodu opartego na interfejsie.
Deven Phillips,
1
Zgadzam się z @ infosec812 na temat rozszerzania klasy abstrakcyjnej, która definiuje własne statyczne pole rejestratora. Czy twoja metoda logger () nie utworzyłaby nowej instancji programu rejestrującego za każdym razem, gdy jest wywoływana?
Eidan Spiegel
Aby zarejestrować się, możesz zajrzeć na Projectlombok.org i ich adnotację @ Slf4j.
Deven Phillips,

Odpowiedzi:

120

Krótka odpowiedź brzmi: bezpiecznie się z nich korzysta :)

Wstrętna odpowiedź: powiedz mi, co masz na myśli mówiąc o cechach, a może dam ci lepszą odpowiedź :)

Mówiąc poważnie, termin „cecha” nie jest dobrze zdefiniowany. Wielu programistów Java jest najlepiej zaznajomionych z cechami wyrażanymi w Scali, ale Scala nie jest pierwszym językiem, który ma cechy, czy to w nazwie, czy w efekcie.

Na przykład w Scali cechy są stanowe (mogą mieć varzmienne); w Fortecy są czystym zachowaniem. Interfejsy Java z domyślnymi metodami są bezstanowe; czy to znaczy, że nie są cechami? (Podpowiedź: to było podchwytliwe pytanie.)

Ponownie, w Scali cechy są komponowane poprzez linearyzację; jeśli klasa Arozszerza cechy Xi Y, to kolejność, w jakiej Xi Ysą mieszane, określa sposób rozwiązywania konfliktów między Xi Y. W Javie ten mechanizm linearyzacji nie występuje (został częściowo odrzucony, ponieważ był zbyt „niepodobny do Java”).

Bezpośrednim powodem dodania domyślnych metod do interfejsów była obsługa ewolucji interfejsu , ale byliśmy świadomi, że wykraczamy poza to. Niezależnie od tego, czy uważasz to za „ewolucję interfejsu ++”, czy „cechy -”, jest kwestią osobistej interpretacji. Tak więc, odpowiadając na pytanie dotyczące bezpieczeństwa ... tak długo, jak trzymasz się tego, co faktycznie obsługuje mechanizm, zamiast próbować życzliwie rozciągać go do czegoś, czego nie obsługuje, powinno być dobrze.

Kluczowym celem projektu było to, że z punktu widzenia klienta interfejsu metody domyślne powinny być nie do odróżnienia od „zwykłych” metod interfejsu. Dlatego też domyślność metody jest interesująca tylko dla projektanta i implementatora interfejsu.

Oto kilka przypadków użycia, które mieszczą się w założeniach projektowych:

  • Ewolucja interfejsu. Tutaj dodajemy nową metodę do istniejącego interfejsu, który ma rozsądną domyślną implementację pod względem istniejących metod w tym interfejsie. Przykładem może być dodanie forEachmetody do Collection, gdzie domyślna implementacja jest zapisana w odniesieniu do iterator()metody.

  • Metody „opcjonalne”. W tym przypadku projektant interfejsu mówi: „Implementatorzy nie muszą implementować tej metody, jeśli chcą żyć z ograniczeniami funkcjonalności, które się z tym wiążą”. Na przykład, Iterator.removepodano wartość domyślną, która rzuca UnsupportedOperationException; ponieważ zdecydowana większość implementacji i tak Iteratorma takie zachowanie, domyślnie ta metoda jest zasadniczo opcjonalna. (Jeśli zachowanie z AbstractCollectionzostało wyrażone jako domyślne włączone Collection, moglibyśmy zrobić to samo dla metod mutacyjnych).

  • Wygodne metody. Są to metody, które są wyłącznie dla wygody, ponownie ogólnie zaimplementowane w kategoriach metod niedomyślnych w klasie. logger()Metoda w pierwszym przykładzie jest rozsądny tego ilustracją.

  • Kombinatory. Są to metody kompozycyjne, które tworzą nowe instancje interfejsu w oparciu o bieżącą instancję. Na przykład metody Predicate.and()lub Comparator.thenComparing()są przykładami kombinatorów.

Jeśli podajesz domyślną implementację, powinieneś również podać specyfikację domyślnej (w JDK używamy @implSpecdo tego znacznika javadoc), aby pomóc implementatorom w zrozumieniu, czy chcą przesłonić metodę, czy nie. Niektóre wartości domyślne, takie jak wygodne metody i kombinatory, prawie nigdy nie są nadpisywane; inne, takie jak metody opcjonalne, są często nadpisywane. Musisz dostarczyć wystarczającą specyfikację (nie tylko dokumentację) dotyczącą tego, co domyślnie obiecuje zrobić, aby osoba wdrażająca mogła podjąć rozsądną decyzję, czy musi ją zastąpić.

Brian Goetz
źródło
9
Dziękuję Brian za wyczerpującą odpowiedź. Teraz mogę używać domyślnych metod z lekkim sercem. Czytelnicy: więcej informacji od Briana Goetza na temat ewolucji interfejsu i domyślnych metod można znaleźć na przykład w NightHacking Worldwide Lambdas .
youri
Dzięki @ brian-goetz. Z tego, co powiedziałeś, myślę, że domyślne metody Javy są bliższe pojęciu tradycyjnych cech, jak zdefiniowano w artykule Ducasse et al ( scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). „Cechy” Scali w ogóle nie wydają mi się cechami, ponieważ mają stan, używają linearyzacji w swoim składzie i wydaje się, że również pośrednio rozwiązują konflikty metod - i to jest wszystko, czego tradycyjne cechy nie mają mieć. W rzeczywistości powiedziałbym, że cechy Scali bardziej przypominają mieszanki niż cechy. Co myślisz? PS: Nigdy nie kodowałem w Scali.
adino
A co powiesz na użycie pustych domyślnych implementacji metod w interfejsie, który działa jako nasłuchiwanie? Implementacja detektora może być zainteresowana tylko nasłuchiwaniem kilku metod interfejsu, więc ustawiając metody jako domyślne, implementujący musi zaimplementować tylko te metody, których potrzebuje.
Lahiru Chandima
1
@LahiruChandima Podoba Ci się MouseListener? W zakresie, w jakim ten styl interfejsu API ma sens, pasuje do segmentu „metody opcjonalne”. Upewnij się, że wyraźnie udokumentowałeś opcjonalność!
Brian Goetz
Tak. Tak jak w MouseListener. Dzięki za odpowiedzi.
Lahiru Chandima