5

Mam kilka klas Java, które rozszerzają różne implementacje ogólnego interfejsu listy. Po prostu rejestrują wszystko, co jest dodawane do Listy.Jak mogę odwołać się do metody super w klasie Java, która implementuje interfejs, ale nie rozszerza innej klasy?

Lista LoggingArrayList jest pokazana poniżej. Jak sama nazwa wskazuje, rozszerza ona ArrayList. Klasa LoggingLinkedList jest identyczna, z wyjątkiem rozszerzenia LinkedList.

Moim głównym celem jest uniknięcie konieczności powielania całego wspólnego kodu, aby móc korzystać z innej klasy bazowej. Staram się przestrzegać zasady DRY (Nie powtarzaj się) w jak największym stopniu.

Przede wszystkim nie sugeruj lepszego sposobu logowania. To w ogóle nie jest moja prawdziwa aplikacja. To prosty sposób na pokazanie problemu, który mam.

Mam dwa ściśle powiązane pytania. Pierwszym jest pytanie w tytule. Jak mogę odwołać się do metody "super" w klasie Java, która implementuje interfejs, ale nie rozszerza innej klasy?

Klasa LoggingArrayList, jak pokazano poniżej, działa prawidłowo, ale kiedy zmienię deklarację klasy z ... rozszerza ArrayList na ... implementuje List, wtedy trzy odwołania do super.method() nie są już wywoływalne, stąd moje pierwsze pytanie .

Dobra odpowiedź na moje drugie pytanie prawie spowoduje pierwsze pytanie. Drugie pytanie brzmi: czy istnieje sposób zadeklarowania abstrakcyjnej klasy bazowej lub interfejsu poszerzającego listę o domyślne implementacje różnych metod add(), aby móc po prostu rozszerzyć tę abstrakcyjną klasę bazową lub zaimplementować ten interfejs i określić tylko jaki rodzaj Listy będzie podstawą konkretnej klasy?

Na przykład, chciałbym zrobić coś takiego:

interface LoggingList<T extends Object, L extends List<T>> extends L 
{ 
    // overloaded methods go here as shown below 
    // with overloaded methods declared as default for the interface 
} 

... wtedy mogę po prostu wdrożyć LoggingList jeden raz dla każdej konkretnej implementacji listy bez powielania wszystkich wspólnym kodzie. Klasy betonu może wtedy wyglądać tak, bez żadnego dodatkowego kodu potrzebnego w ich nawiasach klamrowych:

public class LoggingArrayList<T extends Object> implements LoggingList<T, ArrayList<T>> {} 
public class LoggingLinkedList<T extends Object> implements LoggingList<T, LinkedList<T>> {} 

Problem polega na tym, że definicja interfejsu jak zaproponowałem to jest nieważne (nie będzie kompilacji), a także, odniesienia do super.method w poniższym kodzie są niedostępne, chyba że zrobię LoggingList podklasą abstrakcyjną zamiast interfejsu, a potem skończę z powrotem tam, gdzie teraz jestem.

Z góry dziękuję za wszelkie pomysły na osiągnięcie mojego celu DRY.

Oto moja cała klasa LoggingArrayList.

public abstract class LoggingArrayList<T extends Object> 
    extends ArrayList<T> 
{ 
    protected void log(T e) 
    { 
     System.out.println(e == null ? "null" : e.toString()); 
    } 

    @Override 
    public boolean add(T e) { 
     log(e); 
     // How do I reference a super.method() 
     // in a class that implements an interface 
     // but does not extend another class? 
     return super.add(e); 
    } 

    @Override 
    public boolean addAll(Collection<? extends T> clctn) { 
     boolean anyChanges = false; 
     for(T e : clctn) 
     { 
      // ensure that we call our overridden version of add() 
      // so it gets logged. 
      anyChanges = anyChanges || add(e); 
     } 
     return anyChanges; 
    } 

    @Override 
    public boolean addAll(int i, Collection<? extends T> clctn) { 
     for(T e : clctn) 
     { 
      // ensure that we call our overridden version of add() 
      // so it gets logged. 
      add(i, e); 
      i++; // keep the inserted elements in their original order 
     } 
     return !clctn.isEmpty(); 
    } 

    @Override 
    public T set(int i, T e) { 
     log(e); 
     // How do I reference a super.method() 
     // in a class that implements an interface 
     // but does not extend another class? 
     return super.set(i, e); 
    } 

    @Override 
    public void add(int i, T e) { 
     log(e); 
     // How do I reference a super.method() 
     // in a class that implements an interface 
     // but does not extend another class? 
     super.add(i, e); 
    } 
} 
+0

