Есть ли способ смоделировать концепцию «друга» C ++ в Java?

Я хотел бы иметь возможность написать класс Java в одном пакете, который может получать доступ к закрытым методам класса в другом пакете, не делая его подклассом другого класса. Это возможно?


person Matthew Murdoch    schedule 08.10.2008    source источник


Ответы (18)


Вот небольшой трюк, который я использую в JAVA для репликации механизма друга C ++.

Допустим, у меня есть класс Romeo и еще один класс Juliet. Они находятся в разных упаковках (семье) из соображений ненависти.

Romeo хочет cuddle Juliet, а Juliet хочет позволить Romeo cuddle только ей.

В C ++ Juliet объявляет Romeo как (любовник) friend, но в java таких вещей нет.

Вот классы и хитрости:

Дамы вперед :

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?");
    }

}

Итак, метод Juliet.cuddle - это public, но вам нужен Romeo.Love для его вызова. Он использует это Romeo.Love как «безопасность подписи», чтобы гарантировать, что только Romeo может вызвать этот метод, и проверяет, что любовь реальна, так что среда выполнения выдаст NullPointerException, если это null.

Теперь мальчики:

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);
    }
}

Класс Romeo.Love является общедоступным, но его конструктор - private. Следовательно, любой может его увидеть, но только Romeo может его построить. Я использую статическую ссылку, поэтому Romeo.Love, который никогда не используется, создается только один раз и не влияет на оптимизацию.

Следовательно, Romeo может cuddle Juliet, и только он может, потому что только он может построить и получить доступ к экземпляру Romeo.Love, который требуется Juliet для cuddle ее (иначе она ударит вас NullPointerException).

person Salomon BRYS    schedule 05.09.2013
comment
Продолжение: я новичок в Java, поэтому я предполагаю, что нет атрибута (аннотации?), Который можно было бы использовать для обеспечения того, чтобы параметр Romeo.Love для обнимания не мог быть нулевым? Что-то похожее на '__attribute __ ((nonnull))' во многих компиляторах C? Это сделало бы это идеальным решением IMHO (поскольку оно все еще круто). - person Steazy; 25.07.2014
comment
+1 за пощечину NullPointerException. Очень впечатляюще. - person Nickolas; 04.08.2014
comment
@Steazy Есть: ищите аннотации NotNull, NonNull и CheckForNull. Обратитесь к документации своей IDE, чтобы узнать, как использовать и применять эти аннотации. Я знаю, что IntelliJ встраивает это по умолчанию и что для eclipse нужен плагин (например, FindBugs). - person Salomon BRYS; 20.08.2014
comment
@ChrisNash Вы не можете. Джульетта должна объявить аксессуар: public Whatever getPrivateField(Romeo.Love l) { l.hashCode(); return mPrivateField; }. - person Salomon BRYS; 13.08.2015
comment
Вы можете сделать Romeo Love для Julia вечным, изменив поле love на final ;-). - person Matthias; 10.11.2015
comment
... и если вы думаете, что Romeo увлекался растафарианством Love, вы также можете сделать love поле static ;-). - person Matthias; 10.11.2015
comment
@Matthias Поле любви статично ... Я отредактирую ответ, чтобы сделать его окончательным;) - person Salomon BRYS; 10.11.2015
comment
Еще одна интересная уловка, позволяющая сделать это еще более элегантным (хотя, возможно, и сбивающим с толку), - это переименовать поле love в Love. Да, вы действительно можете это сделать (см. stackoverflow.com/a/14027255/1084488). В результате упоминания Love в коде Romeo (например, в Juliet.cuddle(Love);) будут интерпретироваться как ссылки на его вечный, один Love объект (!), Тогда как упоминания Romeo.Love вне класса Romeo будут относиться к в публичный Love класс (!). - person Matthias; 10.11.2015
comment
не были мои предложение о системе голосования было отклонено, этот ответ получил бы вдохновляющие 5 звезд и гениальные 5 звезд, так как вам удалось проявить хорошее настроение и при этом предоставить отличную альтернативу "из коробки" :) - person Aquarius Power; 19.12.2015
comment
есть ли у вас совет для этот вопрос? это связано с идеей вашего ответа, спасибо! - person Aquarius Power; 16.01.2016
comment
Все ответы должны быть такими (Y) +1 за юмор и отличный пример. - person Zia Ul Rehman Mughal; 24.04.2016
comment
Полностью согласен, не только этот ответ является отличным ответом, но и предоставляет простой, но эффективный контекст для запоминания этого шаблона. Прекрасная работа. - person Stephane Toussaint; 18.05.2016
comment
Совершенно новый смысл фразы Мой код - это поэзия. - person mgarciaisaia; 20.09.2016
comment
Самый фантастический ответ, который я когда-либо читал в SO. +1 за вечную любовь Ромео по квалификатору final;) - person faghani; 10.10.2016
comment
Очень поздно на эту вечеринку, но я должен сказать: это самый великолепный отрывок из многослойной прозы, который я когда-либо читал. БРАВО. Я ГОВОРЮ БРАВО, ДОБРЫЙ Сэр! - person Bryan Shaw; 30.11.2017
comment
Красивый! Хотя необходимость в подобном паттерне обычно является сильным намеком на запах дизайна, мы не всегда можем сразу провести большой рефакторинг, когда что-то пахнет подозрительно. В таких случаях это решение максимально элегантно. - person chris; 27.08.2018
comment
Мне очень нравится ответ, но мне было интересно, можно ли заставить его сопротивляться созданию экземпляра путем отражения (например, как это сделал Gson). В противном случае любой может создать Romeo.Love объект. - person noamtm; 07.06.2020
comment
@noamtm Против отражения мало что можно сделать. По задумке, отражение позволяет делать практически все, включая вызов частных методов. - person Salomon BRYS; 28.06.2020
comment
@Salomon BRYS, это не очень впечатляет, но я думаю, что это не работает. А как насчет класса Fake Romeo (назовем его Iago), который будет иметь член Iago.Love, который не создается сам по себе, а украден у Romeo (поскольку он общедоступен). Что-то вроде: приватный статический финал Love love = Rome.love - person mgueydan; 25.09.2020
comment
Lago не может построить Romeo.Love (частный конструктор) или получить доступ к Romeo.love (частная собственность). - person Salomon BRYS; 25.09.2020
comment
Разве это нельзя воспроизвести, закачав другой объект в Romeo.Love? - person David Tóth; 27.01.2021
comment
Обратите внимание, что этот механизм / идиома (идиома ключа доступа) также можно использовать в C ++ (поскольку он дает доступ только к некоторой части класса, а не ко всему классу). - person Jarod42; 11.02.2021

