Apakah ada cara untuk mensimulasikan konsep 'teman' C ++ di Jawa?

196

Saya ingin dapat menulis kelas Java dalam satu paket yang dapat mengakses metode non-publik dari kelas dalam paket lain tanpa harus membuatnya menjadi subclass dari kelas lain. Apakah ini mungkin?

Matthew Murdoch
sumber

Jawaban:

466

Berikut ini adalah trik kecil yang saya gunakan di JAWA untuk mereplikasi mekanisme teman C ++.

Katakanlah saya punya kelas Romeodan kelas lain Juliet. Mereka berada dalam paket yang berbeda (keluarga) karena alasan kebencian.

Romeoingin cuddle Julietdan Julietingin hanya membiarkannya Romeo cuddle.

Dalam C ++, Julietakan menyatakan Romeosebagai (kekasih) friendtetapi tidak ada hal seperti itu di java.

Inilah kelas dan triknya:

Wanita pertama:

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }

}

Jadi metode Juliet.cuddleadalah publictetapi Anda perlu Romeo.Loveuntuk menyebutnya. Ini menggunakan ini Romeo.Lovesebagai "tanda tangan keamanan" untuk memastikan bahwa hanya Romeodapat memanggil metode ini dan memeriksa bahwa cinta itu nyata sehingga runtime akan membuang NullPointerExceptionjika itu null.

Sekarang anak laki-laki:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();

    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

Kelas Romeo.Lovebersifat publik, tetapi konstruktornya adalah publik private. Karena itu siapa pun dapat melihatnya, tetapi hanya Romeodapat membuatnya. Saya menggunakan referensi statis sehingga Romeo.Loveyang tidak pernah digunakan hanya dibangun sekali dan tidak berdampak optimasi.

Oleh karena itu, Romeobisa cuddle Julietdan hanya dia bisa karena hanya dia bisa membangun dan akses sebuah Romeo.Lovecontoh, yang dibutuhkan oleh Julietuntuk cuddledia (atau dia akan menampar Anda dengan NullPointerException).

Salomon BRYS
sumber
107
+1 untuk "menampar Anda dengan NullPointerException". Sangat mengesankan.
Nickolas
2
@Steazy There adalah: mencari anotasi NotNull, NonNull, dan CheckForNull. Periksa dengan dokumentasi IDE Anda untuk mengetahui cara menggunakan dan menegakkan anotasi tersebut. Saya tahu bahwa IntelliJ menyematkan ini secara default dan gerhana itu membutuhkan plugin (seperti FindBugs).
Salomon BRYS
27
Anda dapat membuat Romeo's Loveuntuk Juliakekal dengan mengubah lovebidang menjadi final;-).
Matthias
5
@Matthias Bidang cinta statis ... Saya akan mengedit jawaban untuk membuatnya final;)
Salomon BRYS
12
Semua jawaban harus seperti ini (Y) +1 untuk humor dan contoh yang bagus.
Zia Ul Rehman Mughal
54

Para perancang Java secara eksplisit menolak gagasan tentang pertemanan karena ia bekerja di C ++. Anda memasukkan "teman" Anda dalam paket yang sama. Keamanan pribadi, yang dilindungi, dan dikemas diberlakukan sebagai bagian dari desain bahasa.

James Gosling ingin Java menjadi C ++ tanpa kesalahan. Saya percaya dia merasa teman itu kesalahan karena melanggar prinsip OOP. Paket memberikan cara yang masuk akal untuk mengatur komponen tanpa terlalu murni tentang OOP.

NR menunjukkan bahwa Anda bisa curang menggunakan refleksi, tetapi bahkan itu hanya berfungsi jika Anda tidak menggunakan SecurityManager. Jika Anda mengaktifkan keamanan standar Java, Anda tidak akan bisa menipu dengan refleksi kecuali Anda menulis kebijakan keamanan untuk mengizinkannya secara khusus.

