Saya menggunakan Guice dalam aplikasi desktop dan saya ingin menambahkan binding generik untuk layanan. Layanan ini adalah instans tunggal (terikat) dan dibuat selama startup aplikasi secara manual. Saya menggunakan wadah IoC hanya untuk membuat GUI. Saya membuat layanan ini secara manual karena saat memulai saya ingin mempublikasikan kemajuannya.

Karena GUI menggunakan layanan ini, mereka harus diikat ke modul GUI guice.

Saya tidak bisa memikirkan cara untuk mengikatnya tanpa menggunakan kelas dengan penyetel dan pengambil untuk setiap kelas.

Katakanlah saya memiliki CarService dan EngineService. Yang saya miliki sekarang adalah:

public class GuiceServices {

    public static void main(String[] args) {
        ServicesModule servicesModule = new ServicesModule();

        CarService carService = new CarServiceImpl();
        servicesModule.setCarService(carService);
        System.out.println("progress: 50%");

        EngineService engineService = new EngineServiceImpl();
        servicesModule.setEngineService(engineService);
        System.out.println("Progress: 100%");

        Injector i = Guice.createInjector(new GuiModule(), servicesModule);
        i.getInstance(MainView.class).show();
    }

    class ServicesModule extends AbstractModule {
        private CarService carService;
        private EngineService engineService;
        @Override
        protected void configure() {
            
        }
        
        public void setCarService(CarService carService) {
            this.carService = carService;
        }
        
        public void setEngineService(EngineService engineService) {
            this.engineService = engineService;
        }
        
        @Provides
        public CarService getCarService() {
            return carService;
        }
        
        @Provides
        public EngineService getEngineService() {
            return engineService;
        }
    }
}

Tapi itu agak menyakitkan karena layanan ini banyak.

Apakah ada cara untuk menghindari hal ini?

Idealnya, peta lebih nyaman. Sesuatu seperti:

public class GuiceServices {

    public static void main(String[] args) {
        ServicesMap servicesMap = new ServicesMap();

        CarService carService = new CarServiceImpl();
        servicesMap.put(CarService.class, carService);
        System.out.println("progress: 50%");

        EngineService engineService = new EngineServiceImpl();
        servicesMap.put(EngineService.class, engineService);
        System.out.println("Progress: 100%");

        Injector i = Guice.createInjector(new GuiModule(), new ServicesModule(servicesMap));
        i.getInstance(MainView.class).show();
    }

    class ServicesModule extends AbstractModule {
        private ServicesMap services;
        public SerrvicesModule(ServicesMap services) {
            this.services = services;
        }
        @Override
        protected void configure() {
            for (Class<?> serviceType : services.keySet())
            {
                bind(serviceType).toInstance(services.get(serviceType));
            }
        }       
    }
}

Tetapi saya tidak dapat menemukan cara untuk membuat-mengimplementasikan "servicesMap" ini. Karena metode bind mengembalikan pembuat generik. Apakah Guice (atau Guava) menyediakan sesuatu untuk kasus seperti ini?

Saya tahu bahwa saya dapat menggunakan Guice untuk membuat layanan dan mempublikasikan kemajuan menggunakan pendengar injeksi/tipe, tetapi paket bisnis saya (modul) yang berisi semua layanan tidak memiliki ketergantungan javax.inject. Plus, pembuatan layanan ini rumit sehingga lebih baik membuatnya secara manual. Selain itu, memublikasikan kemajuan GUI dalam modul Guice terdengar terlalu rumit untuk berada dalam modul Guice.

Jadi, apakah ada cara? Alih-alih System.out.println dalam cuplikan di atas, ada juga layar pembuka yang dibuat secara manual.

0
George Z. 5 Januari 2021, 22:24

3 jawaban

Jawaban Terbaik

Saya sendiri sebenarnya mencapainya.

Apa yang saya lakukan adalah membuat List<Consumer<Binder>> di dalam modul dan kemudian memasukkannya ke dalam metode configure().

class ServicesModule extends AbstractModule {
    private List<Consumer<Binder>> consumers = new ArrayList<>();

    ServicesModule() {
    }

    @Override
    protected void configure() {
        consumers.forEach(c -> c.accept(binder()));
    }

    <T> void putService(Class<T> clazz, T instance) {
        consumers.add(t -> t.bind(clazz).toInstance(instance));
    }

}

Dan kemudian, selama aplikasi dimulai, saya dapat memberi makan dependensi layanan secara bertahap:

public static void main(String[] args) {
    ServicesModule services = new ServicesModule();
    
    CarService carService = new CarServiceImpl();
    serviceModule.putService(CarService.class, carService);
    publishProgress(35);
    
    EngineService engineService = new EngineServiceImpl(carService);
    serviceModule.putService(EngineService.class, engineService);
    publishProgres(50);
    //...
    Injector i = Guice.createInjector(new GuiModule(), services);
    i.getInstance(MainView.class).show();
}
-1
George Z. 8 Januari 2021, 14:19

Cukup pindahkan kode yang membangun setiap impl layanan di dalam isi metode @Provides untuk antarmuka layanan yang sesuai. Anda menyebutkan ingin ini menjadi lajang, jadi Anda juga ingin membubuhi keterangan metode penyedia dengan @Singleton.

Untuk peta, Anda dapat melakukan hal seperti itu dengan Multibinder, tetapi saya ingin memahami desain Anda lebih baik sebelum merekomendasikannya.

0
isomeme 6 Januari 2021, 07:52

Jawaban yang Anda usulkan membutuhkan pengetahuan tentang dependensi kelas-kelas layanan-impl untuk disebarkan di luar kelas-kelas itu. Seluruh ide injeksi dependensi adalah untuk merangkum dependensi implementasi. Misalnya, hanya EngineServiceImpl yang harus tahu bahwa itu bergantung pada CarService. Enkapsulasi membuat kode Anda lebih mudah untuk dipikirkan dan diuji.

class ServiceModule extends AbstractModule {
  protected void configure() {
    bind(CarService.class).to(CarServiceImpl.class).in(Singleton.class);
    bind(EngineService.class).to(EngineServiceImpl.class).in(Singleton.class);
  }
}

class CarServiceImpl implements CarService {
  @Inject
  CarServiceImpl() {} // Not necessary, but adds clarity

  // ...
}

class EngineServiceImpl implements EngineService {
  private final CarService carService;

  @Inject
  EngineServiceImpl(CarService carService) {
    this.carService = carService;
  }

  // ...
}

class Main {
  public static void main(String[] args) {
    // I find it clearer to get the starting-point class instance from the injector
    // creation chain, then operate on that instance.
    MainView mainView = 
        Guice.createInjector(new GuiModule(), new ServiceModule())
            .getInstance(MainView.class);
    mainView.show();
  }
}
-1
isomeme 9 Januari 2021, 00:32