2009-03-18 9 views
17

Tengo un código que analiza algunos archivos de plantilla y cuando encuentra un marcador de posición, lo reemplaza con un valor. Algo así como:Reemplace la declaración de cambio gigante con qué?

<html> 
<head> 
    <title>%title%</title> 
</head> 
<body bgcolor="%color%"> 
...etc. 

En el código, el analizador encuentra aquellos, llama a esta función:

string getContent(const string& name) 
{ 
    if (name == "title") 
     return page->getTitle(); 
    else if (name == "color") 
     return getBodyColor(); 
    ...etc. 
} 

y luego reemplaza el marcador de posición original con valor devuelto.

En el caso real, no es una página web ficticia, y hay muchos (50+) marcadores de posición diferentes que pueden ocurrir.

Mi código es C++, pero supongo que este problema existe en cualquier idioma. Se trata más de algoritmos y diseño OO, supongo. Lo único importante es que debe compilarse, incluso si quisiera no podría tener ningún código dinámico/evaluado.

Pensé en implementar el patrón de Cadena de responsabilidad, pero no parece que mejore la situación mucho.

ACTUALIZACIÓN: y también me preocupa this comment en otro hilo. ¿Debería importarme?

Respuesta

25

Utilice un diccionario que asigne nombres de etiquetas a un controlador de etiquetas.

+0

+1 Para aclarar, en C++ se llaman std :: map. – Eclipse

+1

Especialmente bueno si su diccionario puede usar una búsqueda O (1) como hash. –

+0

Gran respuesta. Un poco en el lado pobre, pero definitivamente es un buen camino por recorrer. :) –

4

Quiere replace conditional with polymorphism. Aproximadamente:

string getContent(const string& name) { 
    myType obj = factory.getObjForName(name); 
    obj.doStuff(); 
} 

donde doStuff está sobrecargado.

+2

Por supuesto, el interruptor simplemente se está moviendo a otra parte (la fábrica), que es donde debería estar. –

+0

Probablemente quiera combinar la fábrica con el mapa de Neil Butterworth y cargar la lógica de creación de instancias desde algún archivo de configuración. Compilado * y * dinámico - increíble. –

+0

En realidad, esa plantilla ES el archivo de configuración. Los usuarios pueden modificarlo ellos mismos. –

3

¿Has considerado XSLT? Es muy adecuado para este tipo de cosas. Desarrollé un sistema de administración de contenido que hizo exactamente lo mismo y descubrí que XSLT es muy efectivo. El analizador hace mucho del trabajo por ti.

ACTUALIZACIÓN: El comentario de Steven plantea un punto importante: querrás que tus plantillas sean XHTML válidas si decides seguir la ruta XSLT. Además, utilizaría un delimitador diferente para sus tokens de reemplazo. Algo menos probable que ocurra de forma natural. ¡Utilicé #! PLACEHOLDER #! en mi CMS.

+0

Creo que es bastante optimista de su parte pensar que las plantillas HTML serán XML válidos. :) –

2

En lugar de analizar, intenté simplemente leer la plantilla en una cadena y luego simplemente realizar reemplazos.

fileContents = fileContents.Replace("%title%", page->getTitle()); 
fileContents = fileContents.Replace("%color%", getBodyColor()); 
+0

golpe de rendimiento, pero probablemente valga la pena por la simplicidad del código si la eficiencia absoluta no es absolutamente necesaria. +1 –

+1

Si la variable "titleValue" contiene la cadena "% color%", no funcionaría correctamente. –

+0

ya es menos seguro también, pero aún más simple :) –

3

voy a combinar 3 ideas:

  1. (de Steven Hugig): uso un método de fábrica que le consigue una clase diferente para cada selector.
    • (de Neil Butterworth): dentro de la fábrica, use un diccionario para deshacerse de la gran switch(){}.
    • (mía): agregue un método setup() a cada clase de controlador, que se agrega (o una nueva instancia de clase) al diccionario.

explicar un poco:

  • crea una clase abstracta que tiene un diccionario static, y los métodos para registrar una instancia con una cadena de selección.
  • en cada subclase el método setup() registra con dict la superclase
  • el método de fábrica es poco más que un diccionario leer
+0

Tipo de investigación ... ¿no es usted – Warrior

+0

+1. Sugeriré deshacerse de la función de instalación() separada y mover su comportamiento al constructor, de esa forma no se puede olvidar. –

2

Como "Uncle" Bob Martin mentioned in a previous podacast with Joel and Jeff, prácticamente todo lo que ocurre es va a ser esencialmente reproduciendo la declaración de interruptor grande.

Si se siente mejor implementando una de las soluciones seleccionadas anteriormente, está bien. Puede hacer que su código sea más bonito, pero bajo las sábanas, es esencialmente equivalente.

Lo importante es asegurarse de que solo haya una instancia de su declaración de cambio grande. Su instrucción o diccionario de cambio debe determinar qué clase maneja esta etiqueta, y las determinaciones posteriores deben manejarse usando polimorfismo.

+0

Esto no es cierto (y es típico de la basura con la que Martin sale). Para agregar a un interruptor, necesito modificar el código del interruptor; puedo agregarlo al diccionario sin modificar el código existente. –

+0

Agregar al diccionario sigue siendo un cambio de código, y uno que el compilador tiene menos posibilidades de detectar problemas ... no es como si el diccionario se está rellenando a partir de datos, sino que se completará a través de un código. Agregar casos a una declaración de cambio no necesita afectar ningún caso existente ...? – Bittercoder

+0

En teoría, sí, el dictrionario podría completarse desde un archivo de configuración o una tabla de base de datos, por lo que podría modificarse sin una nueva compilación. En la práctica, si está cambiando las asignaciones, es probable que sea porque tiene un nuevo controlador, por lo que ya está haciendo una nueva compilación. – JohnMcG

Cuestiones relacionadas