David G
sumber
11
Saya tidak bermaksud menjadi pedant, tetapi pengubah akses bukan mekanisme keamanan.
Greg D
6
Pengubah akses adalah bagian dari model keamanan java. Saya secara khusus merujuk ke java.lang.RuntimePermission untuk refleksi: accessDeclaredMembers dan accessClassInPackage.
David G
54
Jika Gosling benar-benar berpikir bahwa itu friendmelanggar OOP (khususnya, lebih dari akses paket tidak) maka dia benar-benar tidak memahaminya (sangat mungkin, banyak orang salah paham).
Konrad Rudolph
8
Komponen kelas terkadang perlu dipisahkan (mis. Implementasi dan API, objek inti dan adaptor). Perlindungan tingkat paket pada saat yang sama terlalu permisif dan terlalu ketat untuk melakukan ini dengan benar.
dhardy
2
@GregD Mereka dapat dianggap sebagai mekanisme keamanan dalam arti bahwa mereka membantu mencegah pengembang menggunakan anggota kelas yang salah. Saya pikir mereka mungkin lebih baik disebut sebagai mekanisme keselamatan .
naksir
45

Konsep 'teman' berguna di Jawa, misalnya, untuk memisahkan API dari implementasinya. Adalah umum untuk kelas implementasi membutuhkan akses ke internal kelas API tetapi ini tidak boleh diekspos ke klien API. Ini dapat dicapai dengan menggunakan pola 'Friend Accessor' sebagaimana dirinci di bawah ini:

Kelas diekspos melalui API:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

Kelas yang menyediakan fungsionalitas 'teman':

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

Contoh akses dari kelas dalam paket implementasi 'teman':

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
Matthew Murdoch
sumber
1
Karena saya tidak tahu apa artinya "statis" dalam kelas "Exposed": Blok statis, adalah blok pernyataan di dalam kelas Java yang akan dieksekusi ketika kelas pertama kali dimuat ke JVM Baca lebih lanjut di javatutorialhub. com / ...
Guy L
Pola yang menarik tetapi mengharuskan kelas Exposed dan Accessor menjadi publik sementara kelas yang mengimplementasikan API (yaitu, sekumpulan kelas Java yang mengimplementasikan seperangkat antarmuka Java publik) akan lebih baik "dilindungi standar" dan, dengan demikian, tidak dapat diakses oleh klien untuk memisahkan jenis dari implementasinya.
Yann-Gaël Guéhéneuc
8
Saya cukup berkarat di Jawa saya, jadi maafkan ketidaktahuan saya. Apa keuntungannya dibandingkan solusi "Romeo and Juliet" yang diposting Salomon BRYS? Implementasi ini akan menakut-nakuti saya jika saya tersandung di dalam basis kode (tanpa penjelasan Anda terlampir, mis. Komentar berat). Pendekatan Romeo dan Juliet sangat sederhana untuk dipahami.
Steazy
1
Pendekatan ini akan membuat masalah hanya terlihat pada saat runtime, sementara penyalahgunaan Romeo dan Juliet akan membuatnya terlihat pada waktu kompilasi, saat berkembang.
ymajoros
1
@ymajoros Contoh Romeo dan Juliet tidak membuat penyalahgunaan terlihat pada waktu kompilasi. Itu mengandalkan argumen yang diteruskan dengan benar, dan pengecualian dilemparkan. Keduanya adalah aksi run-time.
Radiodef
10

Ada dua solusi untuk pertanyaan Anda yang tidak melibatkan menjaga semua kelas dalam paket yang sama.

Yang pertama adalah menggunakan Friend Accessor / Paket Friend yang dijelaskan dalam (Desain API Praktis, Tulach 2008).

Yang kedua adalah menggunakan OSGi. Ada artikel di sini menjelaskan bagaimana OSGi menyelesaikan ini.

Pertanyaan Terkait: 1 , 2 , dan 3 .

Jeff Axelrod
sumber
7

Sejauh yang saya tahu, itu tidak mungkin.

Mungkin, Anda bisa memberi kami beberapa detail tentang desain Anda. Pertanyaan seperti ini kemungkinan merupakan hasil dari kekurangan desain.

Pertimbangkan saja

  • Mengapa kelas-kelas itu dalam paket yang berbeda, jika mereka sangat terkait?
  • Pernahkah A mengakses anggota pribadi B atau haruskah operasi dipindahkan ke kelas B dan dipicu oleh A?
  • Apakah ini benar-benar panggilan atau penanganan acara lebih baik?
