Privado vs.Miembros públicos en la práctica (¿qué importancia tiene la encapsulación?) [cerrado]

StackOverflow https://stackoverflow.com/questions/99688

  •  01-07-2019
  •  | 
  •  

Pregunta

Una de las mayores ventajas de la programación orientada a objetos es la encapsulación, y una de las "verdades" que nos han enseñado (o, al menos, a mí) es que los miembros siempre deben ser privados y estar disponibles a través de accesor y mutador. métodos, asegurando así la capacidad de verificar y validar los cambios.

Sin embargo, tengo curiosidad por saber qué importancia tiene esto en la práctica.En particular, si tiene un miembro más complicado (como una colección), puede ser muy tentador simplemente hacerlo público en lugar de crear un montón de métodos para obtener las claves de la colección, agregar/eliminar elementos de la colección, etc.

¿Sigues la regla en general?¿Su respuesta cambia dependiendo de si es un código escrito por usted mismo o si es un código escrito por usted?para ser usado por otros?¿Hay razones más sutiles que me faltan para esta ofuscación?

¿Fue útil?

Solución

Eso depende.Esta es una de esas cuestiones que deben decidirse de manera pragmática.

Supongamos que tengo una clase para representar un punto.Podría tener captadores y definidores para las coordenadas X e Y, o podría simplemente hacerlos públicos y permitir el libre acceso de lectura/escritura a los datos.En mi opinión, esto está bien porque la clase actúa como una estructura glorificada: una colección de datos con quizás algunas funciones útiles adjuntas.

Sin embargo, hay muchas circunstancias en las que no desea proporcionar acceso completo a sus datos internos y confiar en los métodos proporcionados por la clase para interactuar con el objeto.Un ejemplo sería una solicitud y respuesta HTTP.En este caso, es una mala idea permitir que cualquiera envíe cualquier cosa por cable; debe ser procesado y formateado mediante los métodos de la clase.En este caso, la clase se concibe como un objeto real y no como un simple almacén de datos.

Realmente todo se reduce a si los verbos (métodos) impulsan la estructura o si los datos sí lo hacen.

Otros consejos

Como alguien que tiene que mantener un código de varios años de antigüedad en el que muchas personas trabajaron en el pasado, tengo muy claro que si un atributo de miembro se hace público, eventualmente se abusará de él.Incluso he escuchado a personas que no están de acuerdo con la idea de accesores y mutadores, ya que eso todavía no cumple con el propósito de la encapsulación, que es "ocultar el funcionamiento interno de una clase".Obviamente es un tema controvertido, pero mi opinión sería "hacer que cada variable miembro sea privada, pensar principalmente en lo que la clase tiene que hacer". hacer (métodos) en lugar de cómo vas a dejar que la gente cambie las variables internas".

Sí, la encapsulación importa.Exponer la implementación subyacente hace (al menos) dos cosas mal:

  1. Mezcla responsabilidades.Las personas que llaman no deberían necesitar ni querer comprender la implementación subyacente.Sólo deberían querer que la clase haga su trabajo.Al exponer la implementación subyacente, su clase no está haciendo su trabajo.En cambio, simplemente está trasladando la responsabilidad a la persona que llama.
  2. Lo vincula a la implementación subyacente.Una vez que expones la implementación subyacente, estás atado a ella.Si les dice a las personas que llaman, por ejemplo, que hay una colección debajo, no podrá cambiar fácilmente la colección por una nueva implementación.

Estos (y otros) problemas se aplican independientemente de si brinda acceso directo a la implementación subyacente o simplemente duplica todos los métodos subyacentes.Debería exponer la implementación necesaria y nada más.Mantener la implementación privada hace que el sistema general sea más fácil de mantener.

