Bir arabirimi iki genel türle uygulayan bir Java sınıfı nasıl yapılır?


164

Genel bir arayüzüm var

public interface Consumer<E> {
    public void consume(E e);
}

İki tür nesne tüketen bir sınıf var, bu yüzden şöyle bir şey yapmak istiyorum:

public class TwoTypesConsumer implements Consumer<Tomato>, Consumer<Apple>
{
   public void consume(Tomato t) {  .....  }
   public void consume(Apple a) { ...... }
}

Görünüşe göre bunu yapamam.

Elbette gönderimi kendim uygulayabilirim, örn.

public class TwoTypesConsumer implements Consumer<Object> {
   public void consume(Object o) {
      if (o instanceof Tomato) { ..... }
      else if (o instanceof Apple) { ..... }
      else { throw new IllegalArgumentException(...) }
   }
}

Ancak, jeneriklerin sağladığı derleme zamanı tür denetleme ve gönderme çözümünü arıyorum.

Düşünebileceğim en iyi çözüm, ayrı arayüzler tanımlamaktır, örn.

public interface AppleConsumer {
   public void consume(Apple a);
}

İşlevsel olarak, bu çözüm tamam, sanırım. Sadece ayrıntılı ve çirkin.

Herhangi bir fikir?


Neden aynı temel tipin iki genel arayüzüne ihtiyacınız var?
akarnokd

6
Tür silme nedeniyle bunu yapamazsınız. Tüketiciyi uygulayan iki farklı sınıf tutun. Daha küçük sınıflar yapar ancak kodunuzu genel tutar (Kabul edilen cevabı kullanmayın, tüm kavramı kırar ... TwoTypesConsumer'a KÖTÜ olan bir tüketici gibi davranamazsınız).
Lewis Diamond

İşlevsel stil
impl

Yanıtlar:


78

Kapsüllemeyi düşünün:

public class TwoTypesConsumer {
    private TomatoConsumer tomatoConsumer = new TomatoConsumer();
    private AppleConsumer appleConsumer = new AppleConsumer();

    public void consume(Tomato t) { 
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) { 
        appleConsumer.consume(a);
    }

    public static class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato t) {  .....  }
    }

    public static class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple a) {  .....  }
    }
}

Bu statik iç sınıfları oluşturmak sizi rahatsız ediyorsa, anonim sınıfları kullanabilirsiniz:

public class TwoTypesConsumer {
    private Consumer<Tomato> tomatoConsumer = new Consumer<Tomato>() {
        public void consume(Tomato t) {
        }
    };

    private Consumer<Apple> appleConsumer = new Consumer<Apple>() {
        public void consume(Apple a) {
        }
    };

    public void consume(Tomato t) {
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) {
        appleConsumer.consume(a);
    }
}

2
bir şekilde kod çoğaltma gibi görünüyor ... Ben aynı sorunla karşılaştı ve temiz görünüyor başka bir çözüm bulunamadı.
bln-tom

109
Ama hiçbir sözleşme TwoTypesConsumeryapmıyor , ne anlamı var? Her iki türünü de isteyen bir yönteme geçirilemez . İki tip bir tüketicinin tüm fikri, domates tüketicisini isteyen bir yöntemin yanı sıra elma tüketicisini isteyen bir yönteme verebilmenizdir. Burada ikimiz de yok. Consumer
Jeff Axelrod

@JeffAxelrod İç sınıfları statik yapmazdım, böylece TwoTypesConsumergerekirse ek örneğe erişebilirler ve sonra twoTypesConsumer.getAppleConsumer()bir elma tüketicisi isteyen bir yönteme geçebilirsiniz . Başka bir seçenek, addConsumer(Producer<Apple> producer)TwoTypesConsumer'a benzer yöntemler eklemek olacaktır.
Herman

Arayüz üzerinde kontrolünüz yoksa (örn. Cxf / rs ExceptionMapper) bu
işe yaramaz

17
Ben söyleyeceğim: Bu Java ile bir kusur . Uygulamaların farklı argümanlar almaları şartıyla, aynı arayüzde birden fazla uygulamaya izin verilmememiz için hiçbir neden yoktur.
gromit190

41

Tür silme nedeniyle aynı arabirimi iki kez (farklı tür parametreleriyle) uygulayamazsınız.


6
Bunun nasıl bir sorun olduğunu görebiliyorum ... soru o zaman bu sorunu atlamanın en iyi (en verimli, güvenli, zarif) yolu.
daphshez

2
İş mantığına girmeden burada bir şey Ziyaretçi modeli gibi 'kokuyor'.
Shimi Bandiel

12

İşte Steve McLeod'in çözümüne dayanan olası bir çözüm :

public class TwoTypesConsumer {
    public void consumeTomato(Tomato t) {...}
    public void consumeApple(Apple a) {...}

