Java >> Java Program >  >> Tag >> private

Hur förhindrar man klienten från att se interna privata klasser i Android-biblioteket?

Om du vägrar att flytta koden till en enskild, kontrollerad server är allt du kan göra att hindra klientprogrammeraren när du försöker använda dina API:er. Låt oss börja tillämpa god praxis på din design:

  1. Låt dina paket organiseras som de är nu.
  2. För varje klass du vill "gömma":

    • Gör det icke-offentligt.
    • Extrahera dess offentliga API till ett nytt, offentligt gränssnitt:

    public interface MyInterface {...}

    • Skapa en offentlig fabriksklass för att få ett objekt av den gränssnittstypen.

    public class MyFactory { public MyInterface createObject(); }

Hittills har du nu dina paket löst kopplade, och implementeringsklasserna är nu privata (som god praxis predikar, och du har redan sagt). Ändå är de fortfarande tillgängliga via gränssnitten och fabrikerna.

Så, hur kan du undvika att "främmande" klienter kör dina privata API:er? Vad som kommer härnäst är en kreativ, lite komplicerad, men ändå giltig lösning, baserad på hindrande klientprogrammerarna:

Ändra dina fabriksklasser:Lägg till en ny parameter till varje fabriksmetod:

public class MyFactory
{
    public MyInterface createObject(Macguffin parameter);
}

Så, vad är Macguffin ? Det är ett nytt gränssnitt du måste definiera i din applikation, med minst en metod:

public interface Macguffin
{
    public String dummyMethod();
}

Men ger inte någon användbar implementering av detta gränssnitt. På varje plats i din kod måste du ange en Macguffin objekt, skapa det genom en anonym klass:

MyFactory.getObject(new Macguffin(){
    public String dummyMethod(){
        return "x";
    }
});

Eller, ännu mer avancerat, genom ett dynamiskt proxyobjekt , så ingen ".class"-fil för denna implementering skulle hittas även om klientprogrammeraren vågar dekompilera koden.

Vad får du ut av detta? I grund och botten är det att avråda programmeraren från att använda en fabrik som kräver ett okänt, odokumenterat, obegripligt objekt. Fabriksklasserna bör bara bry sig om att inte ta emot ett null-objekt, och för att anropa dummymetoden och kontrollera returvärdet är den inte heller null (eller, om du vill ha en högre säkerhetsnivå, lägg till en odokumenterad hemlig-nyckel-regel).

Så den här lösningen bygger på en subtil förvirring av ditt API, för att avskräcka klientprogrammeraren att använda det direkt. Ju oklarare namnen på Macguffin-gränssnittet och dess metoder är, desto bättre.


Jag behöver generera ett bibliotek från detta, men jag vill inte att klienten ska se b_class. Den enda lösningen jag känner till är att platta ut mina vackert begripliga paket till ett enda paket och att använda standardpaketåtkomst för b_class. Finns det något annat sätt att göra det?

Ja, gör b_class paket-privat (standardåtkomst) och instansiera det via reflektion för användning i a_class .

Eftersom du känner till det fullständiga klassnamnet, ladda klassen:

Class<?> clz = Class.forName("b.b_class")

Hitta den konstruktor du vill anropa:

Constructor<?> con = clz.getDeclaredConstructor();

Tillåt dig själv att anropa konstruktorn genom att göra den tillgänglig:

con.setAccessible(true);

Anropa konstruktorn för att få din b_class exempel:

Object o = con.newInstance();

Hurra, nu har du en instans av b_class . Du kan dock inte ringa b_class s metoder på en instans av Object , så du har två alternativ:

  1. Använd reflektion för att anropa b_class s metoder (inte särskilt roligt, men lätt nog och kan vara ok om du bara har ett fåtal metoder med få parametrar).
  2. Har b_class implementera ett gränssnitt som du inte har något emot att klienten ser och casta din instans av b_class till det gränssnittet (läser jag mellan raderna misstänker jag att du kanske redan har ett sådant gränssnitt?).

Du kommer definitivt att vilja gå med alternativ 2 för att minimera din smärta om det inte får dig tillbaka till ruta ett igen (förorenar namnområdet med typer som du inte vill utsätta klienten för).

För fullständig avslöjande, två anmärkningar:

1) Det finns en (liten) overhead för att använda reflektion kontra direkt instansiering och anrop. Om du castar till ett gränssnitt betalar du bara kostnaden för reflektion över instansieringen. I vilket fall som helst är det troligen inte ett problem om du inte gör hundratusentals anrop i en snäv slinga.

2) Det finns inget som hindrar en målmedveten klient från att ta reda på klassnamnet och göra samma sak, men om jag förstår din motivation rätt vill du bara exponera ett rent API, så det här är egentligen inget bekymmer.


Java-tagg