Nå, den almindelige tilgang til datalagring i java er, som du bemærkede, slet ikke særlig objektorienteret. Dette er i og for sig hverken dårligt eller godt:"objektorienterethed" er hverken fordel eller ulempe, det er blot et af mange paradigmer, der nogle gange hjælper med godt arkitekturdesign (og nogle gange ikke gør).
Grunden til, at DAO'er i java normalt ikke er objektorienterede, er præcis, hvad du ønsker at opnå - at slappe af din afhængighed af den specifikke database. I et bedre designet sprog, der gav mulighed for multipel nedarvning, kan dette, eller selvfølgelig, gøres meget elegant på en objektorienteret måde, men med java ser det ud til at være mere besvær, end det er værd.
I en bredere forstand hjælper ikke-OO-tilgangen med at afkoble dine data på applikationsniveau fra den måde, de er gemt på. Dette er mere end (ikke) afhængighed af detaljerne i en bestemt database, men også lagringsskemaerne, hvilket er særligt vigtigt, når du bruger relationelle databaser (få mig ikke i gang med ORM):du kan have et veldesignet relationsskema problemfrit oversat til applikationens OO-modellen af din DAO.
Så det, de fleste DAO'er er i java i dag, er i bund og grund det, du nævnte i begyndelsen - klasser, fulde af statiske metoder. En forskel er, at i stedet for at gøre alle metoderne statiske, er det bedre at have en enkelt statisk "fabriksmetode" (sandsynligvis i en anden klasse), der returnerer en (singleton) instans af din DAO, som implementerer en bestemt grænseflade , brugt af applikationskode til at få adgang til databasen:
public interface GreatDAO {
User getUser(int id);
void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
protected TheGeatestDAO(){}
...
}
public class GreatDAOFactory {
private static GreatDAO dao = null;
protected static synchronized GreatDao setDAO(GreatDAO d) {
GreatDAO old = dao;
dao = d;
return old;
}
public static synchronized GreatDAO getDAO() {
return dao == null ? dao = new TheGreatestDAO() : dao;
}
}
public class App {
void setUserName(int id, String name) {
GreatDAO dao = GreatDAOFactory.getDao();
User u = dao.getUser(id);
u.setName(name);
dao.saveUser(u);
}
}
Hvorfor gøre det på denne måde i modsætning til statiske metoder? Tja, hvad hvis du beslutter dig for at skifte til en anden database? Naturligvis ville du oprette en ny DAO-klasse, der implementerer logikken for dit nye lager. Hvis du brugte statiske metoder, ville du nu skulle gennemgå al din kode, få adgang til DAO og ændre den til at bruge din nye klasse, ikke? Dette kan være en kæmpe smerte. Og hvad hvis du så ombestemmer dig og vil skifte tilbage til den gamle db?
Med denne tilgang er alt hvad du skal gøre at ændre GreatDAOFactory.getDAO()
og få det til at oprette en instans af en anden klasse, og al din applikationskode vil bruge den nye database uden ændringer.
I det virkelige liv gøres dette ofte uden ændringer i koden overhovedet:fabriksmetoden henter implementeringsklassens navn via en egenskabsindstilling og instansierer det ved hjælp af refleksion, så alt hvad du skal gøre for at skifte implementering er at redigere en egenskab fil. Der er faktisk rammer - som spring
eller guice
- der styrer denne "afhængighedsinjektion"-mekanisme for dig, men jeg vil ikke gå i detaljer, først, fordi det virkelig ligger uden for dit spørgsmåls rammer, og også fordi jeg ikke nødvendigvis er overbevist om, at den fordel, du får ved at bruge disse rammer er besværet værd at integrere med dem til de fleste applikationer.
En anden (sandsynligvis, mere tilbøjelig til at blive udnyttet) fordel ved denne "fabrikstilgang" i modsætning til statisk er testbarhed. Forestil dig, at du skriver en enhedstest, som skal teste logikken i din App
klasse uafhængigt af enhver underliggende DAO. Du ønsker ikke, at den skal bruge noget reelt underliggende lager af flere årsager (hastighed, at skulle sætte det op og rydde op efterord, mulige kollisioner med andre tests, mulighed for at forurene testresultater med problemer i DAO, ikke relateret til App
, som faktisk bliver testet osv.).
For at gøre dette, vil du have en testramme, såsom Mockito
, der giver dig mulighed for at "håne" funktionaliteten af ethvert objekt eller metode, og erstatte det med et "dummy" objekt med foruddefineret adfærd (jeg springer detaljerne over, fordi dette igen er uden for rækkevidden). Så du kan oprette dette dummy-objekt, der erstatter din DAO, og lave GreatDAOFactory
returner din dummy i stedet for den ægte vare ved at kalde GreatDAOFactory.setDAO(dao)
før testen (og genoprette den efter). Hvis du brugte statiske metoder i stedet for instansklassen, ville dette ikke være muligt.
Endnu en fordel, som minder om at skifte database, jeg beskrev ovenfor, er at "pimpe" din dao med yderligere funktionalitet. Antag, at din applikation bliver langsommere, efterhånden som mængden af data i databasen vokser, og du beslutter dig for, at du har brug for et cachelag. Implementer en wrapper-klasse, der bruger den rigtige dao-instans (leveret til den som en konstruktørparam) til at få adgang til databasen, og cacher de objekter, den læser i hukommelsen, så de kan returneres hurtigere. Du kan derefter lave din GreatDAOFactory.getDAO
instansiere denne indpakning, for at applikationen kan drage fordel af den.
(Dette kaldes "delegationsmønster" ... og virker som smerte i numsen, især når du har mange metoder defineret i din DAO:du bliver nødt til at implementere dem alle i indpakningen, selv for at ændre adfærd for kun én Alternativt kan du ganske enkelt underklassificere din dao og tilføje caching til den på denne måde. Dette ville være meget mindre kedelig kodning på forhånd, men kan blive problematisk, når du beslutter dig for at ændre databasen, eller endnu værre, at have en mulighed for skifter implementeringer frem og tilbage.)
Et lige så bredt brugt (men efter min mening ringere) alternativ til "fabriksmetoden" er at lave dao
en medlemsvariabel i alle klasser, der har brug for det:
public class App {
GreatDao dao;
public App(GreatDao d) { dao = d; }
}
På denne måde skal koden, der instansierer disse klasser, instantiere dao-objektet (kan stadig bruge fabrikken) og give det som en konstruktørparameter. De afhængighedsinjektionsrammer, jeg nævnte ovenfor, gør normalt noget, der ligner dette.
Dette giver alle fordelene ved "fabriksmetoden", som jeg beskrev tidligere, men som jeg sagde, er den ikke så god efter min mening. Ulemperne her er at skulle skrive en konstruktør for hver af dine appklasser, gøre det samme præcist igen og igen, og heller ikke være i stand til nemt at instansiere klasserne, når det er nødvendigt, og en vis mistet læsbarhed:med en stor nok kodebase , vil en læser af din kode, der ikke er bekendt med den, have svært ved at forstå, hvilken faktisk implementering af dao'en der bruges, hvordan den instansieres, om det er en singleton, en trådsikker implementering, om den bevarer tilstanden eller cacher noget, hvordan beslutningerne om at vælge en bestemt implementering træffes osv.