Prefiero mantener a los miembros privados el mayor tiempo posible y acceder a ellos solo a través de captadores, incluso desde la misma clase.También trato de evitar los definidores como primer borrador para promover objetos de estilo de valor siempre que sea posible.Al trabajar mucho con la inyección de dependencia, a menudo hay definidores pero no captadores, ya que los clientes deberían poder configurar el objeto pero (otros) no pueden saber qué está realmente configurado, ya que este es un detalle de implementación.

Saludos, Ollie

Tiendo a seguir la regla de manera bastante estricta, incluso cuando es solo mi propio código.Me gustan mucho las Propiedades en C# por ese motivo.Hace que sea realmente fácil controlar qué valores se le dan, pero aún puedes usarlos como variables.O hacer que el conjunto sea privado y público, etc.

Básicamente, la ocultación de información tiene que ver con la claridad del código.Está diseñado para facilitar que otra persona extienda su código y evitar que cree errores accidentalmente cuando trabaja con los datos internos de sus clases.Se basa en el principio de que nadie lee nunca los comentarios, especialmente aquellos con instrucciones en ellos.

Ejemplo: Estoy escribiendo un código que actualiza una variable y necesito asegurarme absolutamente de que la GUI cambie para reflejar el cambio. La forma más sencilla es agregar un método de acceso (también conocido como "Setter"), que se llama en lugar de actualizar los datos. está actualizado.

Si hago públicos esos datos y algo cambia la variable sin pasar por el método Setter (y esto sucede cada vez que se dicen malas palabras), entonces alguien necesitará pasar una hora depurando para descubrir por qué no se muestran las actualizaciones.Lo mismo se aplica, en menor medida, a la "Obtención" de datos.Podría poner un comentario en el archivo de encabezado, pero lo más probable es que nadie lo lea hasta que algo salga terriblemente mal.Hacerlo cumplir con privado significa que el error no poder porque se mostrará como un error en tiempo de compilación fácilmente localizable, en lugar de un error en tiempo de ejecución.

Por experiencia, las únicas ocasiones en las que querrás hacer pública una variable miembro y dejar de lado los métodos Getter y Setter es si quieres dejar absolutamente claro que cambiarla no tendrá efectos secundarios;especialmente si la estructura de datos es simple, como una clase que simplemente contiene dos variables como un par.

Esto debería ser algo bastante raro, ya que normalmente desear efectos secundarios, y si la estructura de datos que está creando es tan simple que no la necesita (por ejemplo, un emparejamiento), ya habrá una escrita de manera más eficiente disponible en una biblioteca estándar.

Dicho esto, para la mayoría de los programas pequeños que son de un solo uso y sin extensión, como los que se obtienen en la universidad, es más una "buena práctica" que cualquier otra cosa, porque los recordarás mientras los escribes, y luego Los entregaré y nunca más tocaré el código.Además, si está escribiendo una estructura de datos como una forma de descubrir cómo almacenan los datos en lugar de como un código de lanzamiento, entonces hay un buen argumento de que los Getters y Setters no ayudarán y se interpondrán en el camino de la experiencia de aprendizaje. .

Sólo cuando llegas al lugar de trabajo o a un proyecto grande, donde existe la probabilidad de que tu código sea llamado por objetos y estructuras escritas por diferentes personas, se vuelve vital fortalecer estos "recordatorios".Si se trata o no de un proyecto de un solo hombre es sorprendentemente irrelevante, por la sencilla razón de que "tú dentro de seis semanas" eres una persona tan diferente como un compañero de trabajo.Y "yo hace seis semanas" a menudo resulta ser vago.

Un último punto es que algunas personas son bastante entusiastas a la hora de ocultar información y se molestarán si sus datos son innecesariamente públicos.Lo mejor es seguirles la corriente.

Las propiedades de C# 'simulan' campos públicos.Se ve muy bien y la sintaxis realmente acelera la creación de esos métodos get/set.

Tenga en cuenta la semántica de invocar métodos en un objeto.Una invocación de método es una abstracción de muy alto nivel que se puede implementar en el compilador o en el sistema de tiempo de ejecución de diferentes maneras.

