oop - publico - Razones para usar private en lugar de protected para campos y métodos
publico privado protegido java (7)
Estoy llegando aquí. Sin embargo, creo que el uso de las variables de miembros Protegidos debe hacerse conscientemente, porque no solo planea heredar, sino también porque hay una razón sólida derivada. Classed no debe usar los Setters / Getters de Propiedades definidos en la clase base.
En OOP, "encapsulamos" los campos de miembros para que podamos ejercer control sobre cómo se accede y cambia a las propiedades de la representación. Cuando definimos un getter / setter en nuestra base para una variable miembro, básicamente estamos diciendo que ESTO es cómo quiero que se haga referencia / use esta variable.
Si bien hay excepciones impulsadas por el diseño en las que uno podría necesitar modificar el comportamiento creado en los métodos getter / setter de la clase base, me parece que esto sería una decisión tomada después de una cuidadosa consideración de las alternativas.
Por ejemplo, cuando me veo en la necesidad de acceder directamente a un campo de miembro de una clase derivada, en lugar de a través de getter / setter, empiezo a pensar que tal vez esa propiedad particular debe definirse como abstracta o incluso trasladada a la clase derivada. Esto depende de cuán amplia es la jerarquía y de cualquier cantidad de consideraciones adicionales. Pero para mí, caminar alrededor de la Propiedad pública definida en la clase base comienza a oler.
Por supuesto, en muchos casos, "no importa" porque no estamos implementando nada dentro del getter / setter más allá del acceso a la variable. Pero nuevamente, si este es el caso, la clase derivada puede acceder fácilmente a través del getter / setter. Esto también protege contra errores difíciles de encontrar más adelante, si se emplea de manera consistente. Si el comportamiento del getter / setter para un campo miembro en la clase base se modifica de alguna manera, y una clase derivada hace referencia directamente al campo Protected, existe la posibilidad de problemas.
Esta es una pregunta de OO bastante básica, pero que me ha estado molestando por un tiempo.
Tiendo a evitar el uso del modificador de visibilidad ''privado'' para mis campos y métodos a favor de protected
.
Esto se debe a que, en general, no veo ningún uso para ocultar la implementación entre la clase base y la clase hija, excepto cuando deseo establecer pautas específicas para la extensión de mis clases (es decir, en marcos). Para la mayoría de los casos, creo que tratar de limitar la forma en que mi clase o otros usuarios ampliarán mi clase no es beneficiosa.
Pero, para la mayoría de las personas, el modificador private
suele ser la opción predeterminada cuando se define un campo / método no público.
Entonces, ¿puedes hacer una lista de casos de uso para private
? ¿Hay alguna razón importante para usar siempre en privado? ¿O también crees que es usado en exceso?
Prefiero privacidad que protección en el caso predeterminado porque estoy siguiendo el principio de ocultar tanto como sea posible y es por eso que establezco la visibilidad lo más bajo posible.
Estás en el camino correcto. Hace que algo sea privado, porque su implementación depende de que no lo cambie ni un usuario ni un descendiente.
De manera predeterminada, soy privado y luego tomo una decisión consciente sobre si y cuánto del funcionamiento interno voy a exponer, parece que trabaja sobre la base de que quedará expuesto de todos modos, así que continúe con eso. Mientras ambos recordemos cruzar todos los ojos y salpicar todas las camisetas, estamos bien.
Otra forma de verlo es esto. Si lo haces de forma privada, es posible que alguien no pueda hacer lo que quiere con tu implementación.
Si no lo haces de manera privada, alguien puede hacer algo que realmente no quieres que haga con tu implementación.
Hay dos situaciones en las que importa si un miembro está protected
o es private
:
- Si una clase derivada pudiera beneficiarse del uso de un miembro, hacer que el miembro esté ''protegido'' le permitiría hacerlo, mientras que hacerlo ''privado'' le negaría ese beneficio.
- Si una versión futura de la clase base podría beneficiarse si el miembro no se comporta como lo hace en la versión actual, hacer que el miembro sea "privado" permitiría que esa versión futura modifique el comportamiento (o elimine al miembro por completo), mientras lo hace `protected` requeriría que todas las versiones futuras de la clase mantengan el mismo comportamiento, negándoles así el beneficio que podría obtenerse al cambiarlo.
Si uno puede imaginar un escenario realista donde una clase derivada podría beneficiarse de poder acceder al miembro, y no puede imaginar un escenario donde la clase base podría beneficiarse al cambiar su comportamiento, entonces el miembro debería estar protected
[suponiendo, por supuesto, que no debería ser público]. Si no se puede imaginar un escenario donde una clase derivada obtendría un gran beneficio al acceder al miembro directamente, pero uno puede imaginar escenarios donde una versión futura de la clase base podría beneficiarse al cambiarla, entonces debería ser private
. Esos casos son bastante claros y directos.
Si no hay ningún escenario plausible en el que la clase base se beneficie al cambiar el miembro, sugeriría que uno debe inclinarse hacia la protected
. Algunos dirían que el principio "YAGNI" (No lo va a necesitar) favorece a los private
, pero no estoy de acuerdo. Si esperas que otros hereden la clase, hacer que un miembro sea privado no supone "YAGNI", sino "HAGNI" (No lo va a necesitar). A menos que "usted" necesite cambiar el comportamiento del artículo en una versión futura de la clase, "usted" no va a necesitar que sea private
. Por el contrario, en muchos casos no tendrá forma de predecir lo que los consumidores de su clase podrían necesitar. Eso no significa que uno deba protected
miembros sin antes tratar de identificar formas en que uno podría beneficiarse de cambiarlos, ya que YAGNI
no es realmente aplicable a ninguna de las decisiones. YAGNI se aplica en los casos en los que será posible tratar con una necesidad futura en caso de que se encuentre, de modo que no es necesario tratarla ahora. La decisión de hacer que un miembro de una clase que se da a otros programadores sea private
o protected
implica una decisión sobre qué tipo de necesidad futura potencial se proporcionará, y hará que sea difícil proporcionar para el otro.
A veces ambos escenarios serán plausibles, en cuyo caso puede ser útil ofrecer dos clases, una de las cuales expone a los miembros en cuestión y una clase derivada de la que no (no hay una idiomática estándar para una clase derivada para ocultar miembros heredado de su padre, aunque declarar nuevos miembros que tienen los mismos nombres pero no funcionalidad compilable y están marcados con un atributo Obsolete
tendría ese efecto). Como un ejemplo de las compensaciones involucradas, considere List<T>
. Si el tipo expuso el conjunto de respaldo como un miembro protegido, sería posible definir un tipo derivado CompareExchangeableList<T> where T:Class
que incluye un miembro T CompareExchangeItem(index, TT newValue, T oldvalue)
que devolvería Interlocked.CompareExchange(_backingArray[index], newValue, oldValue)
; dicho tipo podría ser utilizado por cualquier código que esperara una List<T>
, pero el código que sabía que la instancia era una CompareExchangeableList<T>
podría usar CompareExchangeItem
en él. Desafortunadamente, debido a que List<T>
no expone la matriz de respaldo a las clases derivadas, es imposible definir un tipo que permita CompareExchange
en elementos de la lista, pero que aún pueda ser usado por código esperando una List<T>
.
Aún así, eso no implica que exponer la matriz de respaldo hubiera sido completamente sin costo; aunque todas las implementaciones existentes de List<T>
usan una sola matriz de respaldo, Microsoft podría implementar futuras versiones para usar matrices múltiples cuando una lista crecería más allá de 84K, para evitar las ineficiencias asociadas con el Gran montón de objetos. Si la matriz de respaldo se expuso como miembro protegido, sería imposible implementar dicho cambio sin romper ningún código que dependiera de ese miembro.
En realidad, lo ideal podría haber sido equilibrar esos intereses proporcionando un miembro protegido que, dado un índice de elemento de lista, devolverá un segmento de matriz que contiene el elemento indicado. Si solo hay una matriz, el método siempre devolverá una referencia a esa matriz, con un desplazamiento de cero, un subíndice inicial de cero y una longitud igual a la longitud de la lista. Si una versión futura de List<T>
divide la matriz en varias partes, el método podría permitir que las clases derivadas accedan de manera eficiente a los segmentos de la matriz de maneras que no serían posibles sin dicho acceso [por ejemplo, utilizando Array.Copy
] pero List<T>
podría cambiar la forma en que administra su tienda de respaldo sin romper las clases derivadas correctamente escritas. Las clases derivadas incorrectamente escritas podrían romperse si la implementación base cambia, pero eso es culpa de la clase derivada, no de la base.
Soy un principiante en OOP pero he estado presente desde los primeros artículos en ACM e IEEE. Por lo que recuerdo, este estilo de desarrollo fue más para modelar algo. En el mundo real, las cosas, incluidos los procesos y las operaciones, tendrían elementos "privados, protegidos y públicos". Entonces para ser fiel al objeto .....
Fuera de modelar algo, la programación se trata más de resolver un problema. El problema de los elementos "privados, protegidos y públicos" solo es una preocupación cuando se trata de hacer una solución confiable. Como solucionador de problemas, no cometería el error de obtener tos en cómo otros usan mi solución para resolver sus propios problemas. Ahora tenga en cuenta que una razón principal para el problema de ...., fue permitir un lugar para la verificación de datos (es decir, verificar que los datos estén en un rango y estructura válidos antes de usarlos en su objeto).
Con eso en mente, si su código resuelve el problema para el que fue diseñado, ha hecho su trabajo. Si otros necesitan su solución para resolver el mismo problema o uno similar, ¿realmente necesita controlar cómo lo hacen? Yo diría que "solo si obtiene algún beneficio o si conoce las debilidades en su diseño, entonces necesita proteger algunas cosas".
He estado programando OOP desde C ++ en 1993 y Java en 1995. Una y otra vez he visto la necesidad de aumentar o revisar una clase, por lo general añadiendo funcionalidades extra integradas con la clase. La forma de OOP para hacerlo es subclasificar la clase base y hacer los cambios en la subclase. Por ejemplo, un campo de clase base referido originalmente solo en otro lugar en la clase base es necesario para alguna otra acción, o alguna otra actividad debe cambiar un valor del campo (o uno de los miembros contenidos en el campo). Si ese campo es privado en la clase base, entonces la subclase no puede acceder a él, no puede extender la funcionalidad. Si el campo está protegido, puede hacerlo.
Las subclases tienen una relación especial con la clase base que otras clases en otras partes de la jerarquía de clases no tienen: heredan los miembros de la clase base. El propósito de la herencia es acceder a los miembros de la clase base; herencia de frustraciones privadas. ¿Cómo se supone que el desarrollador de la clase base debe saber que ninguna subclase necesitará acceder a un miembro? En algunos casos, eso puede ser claro, pero privado debe ser la excepción en lugar de la regla. Los desarrolladores que subclasan la clase base tienen el código fuente de la clase base, por lo que su alternativa es revisar la clase base directamente (tal vez simplemente cambiando el estado privado a protegido antes de la subclasificación). Eso no es bueno, es una buena práctica, pero eso es lo que hace que uno haga lo privado.
Existe cierto consenso de que uno debería preferir la composición a la herencia en OOP. Hay varias razones para esto (google si estás interesado), pero la parte principal es que:
- la herencia rara vez es la mejor herramienta y no es tan flexible como otras soluciones
- los miembros / campos protegidos forman una interfaz hacia sus subclases
- las interfaces (y las suposiciones sobre su uso futuro) son difíciles de corregir y documentar adecuadamente
Por lo tanto, si elige que su clase sea heredable, debe hacerlo concienzudamente y con todos los pros y contras en mente.
Por lo tanto, es mejor no hacer que la clase sea heredable y, en su lugar, asegurarse de que sea lo más flexible posible (y no más) mediante el uso de otros medios.
Esto es principalmente obvio en marcos más grandes donde el uso de su clase está más allá de su control. Para su pequeña aplicación, no lo notará tanto , pero (herencia por defecto) lo morderá tarde o temprano si no tiene cuidado.
Alternativas
La composición significa que expondrías la personalización a través de interfaces explícitas (totalmente abstractas) (virtuales o basadas en plantillas).
Entonces, en lugar de tener una clase base de vehículo con una función de disco virtual () (junto con todo lo demás, como un entero de precio, etc.), tendría una clase de vehículo tomando un objeto de interfaz de motor y esa interfaz de motor solo expone la función drive (). Ahora puede agregar y reutilizar cualquier tipo de motor en cualquier lugar (más o menos :).