Programación orientada a objetos: captadores / establecedores o nombres lógicos

12

Actualmente estoy pensando en una interfaz para una clase que estoy escribiendo. Esta clase contiene estilos para un carácter, por ejemplo, si el carácter está en negrita, cursiva, subrayado, etc. He estado debatiendo conmigo mismo durante dos días si debo usar getters / setters o nombres lógicos para los métodos que cambian los valores a estos estilos Si bien tiendo a preferir nombres lógicos, significa escribir código que no es tan eficiente ni lógico. Dejame darte un ejemplo.

Tengo una clase CharacterStylesque tiene variables miembro bold, italic, underline(y algunos otros, pero voy a dejar fuera de mantenerlo simple). La forma más fácil de permitir que otras partes del programa accedan a estas variables, sería escribir métodos getter / setter, para que pueda hacer styles.setBold(true)y styles.setItalic(false).

Pero no me gusta esto. No solo porque mucha gente dice que los captadores / establecedores rompen la encapsulación (¿es realmente tan malo?), Sino principalmente porque no me parece lógico. Espero diseñar un personaje a través de un método, styles.format("bold", true)o algo así, pero no a través de todos estos métodos.

Sin embargo, hay un problema. Como no puede acceder a una variable miembro del objeto por el contenido de una cadena en C ++, tendría que escribir un gran contenedor de declaración / cambio para todos los estilos, o tendría que almacenar los estilos en una matriz asociativa ( mapa).

No puedo entender cuál es la mejor manera. En un momento creo que debería escribir los captadores / establecedores, y al momento siguiente me inclino hacia el otro lado. Mi pregunta es: ¿qué harías? ¿Y por qué harías eso?

Rana
fuente
¿La clase CharacterStyles realmente hace algo más que agrupar tres booleanos? ¿Cómo se consume?
Mark Canlas
Sí, porque CharacterStyles debería poder heredar de otros CharacterStyles. Esto significa que si tengo un CharacterStyles con boldset to truey las otras variables indefinidas, los métodos getter para las otras variables deberían devolver el valor del estilo padre (que está almacenado en otra propiedad), mientras que el getter para la boldpropiedad debería regresar true. Hay algunas otras cosas como un nombre y la forma en que se muestra en la interfaz también.
Rana
Puede usar una enumeración para definir las diversas opciones de estilo y luego usar una instrucción de cambio. Por cierto, ¿cómo se maneja indefinido?
Tyanna
@Tyanna: De hecho, también pensé en usar una enumeración, pero eso es solo un detalle menor. Estaba planeando establecer valores indefinidos en NULL. ¿No es esa la mejor manera?
Rana

Respuestas:

6

Sí, los getters / setters rompen la encapsulación, básicamente son solo una capa adicional entre acceder directamente al campo subyacente. También podría acceder directamente a él.

Ahora, si desea métodos más complejos para acceder al campo, eso es válido, pero en lugar de exponer el campo, debe pensar qué métodos debería ofrecer la clase en su lugar. es decir. en lugar de una clase de banco con un valor monetario que se expone utilizando una propiedad, debe pensar qué tipos de acceso debe ofrecer un objeto del banco (agregar dinero, retirar, obtener saldo) e implementarlos en su lugar. Una propiedad en la variable Money solo es sintácticamente diferente de exponer la variable Money directamente.

DrDobbs tiene un artículo que dice más.

Para su problema, tendría 2 métodos setStyle y clearStyle (o lo que sea) que toman una enumeración de los posibles estilos. Una declaración de cambio dentro de estos métodos aplicaría los valores relevantes a las variables de clase apropiadas. De esta manera, puede cambiar la representación interna de los estilos a otra cosa si luego decide almacenarlos como una cadena (para usar en HTML, por ejemplo), algo que requeriría que todos los usuarios de su clase también cambien si usa obtener / establecer propiedades.

Aún puede activar cadenas si desea tomar valores arbitrarios, ya sea tener una gran instrucción if-then (si hay algunas) o un mapa de valores de cadena para punteros de método usando std :: mem_fun (o std :: función ) por lo que "bold" se almacenaría en una clave de mapa con su valor como sts :: mem_fun en un método que establece la variable bold en true (si las cadenas son las mismas que los nombres de las variables miembro, también puede usar la macro de cadena para reducir la cantidad de código que necesita escribir)

gbjbaanb
fuente
Excelente respuesta! Especialmente la parte sobre el almacenamiento de datos como HTML me pareció una buena razón para seguir este camino. ¿Entonces sugeriría almacenar los diferentes estilos en una enumeración y luego configurar estilos como styles.setStyle(BOLD, true)? ¿Es eso correcto?
Rana
sí, solo tendría 2 métodos: setStyle (BOLD) y clearstyle (BOLD). olvide el segundo parámetro, simplemente lo prefiero así y luego podría sobrecargar clearStyle para no tomar parámetros para borrar todas las banderas de estilo.
gbjbaanb
Eso no funcionaría, porque algunos tipos (como el tamaño de fuente) necesitan un parámetro. Pero aún así, gracias por la respuesta!
Rana
He estado tratando de implementar esto, pero hay un problema que no consideré: ¿cómo implementarlo styles.getStyle(BOLD)cuando no solo tiene variables miembro de tipo booleano, sino también de tipos enteros y cadenas (por ejemplo:) styles.getStyle(FONTSIZE). Como no puede sobrecargar los tipos de retorno, ¿cómo programa esto? Sé que podrías usar punteros vacíos, pero eso me parece una muy mala manera. ¿Tienes alguna pista sobre cómo hacer esto?
Rana
puede devolver una unión, o una estructura, o con el nuevo estándar, puede sobrecargar por tipo de retorno. Dicho esto, ¿está tratando de implementar un método único para establecer un estilo, donde el estilo es una bandera, una familia de fuentes y un tamaño? Tal vez estás tratando de forzar demasiado la abstracción en este caso.
gbjbaanb
11