    public Consumer<Tomato> getTomatoConsumer() {
        return new Consumer<Tomato>() {
            public void consume(Tomato t) {
                consumeTomato(t);
            }
        }
    }

    public Consumer<Apple> getAppleConsumer() {
        return new Consumer<Apple>() {
            public void consume(Apple a) {
                consumeApple(t);
            }
        }
    }
}

Sorunun örtük gereksinimi Consumer<Tomato>ve Consumer<Apple>durumu paylaşan nesnelerdi. Consumer<Tomato>, Consumer<Apple>Nesne ihtiyacı, bunları parametre olarak bekleyen diğer yöntemlerden gelir. Durumu paylaşmak için her ikisini de uygulamak için bir sınıfa ihtiyacım var.

Steve'in fikri, her biri farklı bir jenerik tip uygulayan iki iç sınıf kullanmaktı.

Bu sürüm, Tüketici arabirimini uygulayan nesneler için alıcılar ekler; bu, daha sonra onları bekleyen diğer yöntemlere aktarılabilir.


2
Birisi bunu kullanırsa: sık sık çağrılırsa Consumer<*>örnek alanlarındaki örnekleri depolamaya değer get*Consumer.
TWiStErRob

7

En azından, aşağıdakine benzer bir şey yaparak gönderim uygulamanızda küçük bir iyileştirme yapabilirsiniz:

public class TwoTypesConsumer implements Consumer<Fruit> {

Meyve domates ve elma atası olmak.


14
Teşekkürler, ancak profesyoneller ne derse desin, Domates'i meyve olarak görmüyorum. Ne yazık ki, Object dışında ortak bir taban sınıf yoktur.
daphshez

2
İstediğiniz zaman bir temel sınıf oluşturabilirsiniz: AppleOrTomato;)
Shimi Bandiel

1
Daha iyisi, Apple veya Domates'e delege olan bir Meyve ekleyin.
Tom Hawtin - çakmak hattı

@Tom: Söylediklerinizi yanlış anlamadığım sürece, öneriniz sorunu sadece ileriye iter, çünkü Meyvenin Elma veya Domates'e yetki verebilmesi için Meyvenin hem Apple hem de Domates için bir üst sınıf alanı olmalıdır temsil ettiği nesneye atıfta bulunarak.
Buhb

1
Bu, TwoTypesConsumer'ın şu anda uygulanmakta olan herhangi bir meyveyi tüketebileceğini ve gelecekte birisinin uygulayabileceğini ima eder.
Tom Gillen

3

sadece tökezledi. Aynı problemi yaşadım, ama farklı bir şekilde çözdüm: Sadece böyle yeni bir Arayüz oluşturdum

public interface TwoTypesConsumer<A,B> extends Consumer<A>{
    public void consume(B b);
}

ne yazık ki, bu tüm Mantık'a karşı olduğu gibi Consumer<A>DEĞİLDİR Consumer<B>. Bu nedenle, sınıfınızdaki ikinci tüketici için küçük bir Adaptör oluşturmanız gerekir.

public class ConsumeHandler implements TwoTypeConsumer<A,B>{

    private final Consumer<B> consumerAdapter = new Consumer<B>(){
        public void consume(B b){
            ConsumeHandler.this.consume(B b);
        }
    };

    public void consume(A a){ //...
    }
    public void conusme(B b){ //...
    }
}

a Consumer<A>gerekiyorsa, basitçe geçebilirsiniz thisve gerekirse Consumer<B>sadece geçconsumerAdapter


Daphna'nın yanıtı aynı, ancak daha temiz ve daha az kıvrımlı.
TWiStErRob

1

Genel sınıfların silinmesi ve yinelenen arabirim bildirimi nedeniyle aşağıdaki sınıf tanımı derlenemediğinden bunu doğrudan bir sınıfta yapamazsınız.

class TwoTypesConsumer implements Consumer<Apple>, Consumer<Tomato> { 
 // cannot compile
 ...
}

Aynı tüketme işlemlerini bir sınıfta paketlemek için başka herhangi bir çözüm, sınıfınızı şu şekilde tanımlamayı gerektirir:

class TwoTypesConsumer { ... }

her iki işlemin tanımını tekrarlamanız / çoğaltmanız gerektiğinden anlamsızdır ve arabirimden referans alınmaz. Bunu yaparken IMHO kaçınmaya çalışıyorum kötü bir küçük ve kod çoğaltma.

Bu aynı zamanda bir sınıfta 2 farklı nesneyi tüketmek için çok fazla sorumluluk bulunduğunun bir göstergesi olabilir (eşleşmemişlerse).

Ancak yaptığım ve yapabileceğiniz şey, bağlı tüketicileri aşağıdaki şekilde oluşturmak için açık fabrika nesnesi eklemektir:

interface ConsumerFactory {
     Consumer<Apple> createAppleConsumer();
     Consumer<Tomato> createTomatoConsumer();
}

