Есть ли способ смоделировать концепцию «друга» C ++ в Java? - PullRequest
168 голосов
/ 08 октября 2008

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

Ответы [ 18 ]

1 голос
/ 01 августа 2016

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

0 голосов
/ 09 апреля 2014

Метод, который я нашел для решения этой проблемы, заключается в создании объекта доступа, например, так:

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. Он также не требует отражения, поэтому его можно использовать в любой среде.

0 голосов
/ 17 августа 2012

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

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

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

0 голосов
/ 08 декабря 2008

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

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

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

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

0 голосов
/ 08 октября 2008

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

0 голосов
/ 08 октября 2008

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

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

0 голосов
/ 11 октября 2017

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

0 голосов
/ 08 октября 2008

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

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

...