Si el objeto cuyo método está invocando existe en el mismo proceso/mapa de memoria, entonces un compilador o VM podría optimizar un método para acceder directamente al miembro de datos.Por otro lado, si el objeto vive en otro nodo en un sistema distribuido, entonces no hay forma de que pueda acceder directamente a sus miembros de datos internos, pero aún puede invocar sus métodos enviándole un mensaje.

Al codificar en interfaces, puede escribir código al que no le importa dónde existe el objeto de destino o cómo se invocan sus métodos o incluso si está escrito en el mismo idioma.


En su ejemplo de un objeto que implementa todos los métodos de una colección, entonces seguramente ese objeto realmente es Una colección.entonces tal vez este sería un caso en el que la herencia sería mejor que la encapsulación.

Se trata de controlar lo que la gente puede hacer con lo que les das.Cuanto más controlador seas, más suposiciones podrás hacer.

Además, teóricamente puedes cambiar la implementación subyacente o algo así, pero en su mayor parte es:

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

Es un poco difícil de justificar.

La encapsulación es importante cuando se cumple al menos uno de estos:

  1. Cualquiera menos usted usará su clase (o romperán sus invariantes porque no leen la documentación).
  2. Cualquiera que no lea la documentación utilizará su clase (o romperá sus invariantes cuidadosamente documentadas).Tenga en cuenta que esta categoría lo incluye a usted dentro de dos años.
  3. En algún momento en el futuro, alguien heredará de su clase (porque tal vez sea necesario realizar una acción adicional cuando cambia el valor de un campo, por lo que tiene que haber un definidor).

Si es solo para mí y se usa en algunos lugares y no voy a heredar de él, y cambiar los campos no invalidará ninguna invariante que asuma la clase, sólo entonces De vez en cuando haré público un campo.

Mi tendencia es intentar que todo sea privado si es posible.Esto mantiene los límites de los objetos lo más claramente definidos posible y mantiene los objetos lo más desacoplados posible.Me gusta esto porque cuando tengo que reescribir un objeto que fallé la primera (¿segunda, quinta?) vez, mantiene el daño contenido en una cantidad menor de objetos.

Si acopla los objetos con suficiente fuerza, puede resultar más sencillo combinarlos en un solo objeto.Si relajas lo suficiente las restricciones de acoplamiento, volverás a la programación estructurada.

Puede ser que si descubre que muchos de sus objetos son solo funciones de acceso, debería repensar las divisiones de sus objetos.Si no realiza ninguna acción sobre esos datos, es posible que formen parte de otro objeto.

Por supuesto, si está escribiendo algo como una biblioteca, desea una interfaz lo más clara y nítida posible para que otros puedan programar en ella.

Adapte la herramienta al trabajo...Recientemente vi un código como este en mi código base actual:

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

Y luego esta clase se usó internamente para pasar fácilmente múltiples valores de datos.No siempre tiene sentido, pero si solo tienes DATOS, sin métodos, y no los expones a los clientes, me parece un patrón bastante útil.

El uso más reciente que tuve de esto fue una página JSP donde se mostraba una tabla de datos, definida en la parte superior de forma declarativa.Entonces, inicialmente estaba en múltiples matrices, una matriz por campo de datos...esto terminó en que el código fuera bastante difícil de leer, ya que los campos no estaban uno al lado del otro en la definición que se mostraría juntos...así que creé una clase simple como la anterior que lo uniría...el resultado fue un código REALMENTE legible, mucho más que antes.

Moral...a veces deberías considerar alternativas "malas aceptadas" si pueden hacer que el código sea más simple y fácil de leer, siempre y cuando lo pienses detenidamente y consideres las consecuencias...No aceptes ciegamente TODO lo que escuchas.

Dicho eso...Los captadores y definidores públicos son prácticamente equivalentes a los campos públicos...al menos esencialmente (hay un poco más de flexibilidad, pero sigue siendo un mal patrón para aplicar a CADA campo que tenga).

