2011-11-29 8 views
9

Tengo esta situación en la que tengo una relación padre-hijo entre dos conjuntos de datos. Tengo una colección de documentos principal y una colección de documentos secundarios. El requisito es que los padres y sus hijos correspondientes se exporten a 'un' documento pdf. Una simple aplicación de la situación anterior puede ser de la siguiente manera (por debajo de pseudo código java-ish):Refactorización anidada para bucles

for(Document parentDocument:Documents){ 
    ExportToPdf(parentDocument); 
    for(Document childDocument:parentDocument.children()){ 
     AppendToParentPdf(childDocument); 
    } 
} 

Algo que el anterior probablemente resolver el problema, pero, de repente, los cambios en los requerimientos y ahora cada uno de estos los padres y sus hijos correspondientes deben estar en archivos PDF separados, por lo que el fragmento dado anteriormente se modifica cambiando el AppendToParentPdf()-ExportToPdf() sigue:

for(Document parentDocument:Documents){ 
    ExportToPdf(parentDocument); 
    for(Document childDocument:parentDocument.children()){ 
     ExportToPdf(childDocument); 
    } 
} 

Yendo a lo largo de esta manera, no pasará mucho tiempo antes de que este fragmento aparentemente trivial haría sufren de algunos olores graves de código.

Mis preguntas a SO son:

  1. ¿Hay mejores representaciones de relaciones padre-hijo como el anterior donde en vez de fuerza bruta mi camino a través de todos los documentos y sus hijos de una manera O(n^2), Puedo usar una estructura de datos o técnica diferente para recorrer toda la estructura de una manera más óptima.

  2. En el escenario que describí anteriormente, donde las reglas de negocio son bastante fluidas sobre la forma en que deberían exportarse los pdfs, ¿existe alguna forma más inteligente de codificar la naturaleza de la función de exportación? Además, el formato de exportación es transitorio. Los archivos PDF pueden dar paso a * .docs/csvs/xmls et al.

Será genial tener alguna perspectiva sobre esto.

Gracias

+0

En cuanto a su primera pregunta, no se ve como si estuviera ataques de fuerza bruta, porque sólo se está recuperando parentDocument.Children para todos los padres y esto contiene la lista de todos los documentos de los niños para un padre en particular. Entonces su complejidad de tiempo no es o (n^2) sino más bien o (n + m) donde n y m son el recuento de los documentos padre e hijo, respectivamente. – Santhosh

+0

@sc_ray Debe indicar si es posible que 'childDocument' tenga más de un' parentDocument'. – Alderath

+0

@Santosh - No estoy seguro de cómo esto es un problema O (n + m) ya que el ciclo necesita iterar a través de cada uno de los m hijos n veces, dándole una complejidad temporal de O (n * m) u O (n^2). –

Respuesta

3

Puede encapsular lo que quiere hacer con un documento en un controlador. Esto también le permitirá definir nuevos manejadores en el futuro que puede pasar al código existente.

interface DocumentHandler { 
    void process(Document d); 
} 

class ExportToPdf implements DocumentHandler { ... } 
class AppendToParentPdf implements DocumentHandler { ... } 

// Now you're just passing the interface whose implementation does something with the document 
void handleDocument(DocumentHandler parentHandler, DocumentHandler childHandler) { 
    for(Document parent : documents) { 
     parentHandler.process(parent); 

     for(Document child : parent.children()) { 
      childHandler.process(child); 
     } 
    } 
} 

DocumentHandler appendToParent = new AppendToParentPdf(); 
DocumentHandler exportToPdf = new ExportToPdf(); 

// pass the child/parent handlers as needed 
handleDocument(exportToPdf, appendToParent); 
handleDocument(exportToPdf, exportToPdf); 

En cuanto a la eficiencia, yo diría que no intente optimizar a menos que se encuentre con problemas de rendimiento. En cualquier caso, el problema no será con el ciclo anidado sino con la propia lógica que procesa los documentos.

+0

Gracias. Es un fragmento bastante elegante. –

+0

Gracias! Me alegro de haber ayudado :) – armandino

1

yo tratamos de encajar esto en un comentario, pero no hay mucho que decir ...

