Skip to content

Implementación

Una especificación de código se implementará mediante un recorrido del árbol en el que, cuando se llegue a un nodo, se generen las instrucciones de su plantilla. Es decir, hay que hacer un nuevo visitor en el que la implementación de cada método visit se corresponda con el contenido de la plantilla de dicho nodo.

El problema es que puede haber nodos con más de una función de código y, por ello, más de una plantilla. Por ejemplo, sobre las expression se han definido dos funciones de código: value y address. Es por ello que, por ejemplo, el símbolo variable tiene dos plantillas.

value⟦expression ...

value⟦variable → name:string⟧ =
     address⟦variable⟧
     LOAD<variable.type>

address⟦expression⟧ ...

address⟦variable → name:string⟧ =
     PUSHA {variable.varDefinition.address}

Sin embargo, una clase Visitor no puede tener dos métodos con el mismo nombre visit(Variable).

Teniendo en cuenta lo anterior, hay dos formas principales de implementar una especificación de código:

  • Solución monovisitor. Usar un solo visitor, en el que cada método visit(<nodo>) implementa todas las plantillas de dicho nodo.
  • Solución multivisitor. Usar un visitor por cada función de código, de tal manera que en cada visitor sólo están las plantillas de dicha función.

La forma de implementación en un sólo visitor es más sencilla y puede ser suficiente si en la especificación de código no hay mucho solapamiento de funciones. Sin embargo, tiene los siguientes inconvenientes:

  • Es más confuso a la hora de hacer trazabilidad con la especificación de código, ya que un método tiene varias plantillas.
  • Se utiliza el segundo parámetro del visitor para indicar la función a aplicar. Por tanto, si se necesita pasar información entre los visit, se tendrán que buscar una forma de pasar ambos argumentos juntos dentro del segundo parámetro.
  • Todo el código de la especificación está en una sola clase. Esto, si bien no tiene por qué ser un problema para un lenguaje sencillo, puede serlo para lenguajes más complejos.

La implementación con multivisitor no tiene los inconvenientes anteriores:

  • La trazabilidad con la especificación es más sencilla, ya que todas las plantillas están organizadas por función y cada visit sólo tiene una plantilla.
  • El segundo parámetro del visitor queda libre.

Sin embargo, también tiene inconvenientes:

  • El hecho de que el código de las plantillas esté repartido en varias clases, cosa que ayuda a organizar especificaciones grandes, en las especificaciones pequeñas hace que su escaso código esté demasiado disperso.
  • La infraestructura de coordinación de los visitors es más compleja. Desde cualquier visitor se debe tener acceso a los demás para poder ser invocados. No es fácil conseguir esta comunicación entre los visitors.
    • Se puede hacer un diseño que requiera invocar métodos setVisitorX(...) después de crear cada visitor para pasarle los visitors con los que se comunique. Sin embargo, aunque en este caso podría ser una solución válida debido a sus sencillez, en general no es recomendable hacer un diseño que requiera que, entre que se crea el objeto y se pueda usar, haya que invocar unos determinados métodos. Se puede olvidar hacerlo y, además, hasta que se hayan invocado dichos métodos el objetos se encuentra en un estado inconsistente.
    • La solución adecuada, que sería usar el constructor para pasar toda la información que requiera un objeto para crearse en un estado inicial válido, no es posible ya que hay referencias circulares entre los visitors.

En definitiva, la opción monovisitor es más sencilla y puede ser suficiente para especificaciones pequeñas. La opción multivisitor es más compleja, pero más flexible y escalable. En los siguientes apartados se muestran ambas soluciones.