Incluso las bibliotecas estándar de Java tienen algunos casos de campos publicos.

Cuando hago que los objetos tengan significado, son más fácil a usar y más fácil de mantener.

Por ejemplo:Persona.Mano.Agarra(qué rápido, cuánto);

El truco no consiste en pensar en los miembros como valores simples sino como objetos en sí mismos.

Yo diría que esta pregunta confunde el concepto de encapsulación con "ocultamiento de información".
(Esto no es una crítica, ya que parece coincidir con una interpretación común de la noción de "encapsulación")

Sin embargo, para mí, 'encapsulación' es:

  • el proceso de reagrupar varios artículos en un contenedor
  • el propio contenedor reagrupando los artículos

Supongamos que está diseñando un sistema de contribuyentes.Para cada contribuyente, usted podría encapsular la noción de niño en

  • una lista de niños que representan a los niños
  • un mapa de tiene en cuenta a los niños de diferentes padres
  • un objeto Niños (no Niño) que proporcionaría la información necesaria (como el número total de niños)

Aquí tienes tres tipos diferentes de encapsulaciones, 2 representadas por un contenedor de bajo nivel (lista o mapa), uno representado por un objeto.

Al tomar esas decisiones, no

  • hacer que esa encapsulación sea pública, protegida o privada:la elección de "ocultar información" aún está por tomarse
  • hacer una abstracción completa (necesita refinar los atributos del objeto Children y puede decidir crear un objeto Child, que mantendría solo la información relevante desde el punto de vista de un sistema de contribuyente)
    La abstracción es el proceso de elegir qué atributos del objeto son relevantes para su sistema y cuáles deben ignorarse por completo.

Entonces mi punto es:
Esa pregunta puede titularse:
Privado vs.Miembros públicos en la práctica (¿qué importancia tiene? ocultación de información?)

Pero sólo mis 2 centavos.Respeto perfectamente que se pueda considerar la encapsulación como un proceso que incluye la decisión de "ocultar información".

Sin embargo, siempre trato de diferenciar 'abstracción' - 'encapsulación' - 'ocultamiento o visibilidad de información'.

@VonC

Puede que le resulte interesante leer el "Modelo de referencia de procesamiento distribuido abierto" de la Organización Internacional de Normalización.Se define:"Encapsulación:la propiedad de que la información contenida en un objeto es accesible sólo a través de interacciones en las interfaces soportadas por el objeto".

Intenté defender que la ocultación de información es una parte fundamental de esta definición aquí:http://www.edmundkirwan.com/encap/s2.html

Saludos,

Ed.

Encuentro que muchos captadores y definidores son olor a código que la estructura del programa no está bien diseñada.Deberías mirar el código que usa esos captadores y definidores, y buscar funcionalidades que realmente deberían ser parte de la clase.En la mayoría de los casos, los campos de una clase deben ser detalles de implementación privados y sólo los métodos de esa clase pueden manipularlos.

Tener captadores y definidores equivale a que el campo sea público (cuando los captadores y definidores son triviales/generados automáticamente).A veces podría ser mejor simplemente declarar los campos públicos, para que el código sea más simple, a menos que necesite polimorfismo o un marco requiera métodos get/set (y no pueda cambiar el marco).

Pero también hay casos en los que tener captadores y definidores es un buen patrón.Un ejemplo:

Cuando creo la GUI de una aplicación, trato de mantener el comportamiento de la GUI en una clase (FooModel) para que pueda probarse fácilmente por unidad y tener la visualización de la GUI en otra clase (FooView) que pueda probarse. sólo manualmente.La vista y el modelo se unen con un simple código de pegamento;cuando el usuario cambia el valor del campo x, la vista llama setX(String) en el modelo, lo que a su vez puede generar un evento de que alguna otra parte del modelo ha cambiado, y la vista obtendrá los valores actualizados del modelo con captadores.