no veo cómo el cambio que está hablando es de un olor código. Si los requisitos cambian para esta función simple, entonces cambian. Si solo necesita hacer el cambio en un lugar, entonces parece que ha hecho un buen trabajo. Si su cliente va a necesitar ambas formas de hacerlo (o más), entonces podría considerar algún tipo de patrón de estrategia para que no tenga que volver a escribir el código circundante para realizar ninguna función.

Si realiza decenas de estos cambios por semana, puede ser complicado y probablemente deba hacer un plan para lidiar con un eje de cambio muy ocupado. De lo contrario, la disciplina y la refactorización pueden ayudarlo a mantenerlo limpio.

En cuanto a si n² es un problema, depende. ¿Qué tan grande es n? Si tiene que hacer esto con frecuencia (es decir, docenas de veces por hora) yn está en los 1000, entonces es posible que tenga un problema. De lo contrario, no me preocuparía si cumplía o excedía la demanda y el uso de su CPU/disco está fuera de la zona de peligro. problema

+0

Gracias por la respuesta. El problema es que los más de 1 millón de documentos (padres + hijos) pueden ser exportados por los más de 300 usuarios en cualquier momento dado y, aunque nadie espera que ocurra ningún tipo de magia de exportador secundario, el sistema tiene el potencial de ser gravados por este tipo de carga proyectada. –

+0

Incluso si hay un millón de documentos, ¿cuántos va a solicitar un usuario en una solicitud determinada? ¿Cuántos usuarios estarán en el sistema a la vez, y de esos, cuántos generarán documentos a la vez? A menos que tenga algunos requisitos inusuales, espero que esos números funcionen como algo razonable. Sin embargo, si le preocupa el trabajo masivo que debe realizar, entonces es posible que desee considerar las colas de trabajo para reducir la cantidad de trabajo simultáneo que se producirá. Sin embargo, no me preocuparía hasta que algunas pruebas de carga me demostraron que las necesitaba. – Bill

1

las segundas cuestiones puede ser resuelto simplemente creando un inteface Exporter con el método export(Document doc); y luego su aplicación para cada uno de los diversos formatos, por ejemplo class DocExporterImpl implements Exporter.

El primero depende de sus requisitos y ningún patrón de diseño resuelve estos problemas. No puedo ayudarte allí.

2

Para su 2da pregunta, podría usar el provider pattern o una extensión de este.

patrón Proveedor: Este patrón tiene sus raíces en el patrón de Estrategia y que le permite diseñar sus datos y el comportamiento en una abstracción para que pueda intercambiar aplicación en cualquier momento

4

¿Hay mejores representaciones de las relaciones entre padres e hijos, como las anteriores, donde en lugar de forzar mi camino a través de todos los documentos y sus hijos de una manera O (n^2).

Esto no es O(N^2). Es O(N) donde N es la cantidad total de documentos padre e hijo. Suponiendo que ningún niño tenga más de un documento primario, entonces no puede mejorar significativamente el rendimiento. Además, el costo de la travesía es probablemente trivial en comparación con el costo de las llamadas que generan los archivos PDF.

El único caso donde es posible que desee considerar la optimización es si los documentos secundarios pueden ser hijos de múltiples padres. En ese caso, es posible que desee realizar un seguimiento de los documentos para los que ya ha generado PDF ... y omitirlos si los vuelve a visitar en el cruce. La prueba para "he visto este documento antes" se puede implementar utilizando un HashSet.

+0

Gracias por la respuesta. No entiendo cómo dos bucles for anidados hacen que la complejidad de tiempo O (n + m) u O (n) en lugar de n^2. La ejecución del bucle externo se pausará hasta que el bucle interno haya terminado de ejecutarse, a menos que java desarrolle un bucle bastante radical convirtiendo un n^2 en n. –

1

El uso de un conjunto para realizar un seguimiento de los elementos que ya se han exportado podría no ser la solución más hermosa, pero evitará que los documentos se exporten dos veces.

Set<Document> alreadyExported = new HashSet<Document>(); 

for(Document parentDocument:Documents){ 
    ExportToPdf(parentDocument); 
    for(Document childDocument:parentDocument.children()){ 
     if(!aldreadyExported.contains(childDocument)){ 
     ExportToPdf(childDocument); 
     alreadyExported.add(childDocument); 
     } 
    } 
} 
Cuestiones relacionadas