Una idea que quizás no hayas considerado es el Patrón Decorador . En lugar de establecer banderas en un objeto y luego aplicar esas banderas a lo que sea que esté escribiendo, ajusta la clase que escribe en Decoradores, que a su vez aplica los estilos.

El código de llamada no necesita saber cuántos de estos envoltorios ha colocado alrededor de su texto, simplemente llama a un método en el objeto externo y llama a la pila.

Para un ejemplo de pseudocódigo:

class TextWriter : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // write some text somewhere somehow
        }
}

class BoldDecorator : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // bold application on
            m_textWriter.WriteString(x);
            // bold application off
        }

        ctor (TextDrawingInterface textWriter) {
            m_textWriter = textWriter;
        }

    private:
        TextWriter m_TextWriter;
}

Y así sucesivamente, para cada estilo de decoración. En su uso más simple, puede decir

TextDrawingInterface GetDecoratedTextWriter() {
    return new BoldDecorator(new ItalicDecorator(new TextWriter()));
}

Y el código que llama a este método no necesita conocer los detalles de lo que está recibiendo. Siempre que sea ALGO que pueda dibujar texto a través de un método WriteString.

pdr
fuente
Hmmm, voy a echarle un vistazo a eso mañana con una mente fresca y luego me pondré en contacto contigo. Gracias por la respuesta.
Rana
Soy un gran admirador del patrón Decorador. El hecho de que este patrón se parezca a HTML (donde la operación principal es encerrar la cadena de entrada con etiquetas) es una prueba de que este enfoque puede satisfacer todas las necesidades de su interfaz de usuario. Una cosa a tener en cuenta es que una interfaz que es buena y fácil de usar, puede tener una implementación subyacente que es técnicamente complicada. Por ejemplo, si está implementando el renderizador de texto usted mismo, es posible que TextWriternecesite hablar con ambos BoldDecoratory ItalicDecorator(bidireccionalmente) para hacer el trabajo.
rwong
Si bien esta es una buena respuesta, ¿no vuelve a introducir la pregunta del operador? "aplicación en negrita" -> ¿cómo se va a hacer esto? usando setters / getters como SetBold ()? Cuál es exactamente la pregunta del operador.
stijn
1
@stijn: En realidad no. Hay varias formas de evitar esa situación. Por ejemplo, puede ajustar eso en un generador con un método toggleStyle (argumento enum) que agrega y elimina decoradores de una matriz, solo decorando el elemento final cuando llama a BuildDecoratedTextWriter. O puede hacer lo que rwong sugirió y complicar la clase base. Depende de las circunstancias y el OP no fue lo suficientemente específico sobre la especificación general para adivinar cuál es el mejor para él. Sin embargo, parece lo suficientemente inteligente como para poder resolverlo una vez que obtiene el patrón.
pdr
1
Siempre he pensado que el patrón de decorador implica ordenar las decoraciones. Considere el código de ejemplo que se muestra; ¿Cómo se puede eliminar el ItalicDecorator? Aún así, creo que algo como Decorator es el camino a seguir. Negrita, cursiva y subrayado no son los únicos tres estilos. Hay delgadas, semibold, condensadas, negras, anchas, cursivas con swash, mayúsculas, etc. Puede que necesite una forma de aplicar un conjunto arbitrariamente grande de estilos a un carácter.
Barry Brown
0

Me inclinaría hacia la segunda solución. Se ve más elegante y flexible. Aunque es difícil imaginar otros tipos que no sean negrita, cursiva y subrayado (¿sobrelínea?), Sería difícil agregar nuevos tipos utilizando variables miembro.

Utilicé este enfoque en uno de mis últimos proyectos. Tengo una clase que puede tener múltiples atributos booleanos. El número y los nombres de los atributos pueden cambiar con el tiempo. Los guardo en un diccionario. Si un atributo no está presente, supongo que su valor es "falso". También tengo que almacenar una lista de nombres de atributos disponibles, pero esa es otra historia.

Andrzej Bobak
fuente
1
Además de estos tipos, agregaría tachado, tamaño de fuente, familia de fuentes, superíndice, subíndice y quizás otros más adelante. Pero, ¿por qué elegiste ese método en tu proyecto? ¿No crees que es un código sucio tener que almacenar una lista de atributos permitidos? ¡Tengo mucha curiosidad por saber cuáles son sus respuestas a estas preguntas!
Rana
Tuve que lidiar con situaciones en las que los clientes podían definir algunos atributos cuando la aplicación ya estaba implementada. Algunos otros atributos eran necesarios para algunos clientes pero obsoletos para los demás. Podría personalizar los formularios, los conjuntos de datos almacenados y abordar otros problemas utilizando este enfoque. No creo que genere código sucio. A veces no puede predecir qué tipo de información necesitará su cliente.
Andrzej Bobak
Pero mi cliente no necesitará agregar atributos personalizados. Y en ese caso creo que se ve un poco "hacky". No estas de acuerdo
Rana
Si no enfrenta un problema de número dinámico de atributos, no necesita espacio flexible para almacenarlos. Eso es cierto :) Sin embargo, no estoy de acuerdo con la otra parte. En mi opinión, anotar atributos en un diccionario / hashmap / etc.no es un mal enfoque.
Andrzej Bobak
0

Creo que estás pensando demasiado en el tema.

styles.setBold(true) y styles.setItalic(false)están bien

Tulains Córdova
fuente