En un proyecto, hay un modelo de GUI que tiene 15 captadores y definidores, de los cuales sólo 3 métodos de obtención son triviales (de modo que el IDE podría generarlos).Todos los demás contienen alguna funcionalidad o expresiones no triviales, como las siguientes:

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

En la práctica siempre sigo una sola regla: la regla de "no hay talla para todos".

La encapsulación y su importancia es producto de su proyecto.¿Qué objeto accederá a su interfaz, cómo la usarán, importará si tienen derechos de acceso innecesarios a los miembros?esas preguntas y otras similares debe hacerse cuando trabaje en la implementación de cada proyecto.

Baso mi decisión en la profundidad del Código dentro de un módulo.Si estoy escribiendo código interno de un módulo y que no interactúa con el mundo exterior, no encapsulo tanto las cosas con privado porque afecta el rendimiento de mi programador (qué tan rápido puedo escribir y reescribir mi código).

Pero para los objetos que sirven como interfaz del módulo con código de usuario, me adhiero a patrones de privacidad estrictos.

Ciertamente, hace la diferencia si escribe código interno o código para ser utilizado por otra persona (o incluso por usted mismo, pero como una unidad contenida). Cualquier código que se vaya a utilizar externamente debe tener una interfaz bien definida/documentada que usted pueda utilizar. Querrás cambiar lo menos posible.

Para el código interno, dependiendo de la dificultad, es posible que le resulte menos complicado hacer las cosas de forma sencilla ahora y pagar una pequeña penalización más adelante.Por supuesto, la ley de Murphy garantizará que la ganancia a corto plazo se borre muchas veces al tener que realizar cambios de gran alcance más adelante cuando sea necesario cambiar los aspectos internos de una clase que no se pudo encapsular.

Específicamente en su ejemplo de uso de una colección que devolvería, parece posible que la implementación de dicha colección pueda cambiar (a diferencia de las variables miembro más simples), lo que aumenta la utilidad de la encapsulación.

Dicho esto, me gusta un poco la forma en que Python lo aborda.Las variables miembro son públicas de forma predeterminada.Si desea ocultarlos o agregar validación, se proporcionan técnicas, pero se consideran casos especiales.

Sigo las reglas sobre esto casi todo el tiempo.Para mí, hay cuatro escenarios: básicamente, la regla en sí y varias excepciones (todas influenciadas por Java):

  1. Utilizable por cualquier cosa fuera de la clase actual, se accede a través de captadores/definidores
  2. El uso interno a la clase generalmente está precedido por "esto" para dejar claro que no es un parámetro de método.
  3. Algo destinado a permanecer extremadamente pequeño, como un objeto de transporte, básicamente una serie de atributos;todo publico
  4. Se necesita que no sea privado para alguna extensión

Aquí hay una preocupación práctica que no se aborda en la mayoría de las respuestas existentes.La encapsulación y la exposición de interfaces limpias y seguras a código externo siempre es excelente, pero es mucho más importante cuando el código que estás escribiendo está destinado a ser consumido por una base de "usuarios" espacial y/o temporalmente grande.Lo que quiero decir es que si planeas que alguien (incluso tú) mantenga el código durante mucho tiempo en el futuro, o si estás escribiendo un módulo que interactuará con el código de más de un puñado de otros desarrolladores, debes pensar mucho más. con más cuidado que si escribe código único o escrito en su totalidad por usted.

Honestamente, sé lo miserable que es esta práctica de ingeniería de software, pero muchas veces hago todo público al principio, lo que hace que las cosas sean un poco más rápidas de recordar y escribir, y luego agrego encapsulación cuando tenga sentido.Las herramientas de refactorización en los IDE más populares hoy en día determinan el enfoque que se utiliza (agregar encapsulación versus encapsulación).quitándolo) mucho menos relevante de lo que solía ser.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top