Разработчики Java явно отвергли идею друга, поскольку она работает на C ++. Вы кладете своих «друзей» в один пакет. Частная, защищенная и пакетная безопасность обеспечивается как часть языкового дизайна.

Джеймс Гослинг хотел, чтобы Java была C ++ без ошибок. Я полагаю, он считал, что этот друг был ошибкой, потому что это нарушает принципы ООП. Пакеты обеспечивают разумный способ организации компонентов без излишней строгости в отношении ООП.

NR указал, что вы можете обмануть, используя отражение, но даже это работает, только если вы не используете SecurityManager. Если вы включите стандартную безопасность Java, вы не сможете обмануть с помощью отражения, если не напишете политику безопасности, специально разрешающую это.

person David G    schedule 08.10.2008
comment
Я не хочу быть педантом, но модификаторы доступа не являются механизмом безопасности. - person Greg D; 08.10.2008
comment
Модификаторы доступа являются частью модели безопасности Java. Я специально имел в виду java.lang.RuntimePermission для отражения: accessDeclaredMembers и accessClassInPackage. - person David G; 08.10.2008
comment
Если Гослинг действительно думал, что friend нарушает ООП (в частности, больше, чем доступ к пакету), то он действительно этого не понимал (вполне возможно, многие люди это неправильно понимают). - person Konrad Rudolph; 27.05.2011
comment
Компоненты класса иногда необходимо разделять (например, реализация и API, основной объект и адаптер). Защита на уровне пакета в то же время слишком разрешительна и слишком ограничительна, чтобы делать это должным образом. - person dhardy; 12.02.2014
comment
@GregD Их можно считать механизмом безопасности в том смысле, что они помогают предотвратить неправильное использование разработчиками члена класса. Думаю, их лучше называть механизмом безопасности. - person crush; 23.08.2014
comment
Мне просто пришлось проголосовать за написание и творческое решение. Однако я надеюсь, что мне никогда не придется делать такие вещи в продакшене. - person kap; 09.10.2020

Концепция «друга» полезна в Java, например, для отделения API от его реализации. Классам реализации обычно требуется доступ к внутренним компонентам классов API, но они не должны открываться для клиентов API. Это может быть достигнуто с помощью шаблона «Friend Accessor», как подробно описано ниже:

Класс, представленный через 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();
        }
    }
}

