public class DataManager {
private Database database;
public DataManager(Database db) { this.database = db; }
public Data getData() { return database.getData(); }
}
Tomáš Pitner, Radek Ošlejšek, Marek Šabo, Jakub Čecháček pitner@muni.cz
Dependency Injection (DI) je postup, kdy deklarativním způsobem (přímo uvnitř javového kódu nebo vnějším popisovačem, např. XML) označíme, kde je očekáváno na začátku životního cyklu objektu vložení odkazu na objekt, na němž náš závisí - tedy vložení závislosti.
Příklad použití závislosti:
objekt manažeru dat závisí na objektu databáze = potřebuje ho pro svou činnost (metodu getData
);
objekt databáze se do něj dostane už při konstrukci = na začátku životního cyklu manažeru;
objekt database
může nebo nemusí (časteji) být po dobu života manažeru vyměněn.
Výhodné je, když je Database
rozhraní - může být implementováno různými třídami.
public class DataManager {
private Database database;
public DataManager(Database db) { this.database = db; }
public Data getData() { return database.getData(); }
}
Nastavení závislosti metodou set
Nevýhodou je, že mezi konstrukcí a nastavením závislosti je objekt manažeru očividně nepoužitelný.
public class DataManager {
private Database database;
public DataManager() { }
public void setDatabase(Database db) { this.database = db; }
public Data getData() { return database.getData(); }
}
Nastavení závislosti musíme provést sami.
Nejdříve vytvoř prvý objekt - databáze, poté druhý objekt (manažer dat) a propoj závislost.
public static void main(String[] args) {
// database is hardwired here - only MyDatabase is used & no other
Database db = new MyDatabase(...);
DataManager manager = new DataManager();
manager.setDatabase(db);
// now the manager is ready to give data
Data data = manager.getData();
}
Existují nástroje, které zvnejšku zajistí, že do objektu je přidán vhodný objekt závislosti.
Například do manažeru dat je přidána správná databáze = vhodný objekt implementující rozhraní Database
.
Vnější vložení závislosti je posláním rámců (kontejnerů) pro vkládání závislostí, dependency injection frameworks.
Velmi jednoduchý rámec pro DI je Google Guice
Populární je Spring Framework, ale ten je významně obsáhlejší a kvůli samotnému DI je kanónem na vrabce.
Příklad viz https://github.com/google/guice/wiki/Motivation
Třída RealBillingService
závisí na CreditCardProcessor
a TransactionLog
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
@Inject
public RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result);
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
Náš Module musí implementovat rozhraní Guice Module
V konfiguraci přiřadíme každému rozhraní (vlevo) třídu (vpravo), kterou v jeho roli budeme používat.
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
bind(BillingService.class).to(RealBillingService.class);
}
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
... // the TransactionLog and CreditCardProcessor follow
}
Finalizér, tzn. metoda finalize()
vlastní všem objektům, může teoreticky být překryta a tím umožněn adekvátní "likvidační" postup při zániku objektu - sestávající obvykle z uvolnění systémových zdrojů - síťové sokety, spojení na databázi atd.
V Javě nicméně není zaručeno, že se finalizér skutečně zavolá - JVM jej nemusí volat, pokud nepotřebuje fyzicky uvolnit paměť obsazenou (již nepoužívaným) objektem.
I kdyby finalizér zavolán byl, zůstává problém s určením okamžiku, kdy je volán a v jakém pořadí jsou finalizéry na mrtvých objektech volány.
Celkově tedy na finalize()
nespoléhat a nepoužívat je - zdroje uvolňovat explicitním zavoláním vhodné metody.