Przedłużanie zbiorów to zawsze zła praktyka. W przypadku niektórych bardziej złożonych struktur istnieją również pułapki, których można uniknąć poprzez delegowanie zamiast rozszerzania. Spójrz na [ten artykuł na temat rozszerzania klas] (http://www.javaworld.com/article/2073649/core-java/why-extends-is-evil.html). – Dariusz

+0

Artykuł dobrze ilustruje Twój punkt widzenia. W rzeczywistości rozważałem już potencjał tego problemu w przypadku dwóch metod AddAll. Zauważysz, że już zapewniam, że moja własna implementacja metody add() jest wywoływana, zamiast zakładać, że klasa bazowa zawsze będzie ją wywoływać. Sądzę, że podejście delegowane ma co najmniej tę zaletę, że nie musi zastępować żadnej z implementacji klasy bazowej ani przyjmować żadnych założeń na jego temat. Mogę po prostu dodać moje logowanie bez dbania o to, co rzeczywista lista robi wewnętrznie. – David

Odpowiedz

3

Jedną z metod jest: Delegat.

Zamiast wielu implementacji dla różnych rodzajów list, można po prostu mieć jeden

public class LoggingList<T extends Object> implements List<T> 

    protected List<T> superList; 

    public LoggingList(List<T> anotherList) { 
    superList= anotherList; 
    } 

    protected void log(T e) { 
     System.out.println(e == null ? "null" : e.toString()); 
    } 

    @Override 
    public boolean add(T e) { 
     log(e); 
     return superList.add(e); 
    } 

a następnie zamiast dzwonić super. chcesz zadzwonić superList.

Czy nadal muszą dostosować swoje konstruktorów od new LoggingLinkedList() do new LoggingList(new LinkedList()); - ale to nie powinno być duże ...

+0

Nazywa się to również dekoratorem lub wzorem wzoru opakowania: https://en.wikipedia.org/wiki/Decorator_pattern – Puce

+0

Eclipse umożliwia automatyczne generowanie metod delegowania, * alt-s, m *. Po prostu wybierasz wszystkie metody swojej 'superList' w oknie, które się pojawi i gotowe. – Dariusz

+0

Dzięki za szybką odpowiedź. Zastanowiłem się nad tym podejściem, ale potem muszę przeciążać wszystkie metody List tylko po to, by wywołać odpowiednie metody SuperList. Wolałbym korzystać z dziedziczenia i nie niszczyć metod nienaruszonych, jeśli to możliwe. – David

1

Możesz użyć JDK proxy dla tego celu. Wystarczy google "jak używać klasy proxy jdk".

Here described a your use case.

+0

Dzięki Surgey. Jest to bardzo interesujące, chociaż nie jest to odpowiedź, której oczekiwałem. Nie widziałem już tej klasy wcześniej. Z pewnością zajrzę do niego i zobaczę, czy jest to przydatne dla mojej aplikacji. Mimo to chciałbym sprawdzić, czy istnieje sposób na zrobienie czegoś bardziej konwencjonalnego, takiego jak moje oryginalne podejście, tylko z poprawną składnią, jeśli koncepcja jest wykonalna. – David

+1

@Jan - Dynamiczne proxy ** proxy ** obiekty do ** implementacji ** interfejsów. Może łatwo owijać listę 'List' ** i ** implementować' List'. – OldCurmudgeon

+0

Masz rację. Źle to zrozumiałem. – Jan

1

Tak na przyszłość, oto pełna roztwór roboczy, który ułożyła stosując podejście proxy jak sugeruje Siergiej Morozow. Jego rdzeń jest mniejszy niż 50 linii kodu. Ponad połowa kodu zawartego na końcu to tylko test jednostkowy dla rzeczywistej funkcjonalności.

Nie jestem jeszcze pewien, czy ostatecznie zastosuję to podejście do mojego celu, ale było to bardzo przydatne ćwiczenie. Dzięki za sugestię, Siergiej.

public class LoggingListProxyFactory 
{ 
    protected static void log(String methodName, Object element) 
    { 
     System.out.println(methodName 
       + ": [" 
       + (element == null ? "null" : element.toString()) 
       + "]" 
     ); 
    } 

    public static <T extends Object> List<T> getProxy(final List<T> list) { 
     return (List<T>) Proxy.newProxyInstance(
       list.getClass().getClassLoader(), 
       new Class[]{ List.class }, 
       new LoggingInvocationHandler(list) 
     ); 
    } 

    private static class LoggingInvocationHandler<T> 
      implements InvocationHandler 
    { 
     final List<T> underlyingList; 

     public LoggingInvocationHandler(List<T> list) { 
      this.underlyingList = list; 
     } 

     @Override 
     public Object invoke(Object proxy, Method method, Object[] args) 
       throws Throwable 
     { 
      // These are the List interface methods that we want to log. 
      // In every case, the new elements happen to be the last parameter. 
      // 
      // boolean add(Object e) 
      // void add(int index, Object element) 
      // boolean addAll(Collection c) 
      // boolean addAll(int index, Collection c) 
      // Object set(int index, Object element) 
      String methodName = method.getName(); 
      if(("add".equals(methodName) 
       | "addAll".equals(methodName) 
       | "set".equals(methodName) 
       ) 
       // a few additional probably unnecessary checks 
       && args != null 
       && args.length == method.getParameterCount() 
       && method.getParameterCount() > 0 
       ) 
      { 
       log(methodName, args[args.length-1]); 
      } 
      return method.invoke(underlyingList, args); 
     } 
    } 

    public void testGetProxy() { 
     List<String>[] testLists = new List[] { 
      new ArrayList<>(), 
      new LinkedList<>() 
     }; 
     for(List<String> aList : testLists) 
     { 
      List<String> proxy = LoggingListProxyFactory.getProxy(aList); 

//   aList.add(42); // type is enforced at compile time 
      aList.add(aList.getClass().getSimpleName()); 
      aList.add("unlogged"); 
      aList.add(null); 

//   proxy.add(42); // type is enforced at compile time 
      proxy.add(proxy.getClass().getSimpleName()); 
      // exercise each the methods that are being logged 
      proxy.add("foo"); 
      proxy.add(0, "bar"); 
      proxy.add(null); 
      proxy.addAll(aList); 
      proxy.addAll(7, aList); 
      proxy.set(5, "five"); 

      System.out.println(); 
      System.out.println(aList.getClass().getSimpleName() 
        + ".size() = " + aList.size()); 
      aList.stream().forEach(System.out::println); 

      System.out.println(); 
      System.out.println("proxy.size() = " + proxy.size()); 
      proxy.stream().forEach(System.out::println); 
     } 
    } 
} 
Powiązane problemy