Кастинг интерфейсов - PullRequest
4 голосов
/ 12 апреля 2010

интерфейсы обеспечивают полезную возможность абстракции. Можно сделать так, чтобы класс Foo реализовывал некоторые интерфейсы, скажем, A, B и C. Некоторые клиентские коды могут получить ссылку типа A, другие - типа B и т. Д., Каждый из которых на самом деле является одним и тем же объектом Foo, но интерфейс представляет только узкий подмножество функциональности. Конечно, злой клиентский код может попытаться привести A-ссылку к Foo, а затем получить доступ к другим функциям. Как это предотвратить?

Ответы [ 7 ]

10 голосов
/ 12 апреля 2010

Это называется «злонамеренным приведением», и вы можете предотвратить его, имея оболочку, которая реализует только узкий интерфейс, который вы хотите раскрыть (делегируя частную ссылку на объект, который в противном случае вы бы напрямую передали злу клиент).

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

2 голосов
/ 12 апреля 2010

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

1 голос
/ 13 апреля 2010

Лицо, выполняющее злонамеренное действие, делает это на свой страх и риск. Почти во всех случаях можно смело предположить, что пользователь не будет использовать объект за пределами указанного контракта интерфейса.

Единственный случай, когда вам действительно нужно использовать прокси-объект, - это если вы подвергаете объект, чувствительный к безопасности, ненадежному коду. В противном случае потратьте свое время на составление четкой документации о том, как можно использовать объекты, и работайте в предположении, что за ним последуют.

1 голос
/ 12 апреля 2010

Вы не можете. Одним из обходных путей является реализация трех прокси-классов, по одному для реализации каждого интерфейса, которые перенаправляют все вызовы в один экземпляр Foo.

0 голосов
/ 12 апреля 2010

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

Однако , если ваша ситуация действительно требует этой защиты, используйте этот служебный класс для создания динамических прокси-серверов (делегатов) (адаптировано из Динамические прокси-классы - <50 строк производственного кода !!). </p>

Это вызовет ClassCastException s во время выполнения, если кто-то использует попытку злонамеренного приведения. Вы могли бы даже обусловить код, чтобы отключить его во время производства (пусть newInstance() просто возвращает obj - объект в качестве «прокси»).

DynamicProxy.java

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DynamicProxy implements java.lang.reflect.InvocationHandler {

    private Object obj;

    public static Object newInstance(Object obj, Class<?>... interfaces) {
        if (interfaces == null || interfaces.length == 0) {
            throw new IllegalArgumentException("No interfaces");
        }
        return java.lang.reflect.Proxy.newProxyInstance(
            obj.getClass().getClassLoader(),
            interfaces,
            new DynamicProxy(obj));
    }

    private DynamicProxy(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args)
    throws Throwable
    {
        Object result;
        try {
            result = m.invoke(obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw new RuntimeException("unexpected invocation exception: " +
                           e.getMessage());
        }
        return result;
    }

    // ** DEMO CODE BELOW HERE **

    interface A {
        void methodA();
    }

    interface B {
        void methodB();
    }

    static class Foo implements A, B {
        public void methodA() { System.out.println("A"); }
        public void methodB() { System.out.println("B"); }
    }

    public static void main(String[] args) {

        Foo foo = new Foo();  // implements both interfaces

        // calls foo's methods, but only A methods
        A a = (A) DynamicProxy.newInstance(foo, A.class);

        // calls foo's methods, but only B methods
        B b = (B) DynamicProxy.newInstance(foo, B.class);

        // calls foo's methods, but only B methods
        A ab = (A) DynamicProxy.newInstance(foo, A.class, B.class);

        a.methodA();
        b.methodB();
        ab.methodA();
        ((B) ab).methodB();

        // ClassCastException: $Proxy0 cannot be cast to DynamicProxy$Foo
        ((Foo) a).methodA();

        // ClassCastException: $Proxy1 cannot be cast to DynamicProxy$Foo
        ((Foo) b).methodB();

        // ClassCastException: $Proxy0 cannot be cast to DynamicProxy$B
        ((B) a).methodB();

        // ClassCastException: $DynamicProxy1 cannot be cast to DynamicProxy$A
        ((A) b).methodA();
    }
}
0 голосов
/ 12 апреля 2010

Вы можете использовать класс Facade.

Этот класс должен обернуть делегат класса Foo, а затем выставить только методы интерфейса, скажем, A, и просто перенаправить их делегату.

Onс другой стороны, вы можете предотвратить приведение к Foo, объявив его закрытым и иметь открытый фабричный метод, который возвращает только интерфейс A (который в действительности является Foo).Таким образом, кастинг из других пакетов будет невозможен (тем не менее, кто-то может разыграть трюки с отражением).

0 голосов
/ 12 апреля 2010

Скрыть базовый объект.

Допустим, у вас есть:

public interface A {
}

public class B implements A {
}

Итак, интерфейс A реализует лишь подмножество функциональных возможностей B. По сути, он скрывает части B. Ваш вопрос заключается в том, как остановить пользователя от понижения A до B.

B objectOfTypeB = (B)objectOfTypeA; // you don't want this

Итак, не предоставляет пользователю доступ к классу B . Если пользователь не может импортировать его, он не может создать экземпляр или использовать его. Итак, он вынужден использовать интерфейс и ничего более.

Измените вышеуказанный код на:

/* Publicly accessable interface */
public interface A {
}

/* Class hidden inside the package. */
public class B implements A {
}

Тогда вы можете просто получить функцию, возвращающую A, безопасную, зная, что пользователь не может использовать B.

/* Function that returns an A. */
public A foo() {
    /* ... */
    return objectOfTypeB;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...