Hitam
sumber
3

Jawaban eirikma mudah dan luar biasa. Saya mungkin menambahkan satu hal lagi: alih-alih memiliki metode yang dapat diakses publik, getFriend () untuk mendapatkan teman yang tidak dapat digunakan, Anda dapat melangkah lebih jauh dan melarang mendapatkan teman tanpa token: getFriend (Service.FriendToken). FriendToken ini akan menjadi kelas publik dalam dengan konstruktor pribadi, sehingga hanya Layanan yang dapat membuat instantiate.

AriG
sumber
3

Berikut adalah contoh kasus penggunaan yang jelas dengan Friendkelas yang dapat digunakan kembali . Manfaat dari mekanisme ini adalah kesederhanaan penggunaan. Mungkin bagus untuk memberi kelas unit tes akses yang lebih banyak daripada aplikasi lainnya.

Untuk memulai, berikut adalah contoh cara menggunakan Friendkelas.

public class Owner {
    private final String member = "value";

    public String getMember(final Friend friend) {
        // Make sure only a friend is accepted.
        friend.is(Other.class);
        return member;
    }
}

Kemudian dalam paket lain Anda dapat melakukan ini:

public class Other {
    private final Friend friend = new Friend(this);

    public void test() {
        String s = new Owner().getMember(friend);
        System.out.println(s);
    }
}

The Friendkelas adalah sebagai berikut.

public final class Friend {
    private final Class as;

    public Friend(final Object is) {
        as = is.getClass();
    }

    public void is(final Class c) {
        if (c == as)
            return;
        throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
    }

    public void is(final Class... classes) {
        for (final Class c : classes)
            if (c == as)
                return;
        is((Class)null);
    }
}

Namun, masalahnya adalah ia bisa disalahgunakan seperti:

public class Abuser {
    public void doBadThings() {
        Friend badFriend = new Friend(new Other());
        String s = new Owner().getMember(badFriend);
        System.out.println(s);
    }
}

Sekarang, mungkin benar bahwa Otherkelas tidak memiliki konstruktor publik, oleh karena itu membuat Abuserkode di atas tidak mungkin. Namun, jika kelas Anda memang memiliki konstruktor publik maka mungkin disarankan untuk menduplikasi kelas Teman sebagai kelas dalam. Ambil Other2kelas ini sebagai contoh:

public class Other2 {
    private final Friend friend = new Friend();

    public final class Friend {
        private Friend() {}
        public void check() {}
    }

    public void test() {
        String s = new Owner2().getMember(friend);
        System.out.println(s);
    }
}

Dan kemudian Owner2kelas akan seperti ini:

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Perhatikan bahwa Other2.Friendkelas memiliki konstruktor pribadi, sehingga menjadikan ini cara yang jauh lebih aman untuk melakukannya.

intrepidis
sumber
2

Solusi yang diberikan mungkin bukan yang paling sederhana. Pendekatan lain didasarkan pada ide yang sama seperti di C ++: anggota pribadi tidak dapat diakses di luar paket / ruang lingkup pribadi, kecuali untuk kelas tertentu yang pemiliknya punya teman sendiri.

Kelas yang membutuhkan akses teman ke anggota harus membuat "kelas teman" abstrak publik publik di mana kelas yang memiliki properti tersembunyi dapat mengekspor akses ke, dengan mengembalikan subclass yang menerapkan metode implementasi akses. Metode "API" kelas teman bisa bersifat pribadi sehingga tidak dapat diakses di luar kelas yang membutuhkan akses teman. Satu-satunya pernyataan adalah panggilan ke anggota yang dilindungi abstrak yang mengimplementasikan kelas ekspor.

Berikut kodenya:

Pertama tes yang memverifikasi bahwa ini benar-benar berfungsi:

package application;

import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;

public class EntityFriendTest extends TestCase {
    public void testFriendsAreOkay() {
        Entity entity = new Entity();
        Service service = new Service();
        assertNull("entity should not be processed yet", entity.getPublicData());
        service.processEntity(entity);
        assertNotNull("entity should be processed now", entity.getPublicData());
    }
}

Kemudian Layanan yang membutuhkan akses teman ke paket pribadi anggota Entity:

package application.service;

import application.entity.Entity;

public class Service {

    public void processEntity(Entity entity) {
        String value = entity.getFriend().getEntityPackagePrivateData();
        entity.setPublicData(value);
    }

    /**
     * Class that Entity explicitly can expose private aspects to subclasses of.
     * Public, so the class itself is visible in Entity's package.
     */
    public static abstract class EntityFriend {
        /**
         * Access method: private not visible (a.k.a 'friendly') outside enclosing class.
         */
        private String getEntityPackagePrivateData() {
            return getEntityPackagePrivateDataImpl();
        }

        /** contribute access to private member by implementing this */
        protected abstract String getEntityPackagePrivateDataImpl();
    }
}

Akhirnya: kelas Entity yang menyediakan akses yang ramah ke anggota pribadi paket hanya ke class application.service.Service.

package application.entity;

import application.service.Service;

public class Entity {

    private String publicData;
    private String packagePrivateData = "secret";   

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    String getPackagePrivateData() {
        return packagePrivateData;
    }

    /** provide access to proteced method for Service'e helper class */
    public Service.EntityFriend getFriend() {
        return new Service.EntityFriend() {
            protected String getEntityPackagePrivateDataImpl() {
                return getPackagePrivateData();
            }
        };
    }
}

Oke, saya harus akui itu sedikit lebih lama dari "layanan teman :: Layanan;" tetapi dimungkinkan untuk mempersingkat saat mempertahankan waktu kompilasi dengan menggunakan anotasi.

eirikma
sumber
Ini tidak cukup berfungsi sebagai kelas normal dalam paket yang sama hanya bisa getFriend () dan kemudian memanggil metode yang dilindungi melewati yang pribadi.
user2219808
1

Di Jawa dimungkinkan untuk memiliki "persahabatan terkait paket". Ini bisa bermanfaat untuk pengujian unit. Jika Anda tidak menentukan pribadi / publik / dilindungi di depan metode, itu akan menjadi "teman dalam paket". Kelas dalam paket yang sama akan dapat mengaksesnya, tetapi akan menjadi pribadi di luar kelas.

Aturan ini tidak selalu diketahui, dan merupakan perkiraan yang baik untuk kata kunci "teman" C ++. Saya merasa ini pengganti yang baik.

daitangio
sumber
1
Ini benar, tetapi saya benar-benar bertanya tentang kode yang berada dalam paket yang berbeda ...
Matthew Murdoch
1

Saya pikir bahwa kelas teman di C ++ seperti konsep kelas dalam di Jawa. Menggunakan kelas-dalam Anda sebenarnya dapat mendefinisikan kelas tertutup dan kelas tertutup. Kelas tertutup memiliki akses penuh ke anggota publik dan pribadi dari kelas terlampir itu. lihat tautan berikut: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Sephiroth
sumber
Eh, tidak, mereka tidak. Ini lebih seperti persahabatan dalam kehidupan nyata: Bisa, tetapi tidak harus saling menguntungkan (Menjadi teman B tidak berarti B dianggap sebagai teman A), dan Anda dan teman Anda dapat berasal dari yang sama sekali berbeda keluarga dan memiliki lingkaran teman Anda sendiri, mungkin (tetapi tidak harus) tumpang tindih. (Bukannya saya ingin melihat kelas dengan banyak teman. Ini bisa menjadi fitur yang berguna, tetapi harus digunakan dengan hati-hati.)
Christopher Creutzig
1

Saya pikir, pendekatan menggunakan pola pengaksesan teman terlalu rumit. Saya harus menghadapi masalah yang sama dan saya menyelesaikan menggunakan copy constructor yang baik dan lama, yang dikenal dari C ++, di Java:

public class ProtectedContainer {
    protected String iwantAccess;

    protected ProtectedContainer() {
        super();
        iwantAccess = "Default string";
    }

    protected ProtectedContainer(ProtectedContainer other) {
        super();
        this.iwantAccess = other.iwantAccess;
    }

    public int calcSquare(int x) {
        iwantAccess = "calculated square";
        return x * x;
    }
}

Dalam aplikasi Anda, Anda dapat menulis kode berikut:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

        protected PrivateAccessor(ProtectedContainer prot) {
            super(prot);
        }

        public String exposeProtected() {
            return iwantAccess;
        }
    }
}