Eğer gerçekte bu tipler gerçekten bağlıysa (ilgili) o zaman böyle bir uygulama yaratmanızı tavsiye ederim:

class TwoTypesConsumerFactory {

    // shared objects goes here

    private class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato tomato) {
            // you can access shared objects here
        }
    }

    private class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple apple) {
            // you can access shared objects here
        }
    }


    // It is really important to return generic Consumer<Apple> here
    // instead of AppleConsumer. The classes should be rather private.
    public Consumer<Apple> createAppleConsumer() {
        return new AppleConsumer();
    }

    // ...and the same here
    public Consumer<Tomato> createTomatoConsumer() {
        return new TomatoConsumer();
    }
}

Avantajı, fabrika sınıfının her iki uygulamayı da bilmesidir, paylaşılan bir durum vardır (gerekirse) ve gerekirse daha fazla bağlı tüketici iade edebilirsiniz. Arabirimden türetilmeyen yinelenen bir tüketme yöntemi bildirimi yoktur.

Tamamen ilgili değilse, her bir tüketicinin bağımsız (hala özel) sınıf olabileceğini lütfen unutmayın.

Bu çözümün dezavantajı daha yüksek bir sınıf karmaşıklığıdır (bu bir java dosyası olsa bile) ve tüketme yöntemine erişmek için aşağıdakiler yerine bir çağrı daha yapmanız gerekir:

twoTypesConsumer.consume(apple)
twoTypesConsumer.consume(tomato)

var:

twoTypesConsumerFactory.createAppleConsumer().consume(apple);
twoTypesConsumerFactory.createTomatoConsumer().consume(tomato);

Özetlemek gerekirse, 2 iç sınıf kullanarak bir üst düzey sınıfta 2 genel tüketici tanımlayabilirsiniz, ancak arama durumunda ilk olarak uygun uygulayıcı tüketiciye bir referans almanız gerekir, çünkü bu sadece bir tüketici nesnesi olamaz.


1

Fonksiyonel tarzda, arayüzü uygulamadan bunu yapmak oldukça kolaydır ve ayrıca zaman tipi kontrolünü derler.

Varlığı tüketmek için fonksiyonel arayüzümüz

@FunctionalInterface
public interface Consumer<E> { 
     void consume(E e); 
}

yöneticimiz varlığı uygun şekilde işlemek ve tüketmek

public class Manager {
    public <E> void process(Consumer<E> consumer, E entity) {
        consumer.consume(entity);
    }

    public void consume(Tomato t) {
        // Consume Tomato
    }

    public void consume(Apple a) {
        // Consume Apple
    }

    public void test() {
        process(this::consume, new Tomato());
        process(this::consume, new Apple());
    }
}

0

Daha fazla sınıf kullanmaktan kaçınmak için başka bir alternatif. (java8 + kullanan örnek)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

0

Eski soruları cevapladığım için üzgünüm, ama gerçekten çok seviyorum! Bu seçeneği deneyin:

public class MegaConsumer implements Consumer<Object> {

  Map<Class, Consumer> consumersMap = new HashMap<>();
  Consumer<Object> baseConsumer = getConsumerFor(Object.class);

  public static void main(String[] args) {
    MegaConsumer megaConsumer = new MegaConsumer();
    
    //You can load your customed consumers
    megaConsumer.loadConsumerInMapFor(Tomato.class);
    megaConsumer.consumersMap.put(Apple.class, new Consumer<Apple>() {
        @Override
        public void consume(Apple e) {
            System.out.println("I eat an " + e.getClass().getSimpleName());
        }
    });
    
    //You can consume whatever
    megaConsumer.consume(new Tomato());
    megaConsumer.consume(new Apple());
    megaConsumer.consume("Other class");
  }

  @Override
  public void consume(Object e) {
    Consumer consumer = consumersMap.get(e.getClass());
    if(consumer == null) // No custom consumer found
      consumer = baseConsumer;// Consuming with the default Consumer<Object>
    consumer.consume(e);
  }

  private static <T> Consumer<T> getConsumerFor(Class<T> someClass){
    return t -> System.out.println(t.getClass().getSimpleName() + " consumed!");
  }

  private <T> Consumer<T> loadConsumerInMapFor(Class<T> someClass){
    return consumersMap.put(someClass, getConsumerFor(someClass));
  }
}

Bence aradığınız şey bu.

Bu çıktıyı alırsınız:

Domates tükendi!

Bir elma yerim

Dize tüketildi!


Soru: "Ama derleme zamanı tip kontrol arıyorum ..."
aeracode

@aeracode OP'nin istediklerini yapmak için seçenek yok. Tür silme, aynı arabirimi farklı tür değişkenleriyle iki kez uygulamak için imkansız hale getirir. Sana sadece başka bir yol vermeye çalışıyorum. Tabii ki bir onbject tüketmek için daha önce kabul edilen türleri kontrol edebilirsiniz.
Awes0meM4n
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.