Класс, обеспечивающий функциональность «друга»:

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);
}

Пример доступа из класса в пакете реализации friend:

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
person Matthew Murdoch    schedule 25.11.2008
comment
Потому что я не знал, что означает static в Exposed class: статический блок - это блок оператора внутри класса Java, который будет выполняться, когда класс впервые загружается в JVM. Подробнее на javatutorialhub.com/ - person Guy L; 08.04.2013
comment
Интересный шаблон, но он требует, чтобы классы Exposed и Accessor были общедоступными, в то время как классы, реализующие API (т. Е. Набор классов Java, реализующих набор общедоступных интерфейсов Java), лучше были бы защищены по умолчанию и, таким образом, были бы недоступны для клиента для разделения типы из их реализаций. - person Yann-Gaël Guéhéneuc; 26.05.2014
comment
Я изрядно устарел на своей Java, так что простите за невежество. В чем преимущество этого решения по сравнению с решением «Ромео и Джульетта», опубликованным Salomon BRYS? Эта реализация напугала бы меня, если бы я наткнулся на нее в базе кода (без вашего объяснения, т. Е. Тяжелых комментариев). Подход Ромео и Джульетты очень прост для понимания. - person Steazy; 25.07.2014
comment
Такой подход сделает проблемы видимыми только во время выполнения, в то время как неправильное использование Ромео и Джульетты сделает их видимыми во время компиляции во время разработки. - person ymajoros; 18.01.2017
comment
@ymajoros Пример «Ромео и Джульетта» не делает видимым злоупотребление во время компиляции. Он полагается, что аргумент передается правильно и создается исключение. Это оба действия во время выполнения. - person Radiodef; 24.08.2018
comment
Я думаю, что этот пример можно было бы немного упростить, сделав Accessor общедоступным статическим вложенным классом Enclosed с частным конструктором и общедоступными методами. Затем просто введите Accessor экземпляра друзьям, как вы уже делаете. Классы в других пакетах могли бы использовать аксессор, только если он был внедрен. Я не думаю, что есть много пользы от помещения суперкласса доступа в пакет friend, потому что вы должны сделать методы защищенными, что означает, что они в любом случае видны внешнему миру, например. через JavaDocs. - person Radiodef; 24.08.2018
comment
@Radiodef, похоже, вопрос касается способа узнать о злоупотреблениях во время компиляции (что-то вроде оператора «друга»). Этот пример действительно касается проверок во время выполнения, поэтому он не совсем подходит. - person ymajoros; 27.08.2018
comment
@ymajoros Я не знаю, при чем тут то, что я сказал. Однако этот ответ действительно обеспечивает проверки во время компиляции, в отличие от примера «Ромео и Джульетта». В этом примере методы Exposed не видны другим пакетам, а методы средства доступа защищены, поэтому они доступны только для пакета друзей. На самом деле это проверки во время компиляции. - person Radiodef; 27.08.2018

Есть два решения вашего вопроса, которые не связаны с хранением всех классов в одном пакете.

Первый - использовать шаблон Friend Accessor / Friend Package, описанный в (Практическое проектирование API , Тулач 2008).

Второй - использовать OSGi. здесь, объясняющая как OSGi это делает.

Связанные вопросы: 1, 2 и 3.

person Jeff Axelrod    schedule 27.05.2011

Насколько я знаю, это невозможно.

Возможно, Вы расскажете подробнее о своем дизайне. Подобные вопросы, скорее всего, являются результатом недостатков дизайна.

Просто подумай

  • Почему эти классы находятся в разных пакетах, если они так тесно связаны?
  • Имеет ли A доступ к закрытым членам B или операцию следует переместить в класс B и запустить A?
  • Это действительно вызов или обработка событий лучше?
person Black    schedule 08.10.2008

Эйрикма ответ прост и превосходен. Я мог бы добавить еще одну вещь: вместо общедоступного метода getFriend () для получения друга, который нельзя использовать, вы можете пойти еще дальше и запретить получение друга без токена: getFriend (Service.FriendToken). Этот FriendToken будет внутренним публичным классом с частным конструктором, так что только Service может создать его экземпляр.

person AriG    schedule 18.08.2011

Вот наглядный пример использования многоразового класса Friend. Преимущество этого механизма - простота использования. Может быть, хорошо для предоставления классов модульного тестирования большего доступа, чем остальной части приложения.

Для начала приведем пример использования класса Friend.

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;
    }
}

Тогда в другом пакете вы можете сделать это:

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

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

Класс Friend выглядит следующим образом.

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);
    }
}

