Pytanie w sprawie generics, polymorphism, java, overloading – Metoda akceptująca dwa różne typy jako parametr

25

Piszę metodę, która powinna przyjmować jako parametr obiekt jednego z dwóch typów, które nie współdzielą typu nadrzędnego innego niż Object. Na przykład typy to Dreams and Garlic. Możesz zrobić obadreams.crush() igarlic.crush(). Chcę mieć metodęutterlyDestroy(parameter), który zaakceptowałby jako parametr zarówno Dreams, jak i Garlic.

utterlyDestroy(parameter) {
    parameter.crush()
}

Zarówno czosnek, jak i sny są częścią jakiejś biblioteki, więc wprowadzenie ich do interfejsu ICrushable (tak, żebym mógł pisać)utterlyDestroy(ICrushable parameter) ) nie jest opcją.

Moja metoda jest dość długa, więc przeładowanie oznaczałoby powielenie kodu. Brzydki. Jestem pewien, że mógłbym użyć refleksji i zrobić trochę hakowania klasowego. Brzydki.

Próbowałem użyć generics, ale najwyraźniej nie mogę napisać czegoś takiego

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

Czy możliwe jest wpisanie czosnku w marzenia?

utterlyDestroy(Object parameter) {
    ((Dreams)parameter).crush()
}

To jednak nadal byłoby brzydkie. Jakie są moje inne opcje i jaka jest preferowana metoda radzenia sobie z sytuacją?

Twoja odpowiedź

8   odpowiedzi
29

Co powiesz na to:

interface ICrushable {
    void crush();
}

utterlyDestroy(ICrushable parameter) {
    // Very long crushing process goes here
    parameter.crush()
}

utterlyDestroy(Dreams parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

utterlyDestroy(Garlic parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

Nowe rozwiązanie powinno implementować interfejs ICrushable, ale dla istniejących klas parametr jest zawijany w ICrushable i przekazywany do utterlyDestroy (ICrushable), który wykonuje całą pracę.

2

przystosować się twoje typy do niego.

Berło:

public interface Crushable {
  public void crush();
}

Przykładowe wywołanie:

public class Crusher {
  public static void crush(Crushable crushable) {
    crushable.crush();
  }
}

Przykładowa metoda fabryki adapterów:

public final class Dreams {
  public static Crushable asCrushable(final Dream dream) {
    class DreamCrusher implements Crushable {
      @Override
      public void crush() {
        dream.crush();
      }
    }
    return new DreamCrusher();
  }

  private Dreams() {}
}

Kod konsumenta wygląda tak:

  Dream dream = new Dream();
  Crushable crushable = Dreams.asCrushable(dream);
  Crusher.crush(crushable);

Jeśli masz wiele typów do adaptacji, możesz rozważyć refleksję. Oto (niezoptymalizowana) fabryka adapterów, która używaPełnomocnik rodzaj:

public final class Crushables {
  private static final Class<?>[] INTERFACES = { Crushable.class };

  public static Crushable adapt(final Object crushable) {
    class Handler implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        return crushable.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(crushable, args);
      }
    }

    ClassLoader loader = Thread.currentThread()
        .getContextClassLoader();
    return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
  }

  private Crushables() {}
}

Dla konsumenta API to nie jest takie brzydkie:

  Dream dream = new Dream();
  Crushable crushable = Crushables.adapt(dream);
  Crusher.crush(crushable);

Jednakże, jak zwykle w przypadku refleksji, poświęcasz sprawdzanie typu kompilacji.

2

Po prostu użyj przeciążania metody.

public void utterlyDestroy(Dreams parameter) {
    parameter.crush();
}

public void utterlyDestroy(Garlic parameter) {
    parameter.crush();
}

Jeśli chcesz obsługiwać więcej niż te dwa typy w ten sam sposób, możesz zdefiniować dla nich wspólny interfejs i użyć generycznych.

Jak powiedziałem, moja metoda jest dość długa i oznaczałoby to duplikowanie kodu. JohnEye
@JohnEye - Wywołaj interfejs już w dwóch klasach. Zobacz także odpowiedź AadvarkSoup na wypadek, gdybyś nie był w stanie odpowiednio ustrukturyzować swojej hierarchii klas. Jirka Hanika
1

go projektu, proponuję zawinąć je w klasę, coś w rodzaju adaptera.

Nie, to tylko w jednym miejscu. Dziękuję za odpowiedź. JohnEye
6

A może coś tak prostego?

utterlyDestroy(Object parameter) {    
    if(parameter instanceOf Dreams){  
        Dream dream = (Dreams)parameter;  
        dream.crush();
        //Here you can use a Dream 
    }  
    else if(parameter instanceOf Garlic){  
       Garlic garlic = (Garlic)parameter;   
        //Here you can use a Garlic  
       garlic.crush();  
   }
} 

JeśliutterlyDestroy jest zbyt złożona i duża, a chcesz po prostu zadzwonićcrush wtedy robi to, co chcesz

Czy nie powtarzasz tutaj tego samego kodu? Nie ma dla mnie sensu. Akshay
Naturalnie przyszło mi to do głowy, ale użycie obiektu jako parametru wydaje się niewłaściwe. JohnEye
4

coś takiego:

class Either<L,R>
{
    private Object value;

    public static enum Side {LEFT, RIGHT}

    public Either(L left)  {value = left;}
    public Either(R right) {value = right;}

    public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}

    // Both return null if the correct side isn't contained.
    public L getLeft() {return value instanceof L ? (L) value : null;}
    public R getRight() {return value instanceof R ? (R) value : null;}
}

Następnie pozwól tej metodzie wziąć coś w rodzajuEither<Dreams, Garlic>.

0

Jak używam:

void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}

Ale działa to tylko wtedy, gdy Type2 można przekonwertować na Type1

1

podtypowanie Garlic lub Dreams jest opcją i dodanie interfejsu do podtypu?

Poza tym możesz umieścić wspólny kod w prywatnej metodzie, a dwie wersje utterlyDestroy zrobią to, co muszą zrobić z poszczególnymi obiektami przed wywołaniem wspólnego kodu. Jeśli metoda jest długa, prawdopodobnie i tak trzeba ją rozbić na metody prywatne. Zgaduję, że już o tym pomyślałeś, ponieważ jest to jeszcze bardziej oczywiste rozwiązanie niż dodanie interfejsu.

Możesz wprowadzić parametr jako obiekt, a następnie go rzucić. Czy to masz na myśli przez odbicie? to znaczy.,

public void utterlyCrush(Object crushable) {
    if (crushable instanceOf Dream) {
         ...
    }
    if (curshable instanceOf Garlic) {
         ...
    }

Ale rzucanie z Garlic to Dream nie jest opcją, zważywszy, że jeden nie jest podtypem drugiego.

Nie, przez refleksję miałem na myśli użycie niektórych fajnych funkcji, aby znaleźć metodę zwaną crush () i jej wykonanie lub coś podobnego. Napisałem to tylko po to, by zniechęcić „trolle refleksyjne” do odpowiedzi. JohnEye

Powiązane pytania