Keuntungan dari metode ini adalah hanya aplikasi Anda yang memiliki akses ke data yang dilindungi. Ini bukan pengganti kata kunci teman. Tapi saya pikir itu sangat cocok ketika Anda menulis perpustakaan khusus dan Anda perlu mengakses data yang dilindungi.

Setiap kali Anda harus berurusan dengan instance ProtectedContainer Anda dapat membungkus ProtectedAccessor Anda di sekitarnya dan Anda mendapatkan akses.

Ini juga bekerja dengan metode yang dilindungi. Anda mendefinisikannya terlindungi di API Anda. Kemudian dalam aplikasi Anda, Anda menulis kelas pembungkus pribadi dan mengekspos metode yang dilindungi sebagai publik. Itu dia.

Chris
sumber
1
Tetapi ProtectedContainerdapat disubklasifikasikan di luar paket!
Raphael
0

Jika Anda ingin mengakses metode yang dilindungi, Anda bisa membuat subkelas dari kelas yang ingin Anda gunakan yang memperlihatkan metode yang ingin Anda gunakan sebagai publik (atau internal ke namespace menjadi lebih aman), dan memiliki instance kelas itu di kelas Anda (gunakan sebagai proxy).

Sejauh menyangkut metode pribadi (saya pikir) Anda kurang beruntung.

Omar Kooheji
sumber
0

Saya setuju bahwa dalam kebanyakan kasus kata kunci teman tidak perlu.

  • Package-private (alias. Default) sudah cukup dalam banyak kasus di mana Anda memiliki sekelompok kelas yang sangat terkait
  • Untuk kelas debug yang ingin akses ke internal, saya biasanya membuat metode pribadi dan mengaksesnya melalui refleksi. Kecepatan biasanya tidak penting di sini
  • Terkadang, Anda menerapkan metode yang merupakan "peretasan" atau yang dapat berubah. Saya membuatnya menjadi publik, tetapi gunakan @Deprecated untuk menunjukkan bahwa Anda tidak harus bergantung pada metode yang ada ini.

Dan akhirnya, jika benar-benar diperlukan, ada pola pengakses teman yang disebutkan dalam jawaban lain.

Casebash
sumber
0

Tidak menggunakan kata kunci atau lebih.

Anda dapat "menipu" menggunakan refleksi dll, tetapi saya tidak akan merekomendasikan "curang".

NR.
sumber
3
Saya akan menganggap ini sebagai ide buruk yang bahkan menyarankan itu menjijikkan bagi saya. Tentunya ini adalah cludge yang terbaik, dan tidak boleh menjadi bagian dari desain apa pun.
shsteimer
0

Metode yang saya temukan untuk menyelesaikan masalah ini adalah membuat objek accessor, seperti:

class Foo {
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* This is the accessor. Anyone with a reference to this has special access. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    /** You get an accessor by calling this method. This method can only
     * be called once, so calling is like claiming ownership of the accessor. */
    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }
}

Kode pertama yang memanggil getAccessor()"klaim kepemilikan" dari accessor. Biasanya, ini adalah kode yang menciptakan objek.

Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.

Ini juga memiliki keunggulan dibandingkan mekanisme teman C ++, karena memungkinkan Anda membatasi akses pada level per-instance , dibandingkan dengan level per-kelas . Dengan mengontrol referensi accessor, Anda mengontrol akses ke objek. Anda juga dapat membuat banyak pengakses, dan memberikan akses yang berbeda untuk masing-masingnya, yang memungkinkan kontrol yang baik atas kode apa yang dapat mengakses apa:

class Foo {
    private String secret;
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* Normal accessor. Can write to locked, but not read secret. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }

    /* Super accessor. Allows access to secret. */
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    private FooSuperAccessor superAccessor;

    public FooSuperAccessor getAccessor() {
        if (superAccessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return superAccessor = new FooSuperAccessor();
    }
}

Akhirnya, jika Anda ingin sesuatu menjadi sedikit lebih terorganisir, Anda dapat membuat objek referensi, yang menyatukan semuanya. Ini memungkinkan Anda untuk mengklaim semua pengakses dengan satu pemanggilan metode, serta menyatukannya dengan instance tertautnya. Setelah Anda memiliki referensi, Anda dapat membagikan aksesor ke kode yang membutuhkannya:

class Foo {
    private String secret;
    private String locked;

    public String getLocked() { return locked; }

    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    public class FooReference {
        public final Foo foo;
        public final FooAccessor accessor;
        public final FooSuperAccessor superAccessor;

        private FooReference() {
            this.foo = Foo.this;
            this.accessor = new FooAccessor();
            this.superAccessor = new FooSuperAccessor();
        }
    }

    private FooReference reference;

    /* Beware, anyone with this object has *all* the accessors! */
    public FooReference getReference() {
        if (reference != null)
            throw new IllegalStateException("Cannot return reference more than once!");
        return reference = new FooReference();
    }
}

Setelah banyak memukul-mukul (bukan jenis yang baik), ini adalah solusi terakhir saya, dan saya sangat menyukainya. Ini fleksibel, mudah digunakan, dan memungkinkan kontrol yang sangat baik atas akses kelas. ( Akses dengan referensi saja sangat berguna.) Jika Anda menggunakan protected sebagai ganti private untuk accessor / referensi, subkelas Foo bahkan dapat mengembalikan referensi yang diperluas dari getReference. Itu juga tidak memerlukan refleksi apa pun, sehingga dapat digunakan di lingkungan apa pun.

jpfx1342
sumber
0

Pada Java 9, modul dapat digunakan untuk menjadikan ini tidak menjadi masalah dalam banyak kasus.

Raphael
sumber
0

Saya lebih suka delegasi atau komposisi atau kelas pabrik (tergantung pada masalah yang menyebabkan masalah ini) untuk menghindari menjadikannya kelas publik.

Jika itu adalah masalah "antarmuka / implementasi kelas dalam paket yang berbeda", maka saya akan menggunakan kelas pabrik umum yang akan dalam paket yang sama dengan paket impl dan mencegah pemaparan dari kelas impl.

Jika itu adalah masalah "Saya benci membuat kelas / metode ini hanya untuk menyediakan fungsionalitas ini untuk beberapa kelas lain dalam paket yang berbeda", maka saya akan menggunakan kelas delegasi publik dalam paket yang sama dan hanya mengekspos bagian fungsionalitas itu saja. dibutuhkan oleh kelas "orang luar".

Beberapa keputusan ini didorong oleh arsitektur classloading server target (bundel OSGi, WAR / EAR, dll.), Penerapan dan konvensi penamaan paket. Misalnya, solusi yang diajukan di atas, pola 'Friend Accessor' pintar untuk aplikasi java normal. Saya ingin tahu apakah akan sulit untuk mengimplementasikannya di OSGi karena perbedaan gaya classloading.

Suraj Rao
sumber
0

Saya tidak tahu apakah itu berguna bagi siapa pun tetapi saya menanganinya dengan cara berikut:

Saya membuat antarmuka (AdminRights).

Setiap kelas yang seharusnya dapat memanggil fungsi tersebut harus mengimplementasikan AdminRights.

Kemudian saya membuat fungsi HasAdminRights sebagai berikut:

private static final boolean HasAdminRights()
{
    // Gets the current hierarchy of callers
    StackTraceElement[] Callers = new Throwable().getStackTrace();

    // Should never occur with me but if there are less then three StackTraceElements we can't check
    if (Callers.length < 3)
    {
        EE.InvalidCode("Couldn't check for administrator rights");
        return false;

    } else try
    {

        // Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
        Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);

        // If everything worked up to now, it has admin rights!
        return true;

    } catch (java.lang.ClassCastException | ClassNotFoundException e)
    {
        // In the catch, something went wrong and we can deduce that the caller has no admin rights

        EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
        return false;
    }
}
mdre
sumber
-1

Saya pernah melihat solusi berbasis refleksi yang melakukan "teman memeriksa" saat runtime menggunakan refleksi dan memeriksa tumpukan panggilan untuk melihat apakah kelas yang memanggil metode diizinkan untuk melakukannya. Menjadi cek runtime, ia memiliki kelemahan yang jelas.

Ran Biron
sumber