Однако проблема в том, что им можно злоупотреблять так:

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

Теперь может быть правдой, что класс Other не имеет каких-либо общедоступных конструкторов, поэтому приведенный выше код Abuser невозможен. Однако, если ваш класс действительно имеет общедоступный конструктор, вероятно, целесообразно продублировать класс Friend как внутренний класс. В качестве примера возьмем этот Other2 класс:

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);
    }
}

И тогда класс Owner2 будет таким:

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

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

Обратите внимание, что у класса Other2.Friend есть частный конструктор, что делает этот способ гораздо более безопасным.

person intrepidis    schedule 13.08.2015

Предложенное решение было, пожалуй, не самым простым. Другой подход основан на той же идее, что и в C ++: закрытые члены недоступны за пределами пакета / частной области, за исключением определенного класса, который владелец делает себе другом.

Класс, которому требуется дружественный доступ к члену, должен создать внутренний общедоступный абстрактный «дружественный класс», к которому класс, владеющий скрытыми свойствами, может экспортировать доступ, возвращая подкласс, реализующий методы реализации доступа. Метод «API» класса друга может быть частным, поэтому он недоступен за пределами класса, которому требуется доступ друзей. Его единственное утверждение - это вызов абстрактного защищенного члена, который реализует экспортирующий класс.

Вот код:

Сначала тест, подтверждающий, что это действительно работает:

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());
    }
}

Затем Сервис, которому требуется дружеский доступ к частному члену пакета 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();
    }
}

Наконец: класс Entity, который обеспечивает удобный доступ к закрытому члену пакета только к классу 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();
            }
        };
    }
}

Хорошо, я должен признать, что это немного длиннее, чем "Friend service :: Service;" но можно было бы сократить его, сохранив при этом проверку во время компиляции, используя аннотации.

person eirikma    schedule 07.10.2009
comment
Это не совсем работает, поскольку обычный класс в том же пакете может просто getFriend (), а затем вызвать защищенный метод, минуя частный. - person user2219808; 05.07.2017

В Java возможно «дружелюбие, связанное с пакетами». Это может быть полезно для модульного тестирования. Если вы не укажете private / public / protected перед методом, он будет «другом в пакете». Класс в том же пакете сможет получить к нему доступ, но он будет частным вне класса.

Это правило не всегда известно, и это хорошее приближение ключевого слова "друг" C ++. Я считаю это хорошей заменой.

person daitangio    schedule 13.12.2010
comment
Это правда, но я действительно спрашивал о коде, находящемся в разных пакетах ... - person Matthew Murdoch; 13.12.2010

Я думаю, что классы друзей в C ++ похожи на концепцию внутреннего класса в Java. Используя внутренние классы, вы можете фактически определить охватывающий класс и закрытый класс. Закрытый класс имеет полный доступ к открытым и закрытым членам закрывающего класса. см. следующую ссылку: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

person Sephiroth    schedule 26.03.2012
comment
Э, нет, они не такие. Это больше похоже на дружбу в реальной жизни: она может, но не обязательно, быть взаимной (то, что А является другом Б, не означает, что Б считается другом А), и вы и ваши друзья можете быть совершенно разными людьми. семьи и иметь свой собственный, возможно (но не обязательно) пересекающийся круг друзей. (Не то чтобы я хотел видеть классы с большим количеством друзей. Это может быть полезной функцией, но ее следует использовать с осторожностью.) - person Christopher Creutzig; 17.08.2015

Не использовать ключевое слово или около того.

Вы можете «обмануть», используя отражение и т. Д., Но я бы не рекомендовал «обмануть».

person NR.    schedule 08.10.2008
comment
Я бы посчитал это такой плохой идеей, что даже предполагать это мне противно. Очевидно, что это в лучшем случае лабиринт, и он не должен быть частью какого-либо дизайна. - person shsteimer; 25.11.2008

Я думаю, что подход к использованию шаблона аксессуара друга слишком сложен. Мне пришлось столкнуться с той же проблемой, и я решил использовать старый добрый конструктор копирования, известный по C ++, на 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;
    }
}

В своем приложении вы можете написать следующий код:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

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

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

Преимущество этого метода в том, что только ваше приложение имеет доступ к защищенным данным. Это не совсем подмена ключевого слова друга. Но я думаю, что это вполне подходит, когда вы пишете собственные библиотеки и вам нужен доступ к защищенным данным.

Всякий раз, когда вам приходится иметь дело с экземплярами ProtectedContainer, вы можете обернуть его своим ProtectedAccessor, и вы получите доступ.

