sql >> Databasteknik >  >> NoSQL >> MongoDB

Designmönster för dataåtkomstskikt

Tja, det vanliga tillvägagångssättet för datalagring i java är, som du noterade, inte alls särskilt objektorienterat. Detta är i och för sig varken dåligt eller bra:"objektorientering" är varken fördel eller nackdel, det är bara ett av många paradigm som ibland hjälper till med bra arkitekturdesign (och ibland inte gör det).

Anledningen till att DAO:er i java vanligtvis inte är objektorienterade är precis vad du vill uppnå - att minska ditt beroende av den specifika databasen. I ett bättre designat språk, som möjliggjorde multipla arv, kan detta, eller naturligtvis, göras mycket elegant på ett objektorienterat sätt, men med java verkar det bara vara mer besvär än det är värt.

I en bredare mening hjälper tillvägagångssättet utan OO att frikoppla dina data på applikationsnivå från hur de lagras. Detta är mer än (icke) beroende av specifikationerna för en viss databas, men också lagringsscheman, vilket är särskilt viktigt när du använder relationsdatabaser (kom inte igång med ORM):du kan ha ett väldesignat relationsschema sömlöst översatt till applikationens OO-modell av din DAO.

Så, vad de flesta DAOs är i java nuförtiden är i huvudsak det du nämnde i början - klasser, fulla av statiska metoder. En skillnad är att istället för att göra alla metoder statiska, är det bättre att ha en enda statisk "fabriksmetod" (förmodligen i en annan klass), som returnerar en (singleton) instans av din DAO, som implementerar ett visst gränssnitt , som används av programkod för att komma åt 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);
     }
}

Varför göra det på detta sätt i motsats till statiska metoder? Tja, vad händer om du bestämmer dig för att byta till en annan databas? Naturligtvis skulle du skapa en ny DAO-klass och implementera logiken för din nya lagring. Om du använde statiska metoder skulle du nu behöva gå igenom all din kod, komma åt DAO och ändra den för att använda din nya klass, eller hur? Detta kan vara en enorm smärta. Och tänk om du ändrar dig och vill byta tillbaka till den gamla db?

Med detta tillvägagångssätt är allt du behöver göra att ändra GreatDAOFactory.getDAO() och få den att skapa en instans av en annan klass, så kommer all din applikationskod att använda den nya databasen utan några ändringar.

I det verkliga livet görs detta ofta utan några ändringar i koden alls:fabriksmetoden får implementeringsklassens namn via en egenskapsinställning och instansierar det med hjälp av reflektion, så allt du behöver göra för att byta implementeringar är att redigera en egenskap fil. Det finns faktiskt ramverk - som spring eller guice - som hanterar denna "beroendeinjektion"-mekanism åt dig, men jag kommer inte att gå in på detaljer, först eftersom det verkligen ligger utanför ramen för din fråga, och även för att jag inte nödvändigtvis är övertygad om att fördelen du får av att använda dessa ramverk är värt besväret att integrera med dem för de flesta applikationer.

En annan (förmodligen, mer sannolikt att utnyttjas) fördel med denna "fabriksmetod" i motsats till statisk är testbarhet. Föreställ dig att du skriver ett enhetstest som borde testa logiken i din App klass oberoende av underliggande DAO. Du vill inte att den ska använda någon verklig underliggande lagring av flera skäl (hastighet, att behöva ställa in den och städa upp efterord, möjliga kollisioner med andra tester, möjlighet att förorena testresultat med problem i DAO, inte relaterat till App , som faktiskt testas, etc.).

För att göra detta vill du ha ett testramverk, som Mockito , som låter dig "håna" funktionaliteten för alla objekt eller metoder, ersätta det med ett "dummy"-objekt, med fördefinierat beteende (jag hoppar över detaljerna, eftersom detta återigen ligger utanför räckvidden). Så du kan skapa detta dummy-objekt som ersätter din DAO och göra GreatDAOFactory returnera din dummy istället för den äkta varan genom att anropa GreatDAOFactory.setDAO(dao) före testet (och återställa det efter). Om du använde statiska metoder istället för instansklassen, skulle detta inte vara möjligt.

Ytterligare en fördel, som liknar att byta databaser som jag beskrev ovan, är att "pimpa upp" din dao med ytterligare funktionalitet. Anta att din applikation blir långsammare när mängden data i databasen växer och du bestämmer dig för att du behöver ett cachelager. Implementera en wrapper-klass, som använder den riktiga dao-instansen (tillhandahålls till den som en konstruktorparam) för att komma åt databasen, och cachelagrar objekten den läser i minnet, så att de kan returneras snabbare. Du kan sedan göra din GreatDAOFactory.getDAO instansiera detta omslag så att applikationen kan dra nytta av det.

(Detta kallas "delegeringsmönster" ... och verkar som smärta i baken, speciellt när du har många metoder definierade i din DAO:du måste implementera dem alla i omslaget, även för att ändra beteendet för bara en . Alternativt kan du helt enkelt underklassa din dao och lägga till caching till den på det här sättet. Detta skulle vara mycket mindre tråkig kodning i förväg, men kan bli problematiskt när du bestämmer dig för att ändra databasen, eller, ännu värre, att ha ett alternativ till byta implementeringar fram och tillbaka.)

Ett lika brett använt (men, enligt min mening, sämre) alternativ till "fabriksmetoden" är att göra dao en medlemsvariabel i alla klasser som behöver den:

public class App {
   GreatDao dao;
   public App(GreatDao d) { dao = d; }
}

På så sätt måste koden som instansierar dessa klasser instansiera dao-objektet (kan fortfarande använda fabriken) och tillhandahålla det som en konstruktorparameter. Ramverken för beroendeinjektion som jag nämnde ovan gör vanligtvis något liknande detta.

Detta ger alla fördelar med "fabriksmetoden", som jag beskrev tidigare, men, som jag sa, är inte lika bra enligt min mening. Nackdelarna här är att behöva skriva en konstruktor för var och en av dina appklasser, göra exakt samma sak om och om igen, och dessutom inte kunna instansiera klasserna lätt när det behövs, och en del förlorad läsbarhet:med en tillräckligt stor kodbas , en läsare av din kod, som inte är bekant med den, kommer att ha svårt att förstå vilken faktisk implementering av dao som används, hur den instansieras, om det är en singleton, en trådsäker implementering, om den behåller tillstånd eller cachar allt, hur besluten om att välja en viss implementering tas etc.




  1. Hur hittar man strängar med matchade bokstäver i list/array med lambda-funktionen?

  2. Hur avaktiverar jag alla rekordsträngar i mongodb med php?

  3. hur man får maxvärdet för ett fält i MongoDB

  4. Ladda upp filer till DEFAULT_FILE_STORAGE istället för GridFs med mongoengine