Vuelvo con otra de mis dudas existenciales después de asistir a varias clases de programación. Esta vez se trata del encapsulamiento.
El otro día, el profesor, explicándonos la Programación Orientada a Objetos, nos comentó que como norma de estilo existe el convenio de encapsular las propiedades de las clases con el fin de que si el usuario-programador (quien usa la clase) altera el valor de un atributo, otros atributos que puedan estar relacionados con él mantengan la integridad. Es decir, que se tiene la costumbre de declarar las propiedades con ámbito privado y crear métodos consultores y modificadores, de forma que un usuario-programador no pueda hacer esto:
oFactura:nIva := 7
Sino que tenga que hacer esto otro:
oFactura:SetIva( 7 )
De esta forma, el programador de la clase puede controlar en el método modificador del IVA que el total de la factura sea el que debe ser:
METHOD SetIva( nIva ) CLASS TFactura ::nIva := nIva ::nTotal := ::nImporte + ::nImporte * ::nIva/100 RETURN nil
La idea desde luego que es buena. Lo que no tengo claro es por qué llevo dos años usando Fivewin y accediendo a las propiedades de todas las clases sin ninguna restricción. Incluso en Xailer, un producto que está saliendo ahora, ocurre lo mismo. Uno puede hacer esto sin ningún problema:
WITH OBJECT TLabel():New( oForm ) :cText := "Texto de la etiqueta" :Create() END
Y no se vé obligado a usar un método :SetText( cTexto ). No digo que no se pueda hacer, pero al menos no está obligado a hacerlo, como he entendido de la explicación de mi profesor.
¿Y por qué esto? Una de tres: o Fivewin y Xailer son excepciones que confirman la regla (mucha coincidencia me parece a mí), o el encapsulamiento no es un convenio tan aceptado como nos han enseñado (aun así entendería que lo hicieran para que aprendiéramos buenas técnicas), o hay algún punto en el que me he colado y en los comentarios de este post me van a canear finamente :)
comentarios (12) |
Jaime, como muchas cosas en esta vida, una cosa es lo que se deberia hacer y otra lo que se hace ;-)
Yo no es que haya estudiado la OOP seriamente, pero en gran medida tiene su sentido en la encapsulación, y esta en la programación orientada a "cajas negras".
Al final, lo que algunos llaman convenios, no son más que meras recomendaciones para mantener controlado el código.
Yo recomendaria que ya que uno se mete con los objetos, hacer las cosas con su sentido de ser, implica más trabajo inicial, pero luego te ahorras errores tontos...
Efectivamente, es verdad que no hay ningún convenio para la encapsulación obligada a la hora de programar. Es simplemente una forma correcta, segura y aconsejable de crear las clases y sus propiedades por muchas razones basadas, sobre todo, en la experiencia. Recuerdo que mi profesor nos dijo exactamente lo mismo, aunque matizó que tampoco era una barbaridad observar lo contrario por ahí. Yo personalmente, como tú, lo recomiendo.
La verdad, se supone que es un convenio, pero como en todo convenio, no se puede ser totalmente inflexible; llegan momentos en los que una clase (objeto) se puede llegar a hacer tan compleja, que resulta imposible mantener una estructura de programacion tan rigida, y menos aun cuando puede que necesites acceder a variables de la clase, no solo mediante metodos internos de la misma, sino usando metodos externos; con lo cual se llega a una conclusion, que suele ser para muchos casos distintos, la misma. "Ni tanto ni tan calvo", es decir, nunca se puede ser 100% rigido ni 100% flexible a la hora de seguir un estilo o norma , o canon o como lo quieras llamar , de programacion. Ha de haber un equilibrio, entre "belleza" y utilidad personal del codigo; ya que al final, cada programador/a tiene un estilo propio.
Yo creo que más que un convenio es una buena práctica.
Yo estoy de acuerdo con que es una buena práctica, y es muy interesante seguirla. Las razones son muchas:
1/ Mantener la integridad, haciendo que una asignación a un atributo modifique otros atributos relacionados. (esta es la razón que te han dado)
2/ Permitirte evolucionar la clase más fácilmente. Podría ser que un determinado atributo sea más tarde calculado a partir de otros. De esta manera, puedes hacer desaparecer físicamente el atributo de la clase pero conservas los "getter" y "setters" y las clases clientes no se verían afectadas.
3/ Permite crear interacciones más ricas con la clase. Por ejemplo, cada vez que se lee o escribe un atributo, se podría registrar la acción, lanzar un evento, etc.
En definitiva todo son ventajas y ningún inconvenitente. El único inconveniente es que se puede hacer algo pesado escribir todos los setters y getters. No obstante, muchos entornos de desarrollo tienen ayudas para escribir estos métodos sencillos, e incluso creo que hay lenguajes como Delphi (Object Pascal) y C# que automatizan el proceso ellos mismos, generándote los métodos implícitamente (para los casos sencillos).
Un saludo.
Jaime,
como veo que has nombrado a Xailer, te diré que Xailer sí utiliza el encapsulamiento de propiedades. Echa un vistazo a doc/introduccion.txt; ahí tienes una explicación detallada. No obstante, te comento por encima:
Al construir una clase en Xailer, puedes poner:
PROPERTY nValue WRITE ::SetValue( Value )
y después desarrollar el método SetValue. Eso te permite llamar al método SetValue para asignar el valor y también asignar directamente la propiedad. Pero cuando haces esto último, en realidad estás llamando al método indirectamente, y no modificas directamente el valor de la propiedad. También existe la cláusula READ para cuando lees el valor de la propiedad.
La ventaja de hacerlo así en vez de obligar a usar siempre el método Set correspondiente es la legibilidad del código. P.ej. si tienes que incrementar el valor de una propiedad, si estás obligado a usar el método Set lo tendrías que hacer así:
Obj:SetValue( Obj:GetValue() + 1 )
en cambio, en Xailer puedes hacer simplemente:
Obj:nValue++
y ya se encargará Xailer de llamar a los métodos Get y Set correspondientes.
No obstante, si no utilizas las cláusulas READ y WRITE en la declaración de la propiedad, entonces sí estarás accediendo directamente a esa propiedad. Esto también puede ser válido en muchas situaciones. Pero aún así, si en algún momento necesitaras encapsular la asignación de esa propiedad, sólo tienes que modificar la clase añadiendo esas cláusulas en la propiedad y desarrollando los métodos Get y Set, pero no tienes que tocar absolutamente nada en el resto del código de tu programa.
Y esto no es exclusivo de Xailer; p.ej. Delphi también tiene un sistema muy parecido.
Un saludo.
José,
Hace unos días que leí el introduccion.txt y me gustó especialmente esto que comentas de los PROPERTYs. Sabía lo del READ y WRITE, lo que pasa es que entendía con esto que sólo servía para propiedades declaradas como PROPERTY, y no para DATA o COMPONENT (que si no he entendido mal son lo mismo pero con distinto nombre por legibilidad).
Lo que no sabía era que al hacer una asignación normal, en la que en apariencia se está alternado el valor de la propiedad directamente:
::Value := "hola"
internamente se ejecuta su correspondiente ::Set(). Eso ya es la bomba, porque si el encapsulamiento tiene alguna pega, sin duda es que en cierto modo ensucia un poco el código del usuario-programador, además de que obliga a declarar cada ::Set() y ::Get(), como dice Juanjo.
Vamos, que me parece una solución estupenda lo que hace Xailer... cada vez me gusta más. Sólo aclárame, por favor, si en los DATA y COMPONENT también, internamente, se ejecuta un método ::Set() y un método ::Get() al consultar o alterar su valor. Y si, por lo tanto, cualquiera de estas 3 sentencias es correcta:
PROPERTY nValue READ ::GetValue() WRITE ::SetValue( Value )
DATA nValue READ ::GetValue() WRITE ::SetValue( Value )
COMPONENT oCtrl ::GetCtrl() WRITE ::SetCtrl( Value )
Gracias a todos por vuestras aportaciones. Definitivamente creo que es una práctica de lo más aconsejable.
Jaime,
las cláusulas READ y WRITE sólo son para las PROPERTY, no para DATA ni COMPONENT.
En el caso de COMPONENT, efectívamente es un sinónimo de DATA, pero está así por legibilidad y para que el IDE encuentre la declaración de los componentes sin ambigüedades.
Y el caso de las DATA, pues me temo que están exactamente igual que en [x]Harbour, y por lo tanto no tienen esa funcionalidad.
Ahora bien, si quieres ser coherente con la encapsulación, lo que tienes que hacer al declarar una clase es usar siempre PROPERTY para las propiedades públicas (accesibles desde fuera de la clase), y DATA privadas para uso interno de la clase. Así si cumples con el objetivo de la encapsulación, y así es como está escrito Xailer.
Un saludo
Estupendo, José. Gracias por la explicación.
En efecto como todos te dicen, esa recomendación o convenio es una buena práctica... no solo es buena sino que cuando te metes en proyectos de envergadura, cuando creas librerias de objetos o tus clases van a ser usadas por otros miembros de un equipo, es una autentica NECESIDAD.
LAs razones ya las has expuesto tu, pero los inconvenientes te los resuelven los entornos de programación.
Por ejemplo para Java, el intelliJ, que es una autentica maravilla, lo hace por ti si se lo pides.
Con este entorno sabes perfectamente si un programa va a tener errores de compilación antes de compilar y donde están exactamente.
En otros entornos de desarrollo, como los JavaBeans o al usar la introspeccion (capacidad de los objetos de hacer ver automaticamente a quien los use de que metodos se dispone) los getters y setters son imprescindibles.
Lo que va de encapsulación, se deriva de la propia descripción de lo que es un objeto en lo que al paradigma de POO se refiere. Por norma de seguridad está más que bien adecuarse a los lineamientos de lo que este método de programación pretende ser como tal. Por otra parte y a favor de la encapsulación está lo que se refire al ámbito de las variables presentes en los métodos y funciones; lo que al declararlas como privadas nos beneficia en cuanto a evitar que sean manipuladas desde cualquier código externo. Además este tipo de variables es creado automáticamente cuando se ejecuta el método o función y destruido cuando este termina; lo que por consiguiente reduce el consumo global de memoria ya que la mayor parte de las variables existen tan sólo mientras se está ejecutando la porción de código en la que se declaran y la memoria que ocupan se libera al salir.
Duvidas quanto a sintaxe (Perdoe o Idioma)
Veja exemplo abaixo e um comparativo xharbour , vb e delphi:
As tres linguagens aceitam a inicialização abaixo sem problemas
CreateObject("InternetExplorer.Application")
O codigo abaixo funciona em xHarbour/VB/Delphi
Alguns metodos que funcionam sao :
IE:Visible := .T.
IE:Navigate( "www.google.com.br")
Mas esse ai abaixo da problema :
No VB eu faço :
IEPostBinaryRequest = IE.Document.Body.innerHTML
No Delphi eu faço
text = ie.Document.body.innerHTML
Mas no xHarbour ele nao aceita,
texto := IE:Document:body:innerHTML()
Usando o Word, o Excel , etc existem alguns metodos que no xHarbour nao
funciona, parece que
ele nao consegue "enchergar" o método quando existe mais de um metodo
tipo : Objeto:Metodo1:Metodo2:Metodo3 , etc
Alguem teria alguma ideia ?
Obrigado e Desculpe o Idioma, meu espanhol e horrivel
Abraço a todos