Он также работает с защищенными методами. Вы определяете их как защищенные в своем API. Позже в своем приложении вы напишете частный класс-оболочку и сделаете защищенный метод общедоступным. Вот и все.

person Chris    schedule 01.08.2016
comment
Но ProtectedContainer можно разделить на подклассы вне пакета! - person Raphael; 11.10.2017

Если вы хотите получить доступ к защищенным методам, вы можете создать подкласс класса, который вы хотите использовать, который предоставляет методы, которые вы хотите использовать как общедоступные (или внутренние по отношению к пространству имен, чтобы быть безопаснее), и иметь экземпляр этого класса в своем классе. (используйте его как прокси).

Что касается частных методов (я думаю), вам не повезло.

person Omar Kooheji    schedule 08.10.2008

Я согласен с тем, что в большинстве случаев ключевое слово друга не нужно.

  • Package-private (также известный как default) достаточно в большинстве случаев, когда у вас есть группа сильно связанных классов.
  • Для классов отладки, которым нужен доступ к внутренним компонентам, я обычно делаю метод закрытым и получаю к нему доступ через отражение. Скорость здесь обычно не важна
  • Иногда вы реализуете метод, который является «взломом» или иным образом, который может быть изменен. Я делаю его общедоступным, но использую @Deprecated, чтобы указать, что вам не следует полагаться на существующий метод.

И, наконец, если это действительно необходимо, в других ответах упоминается шаблон аксессуара друга.

person Casebash    schedule 17.08.2012

Я нашел способ решения этой проблемы - создать объект-аксессор, например:

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();
    }
}

Первый код, вызывающий getAccessor(), "заявляет о праве собственности" на средство доступа. Обычно это код, который создает объект.

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

Это также имеет преимущество перед механизмом друзей C ++, поскольку он позволяет вам ограничивать доступ на уровне для каждого экземпляра, в отличие от уровня для каждого класса. Управляя ссылкой на метод доступа, вы управляете доступом к объекту. Вы также можете создать несколько средств доступа и предоставить разный доступ к каждому, что позволяет детально контролировать, какой код может получить доступ к чему:

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();
    }
}

Наконец, если вы хотите, чтобы все было немного более организовано, вы можете создать ссылочный объект, который объединяет все вместе. Это позволяет вам запрашивать все аксессоры одним вызовом метода, а также сохранять их вместе с их связанным экземпляром. Получив ссылку, вы можете передать аксессоры коду, который в ней нуждается:

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();
    }
}

После долгой тряски (не самого лучшего) это было моим окончательным решением, и оно мне очень понравилось. Он гибкий, простой в использовании и позволяет очень хорошо контролировать доступ к классам. (Доступ только по ссылке очень полезен.) Если вы используете защищенный вместо частного для средств доступа / ссылок, подклассы Foo могут даже возвращать расширенные ссылки из getReference. Он также не требует отражения, поэтому его можно использовать в любой среде.

person jpfx1342    schedule 08.04.2014

Начиная с Java 9, во многих случаях для решения этой проблемы можно использовать модули.

person Raphael    schedule 11.10.2017

Я предпочитаю делегирование, композицию или фабричный класс (в зависимости от проблемы, которая приводит к этой проблеме), чтобы не делать его общедоступным классом.

Если это проблема «классы интерфейса / реализации в разных пакетах», то я бы использовал общедоступный фабричный класс, который был бы в том же пакете, что и пакет impl, и предотвратил бы раскрытие класса impl.

Если это проблема типа «я ненавижу делать этот класс / метод общедоступным только для того, чтобы предоставить эту функциональность для какого-либо другого класса в другом пакете», то я бы использовал публичный класс делегата в том же пакете и предоставлял бы только эту часть функциональности. нужен классу "аутсайдеров".

Некоторые из этих решений определяются архитектурой загрузки классов целевого сервера (пакет OSGi, WAR / EAR и т. Д.), Соглашениями о развертывании и именовании пакетов. Например, предложенное выше решение, шаблон «Friend Accessor», хорошо подходит для обычных приложений Java. Интересно, сложно ли реализовать это в OSGi из-за разницы в стиле загрузки классов.

person Community    schedule 08.12.2008

Однажды я видел решение на основе отражения, которое выполняло «проверку друзей» во время выполнения с использованием отражения и проверки стека вызовов, чтобы узнать, разрешено ли это делать классу, вызывающему метод. Будучи проверкой во время выполнения, она имеет очевидный недостаток.

person Community    schedule 08.10.2008