Código como:
public Thing[] getThings(){
return things;
}
No tiene mucho sentido, ya que su método de acceso no está haciendo nada más que devolver directamente la estructura de datos interna. Es lo mismo que simplemente declara Thing[] things
ser public
. La idea detrás de un método de acceso es crear una interfaz que aísle a los clientes de los cambios internos y les impida manipular la estructura de datos real, excepto en formas discretas, según lo permita la interfaz. Como descubrió cuando se rompió todo el código de su cliente, su método de acceso no lo hizo, es solo código desperdiciado. Creo que muchos programadores tienden a escribir código como ese porque aprendieron en alguna parte que todo debe encapsularse con métodos de acceso, pero eso es por las razones que expliqué. Hacerlo solo para "seguir la forma" cuando el método de acceso no sirve para nada es solo ruido.
Definitivamente recomendaría su solución propuesta, que cumple algunos de los objetivos más importantes de la encapsulación: brindar a los clientes una interfaz sólida y discreta que los aísle de los detalles de implementación internos de su clase y no les permita tocar la estructura de datos interna espere en las formas que usted decida que son apropiadas: "la ley del privilegio menos necesario". Si observa los grandes marcos de OOP populares, como el CLR, el STL, el VCL, el patrón que ha propuesto está muy extendido, exactamente por esa razón.
¿Deberías hacer eso siempre ? No necesariamente. Por ejemplo, si tiene clases auxiliares o de amigos que son esencialmente un componente de su clase principal de trabajo y no están "orientadas al frente", no es necesario, es una exageración que agregará una gran cantidad de código innecesario. Y en ese caso, no usaría ningún método de acceso, no tiene sentido, como se explicó. Simplemente declare la estructura de datos de una manera que esté limitada solo a la clase principal que la usa, la mayoría de los idiomas admiten formas de hacerlo friend
, o declarándola en el mismo archivo que la clase de trabajador principal, etc.
El único inconveniente que puedo ver en su propuesta es que es más trabajo codificar (y ahora tendrá que volver a codificar sus clases de consumidor, pero debe / debe hacerlo de todos modos). Pero eso no es realmente un inconveniente - necesitas hacerlo bien, y a veces eso requiere más trabajo.
Una de las cosas que hace que un buen programador sea bueno es que saben cuándo vale la pena el trabajo extra y cuándo no. A la larga, poner el extra ahora dará sus frutos con grandes dividendos en el futuro, si no en este proyecto, en otros. Aprenda a codificar de la manera correcta y use su mente al respecto, no solo siga los formularios prescritos robóticamente.
Tenga en cuenta que las colecciones más complejas que las matrices pueden requerir que la clase adjunta implemente incluso más de tres métodos solo para acceder a la estructura de datos interna.
Si está exponiendo una estructura de datos completa a través de una clase que contiene, en mi opinión, debe pensar por qué esa clase está encapsulada, si no es simplemente para proporcionar una interfaz más segura: una "clase de envoltura". Estás diciendo que la clase que contiene no existe para ese propósito, así que tal vez hay algo que no está bien en tu diseño. Considere dividir sus clases en módulos más discretos y superponerlos.
Una clase debe tener un propósito claro y discreto, y proporcionar una interfaz para admitir esa funcionalidad, nada más. Puede que estés tratando de agrupar cosas que no estén juntas. Cuando haces eso, las cosas se romperán cada vez que tengas que implementar un cambio. Cuanto más pequeñas y discretas sean sus clases, más fácil será cambiar las cosas: piense en LEGO.
get(index)
,add()
,size()
,remove(index)
, yremove(Object)
. Usando la técnica propuesta, la clase que contiene esta ArrayList debe tener cinco métodos públicos solo para delegar en la colección interna. Y el propósito de esta clase en el programa probablemente no sea encapsular esta ArrayList, sino más bien hacer otra cosa. ArrayList es solo un detalle. [...]remove
no es solo de lectura. Entiendo que el OP quiere hacer público todo, como en el código original antes del cambio propuesto.public Thing[] getThings(){return things;}
Eso es lo que no me gusta.Usted preguntó: ¿Debería encapsular siempre una estructura de datos interna por completo?
Breve respuesta: Sí, la mayoría de las veces pero no siempre .
Respuesta larga: Creo que las clases siguen las siguientes categorías:
Clases que encapsulan datos simples. Ejemplo: punto 2D. Es fácil crear funciones públicas que brinden la capacidad de obtener / establecer las coordenadas X e Y, pero puede ocultar los datos internos fácilmente sin demasiados problemas. Para tales clases, no es necesario exponer los detalles internos de la estructura de datos.
Clases de contenedores que encapsulan colecciones. STL tiene las clases clásicas de contenedores. Lo considero
std::string
ystd::wstring
entre esos también. Proporcionan una interfaz rica para hacer frente a las abstracciones, sinostd::vector
,std::string
ystd::wstring
también proporcionan la capacidad de obtener acceso a los datos en bruto. No me apresuraría a llamarlas clases mal diseñadas. No sé la justificación para estas clases que exponen sus datos en bruto. Sin embargo, en mi trabajo, he encontrado que es necesario exponer los datos sin procesar cuando se trata con millones de nodos de malla y datos en esos nodos de malla por razones de rendimiento.Lo importante de exponer la estructura interna de una clase es que tienes que pensar mucho antes de darle una señal verde. Si la interfaz es interna a un proyecto, será costoso cambiarla en el futuro pero no imposible. Si la interfaz es externa al proyecto (como cuando está desarrollando una biblioteca que será utilizada por otros desarrolladores de aplicaciones), puede ser imposible cambiar la interfaz sin perder sus clientes.
Clases que son en su mayoría funcionales por naturaleza. Ejemplos:
std::istream
,std::ostream
, iteradores de los contenedores STL. Es completamente tonto exponer los detalles internos de estas clases.Clases híbridas Estas son clases que encapsulan cierta estructura de datos pero también proporcionan funcionalidad algorítmica. Personalmente, creo que estos son el resultado de un diseño mal pensado. Sin embargo, si los encuentra, debe decidir si tiene sentido exponer sus datos internos caso por caso.
En conclusión: la única vez que he encontrado necesario exponer la estructura interna de datos de una clase es cuando se convirtió en un cuello de botella en el rendimiento.
fuente
En lugar de devolver los datos sin procesar directamente, intente algo como esto
Por lo tanto, básicamente está proporcionando una colección personalizada que presenta cualquier cara al mundo que se desee. En su nueva implementación, entonces,
Ahora tiene la encapsulación adecuada, oculta los detalles de implementación y proporciona compatibilidad con versiones anteriores (a un costo).
fuente
List
. Los métodos de acceso que simplemente devuelven miembros de datos, incluso con un reparto para hacer las cosas más robustas, no son realmente buenas encapsulaciones IMO. La clase trabajadora debería manejar todo eso, no el cliente. Cuanto más "tonto" sea un cliente, más robusto será. Como comentario aparte, no estoy seguro de que hayas respondido la pregunta ...Deberías usar interfaces para esas cosas. No ayudaría en su caso, ya que la matriz de Java no implementa esas interfaces, pero debe hacerlo a partir de ahora:
De esa manera usted puede cambiar
ArrayList
aLinkedList
o cualquier otra cosa, y no se va a romper cualquier código dado que todas las colecciones de Java (al lado de arrays) que tienen (pseudo?) De acceso aleatorio probablemente implementoList
.También puede usar
Collection
, que ofrece menos métodos que,List
pero puede admitir colecciones sin acceso aleatorio, oIterable
incluso puede admitir transmisiones, pero no ofrece mucho en términos de métodos de acceso ...fuente
List
como clases derivadas, tendría más sentido, pero simplemente "esperar lo mejor" no es una buena idea. "Un buen programador mira en ambos sentidos en una calle de sentido único".List
(oCollection
, o al menosIterable
). Ese es el objetivo de estas interfaces, y es una pena que las matrices de Java no las implementen, pero son una interfaz oficial para colecciones en Java, por lo que no es descabellado asumir que cualquier colección de Java las implementará, a menos que esa colección sea más antigua queList
, y en ese caso, es muy fácil envolverlo con AbstractList .List
enArrayList
, pero no es igual que la implementación puede ser protegido 100% - siempre se puede utilizar la reflexión para acceder a los campos privados. Hacerlo está mal visto, pero el lanzamiento también está mal visto (aunque no tanto). El objetivo de la encapsulación no es evitar el pirateo malintencionado, sino evitar que los usuarios dependan de los detalles de implementación que desee cambiar. El uso de laList
interfaz hace exactamente eso: los usuarios de la clase pueden depender de laList
interfaz en lugar de laArrayList
clase concreta que podría cambiar.Esto es bastante común para ocultar su estructura de datos internos del mundo exterior. En algún momento es excesivo especialmente en DTO. Recomiendo esto para el modelo de dominio. Si es necesario exponer, devuelva la copia inmutable. Junto con esto, sugiero crear una interfaz que tenga estos métodos como obtener, establecer, eliminar, etc.
fuente