2010-10-28 14 views
14

Continuando con esta pregunta: Why can't you reduce the visibility of a method in a Java subclass?Java, no puede reducir la visibilidad del método heredado de objeto

tengo que crear la clase B que es casi idéntica a la clase A, excepto que B no puede hacer ciertas cosas que A lata.

Siendo un programador perezoso como soy, he intentado heredar A, solo para saludar con el error que B no puede reducir la visibilidad de los métodos A. Duh! ..

Ahora A es una API de un proveedor, mi intención es encapsular esta API para que sea más fácil de usar.

Me pregunto cuál es la mejor práctica para evitar esto?

Respuesta

18

Dos opciones:

Si necesita B para mantener la misma interfaz que A (de modo que el código de cliente puede utilizar cualquiera de los dos sin cambios), puede reemplazar los métodos "prohibidos" en B y hacer que se tiran un UnsupportedOperationException.Por ejemplo:

public class A 
{ 
    public int allowedMethod() { ... } 
    public int forbiddenMethod() { ... } 
} 

public class B extends A 
{ 
    public int forbiddenMethod() 
    { 
     throw new UnsupportedOperationException("Sorry, not allowed."); 
    } 
} 

O, si realmente desea la API de B a ser un subconjunto de la API de A, a continuación, sólo tiene B contienen una instancia de A, y el método delegado llama apropiadamente.

public class A 
    { 
     public int allowedMethod() { ... } 
     public int forbiddenMethod() { ... } 
    } 

    public class B 
    { 
     private A a; 

     public int allowedMethod() 
     { 
      return a.allowedMethod(); 
     } 
    } 
+0

La segunda solución era más o menos lo que tenía en mente en primer lugar ... pero tendré que replicar manualmente todos los métodos deseados. La primera solución parece "impura" para mí ... un montón de desorden y códigos no deseados. –

+2

Sí, eso es cierto. Supongo que, dependiendo de cuál sea la proporción de métodos que se mantendrán/métodos a rechazar, una de las soluciones podría verse "más limpia" que la otra. p.ej. si solo quiere prohibir 2-3 métodos de 20, entonces A probablemente se vea más limpio. Si solo quiere mantener 2-3 métodos de 20, entonces B se ve más limpio. Pero todo lo demás es igual, también tiendo a preferir B sobre A. – Grodriguez

+0

'UnsupportedOperationException' viola la sustitución de Liskov ... –

2

Puede crear una clase contenedora que ofrezca una API reducida, o puede lanzar una excepción como UnsupportedOperationException desde los métodos que desea deshabilitar.

+1

Por favor, no abuse 'IllegalStateException'. No hay un estado involucrado aquí. – Grodriguez

+0

@Grodriguez, se le ofreció otra opción, la elección real de la excepción depende del modelo de la aplicación, por supuesto, y podría optar por una excepción de estado. – rsp

+0

De acuerdo con la documentación, el 'IllegalStateException' indica que un método ha sido invocado en un momento ilegal o inapropiado. En otras palabras, el entorno Java o la aplicación Java no se encuentran en un estado apropiado para la operación solicitada." Esto es obviamente engañoso ya que los métodos deshabilitados deben arrojar la excepción siempre, no dependiendo del estado de la aplicación o del momento de la llamada. – Grodriguez

2

Si B no puede hacer todas las cosas una lata, no se podía tratar a B como A.

Tal vez usted necesita un contenedor, no un subclasse.

EDIT:

Así que usted entendería nunca se va a reducir la "visibilidad" de un método subclase :). Lanzar una excepción o no hacer nada no es reducir la visibilidad, por lo tanto, necesita un contenedor. A veces, esta situación es una señal de un mal diseño (solo algunas veces).

Esto está muy relacionado con circle-ellipse problem.

+0

sí Entiendo ese concepto OO de acuerdo ... Solo me interesa saber cómo otros resuelven este problema. –

4

Una fachada se utiliza cuando se quiere una interfaz más fácil o más simple para trabajar.

Debería crear su propia clase contenedora (Patrón de fachada) alrededor de su interfaz externa.

interface Foreign 
{ 
    void dontWantThis(); 
    void keepThis(); 
} 

interface/class MyForeign 
{ 
    void keepThis(); 
} 

La implementación tendría entonces una instancia de Extranjero a la que puede referir llamadas.

+0

Su sugerencia es correcta, pero esto no es una fachada. – Grodriguez

+0

@Grodriguez - De wikipedia: Una fachada es un objeto que proporciona una interfaz simplificada para un cuerpo más grande de código, como una biblioteca de clases. – willcodejavaforfood

+1

De hecho. El objetivo del patrón Facade es "proporcionar una interfaz unificada para un conjunto de interfaces en un subsistema. Facade define una interfaz de nivel superior que hace que el subsistema sea más fácil de usar". [GoF, p185]. Este no es el caso en este ejemplo; 'MyForeign' no proporciona una" interfaz simplificada "para un" subsistema complejo ", simplemente está envolviendo una sola clase. Este es el patrón Adapter/Wrapper. – Grodriguez

1

La solución probablemente usaría "composición" sobre "inherencia". Puede tener una propiedad en la clase B, de tipo A. A continuación, exponer solo los métodos en B que desea implementar realmente

+0

Esto es una especie de envoltorio. – rapadura

9

Use Composition rather than Inheritance.

es decir, la clase B contiene una referencia a una clase A y internamente llama métodos a ella.

+2

Tendré que replicar todos los métodos en 'A' que yo _do_ quiero exponer en' B', ¡y hay muchos !. ¿No mencioné que soy flojo? Lol .. –

+0

En lugar de 'Lazy' be' Eager' –

+0

Los IDEs a menudo tienen un comando de menú "Delegar" que escribirá los métodos de reenvío en un lote. –

1

Otra opción que puede considerar.

decir que quiere reducir la API de alguna clase:

public class LargeApi { 
    public void doFoo() { ... } 
    public void doBar() { ... } 
    public void doBaz() { ... } 
    ... 
    ... 
} 

De tal manera que los clientes sólo estarían expuestos a decir que el método doFoo (o cualquier método que prefiere a utilizar en su lugar):

public interface ReducedApi { 
    void doFoo(); 
} 

Pero para que las instancias de la ReducedApi para ser utilizado en cualquier lugar se espera que el LargeApi, se necesita una manera de volver a la LargeApi (preferiblemente sin casting):

public interface ReducedApi { 
    void doFoo(); 
    LargeApiClass asLargeApiClass(); 
} 

un ejemplo de implementación obligar a los clientes a usar su nueva API reducida podría ser similar al siguiente:

public class ReducedApiImpl 
    extends LargeApi 
    implements ReducedApi { 

    // Don't let them instantiate directly, force them to use the factory method 
    private ReducedApiImpl() { 

    } 

    // Notice, we're returning the ReducedApi interface 
    public static ReducedApi newInstance() { 
    return new ReducedApiImpl(); 
    } 

    @Override 
    public void doFoo() { 
    super.doFoo(); 
    } 

    @Override 
    public LargeApi asLargeApi() { 
    return this; 
    } 
} 

Ahora sus clientes pueden usar su API reducida para el caso común, pero fundido de nuevo a la gran api cuando sea necesario:

ReducedApi instance = ReducedApiImpl.newInstance(); 
instance.doFoo(); 
callSomeMethodExpectingLargeApi(instance.asLargeApi()); 
Cuestiones relacionadas