Design Patterns¶
crunch_uml uses a number of recognizable design patterns that determine the extensibility and maintainability of the system.
Registry Pattern¶
The central pattern of crunch_uml. All parsers, renderers and transformers are registered via decorators and are queryable via a uniform interface.
classDiagram
class Registry {
<<abstract>>
+register(name, descr) decorator
+entries() list
+getinstance(name) object
+getDescription(name) str
}
class ParserRegistry {
7 registered parsers
}
class RendererRegistry {
11 registered renderers
}
class TransformerRegistry {
2+ registered transformers
}
Registry <|-- ParserRegistry
Registry <|-- RendererRegistry
Registry <|-- TransformerRegistry
Advantage: new implementations can be added without modifying existing code — only a @register decorator is needed.
Singleton Pattern¶
The Database class uses a singleton with _instance class variable. This guarantees one active database connection per process.
class Database:
_instance = None
def __new__(cls, db_url, db_create=False):
if cls._instance is None:
cls._instance = super().__new__(cls)
# Initialize engine + session
return cls._instance
Note
The singleton pattern is problematic with multi-threaded or concurrent usage. See Vulnerabilities.
Mixin Pattern¶
Reusable field definitions are composed via mixins. This prevents duplication across the ORM models.
classDiagram
class UML_Generic {
+id: str
+schema_id: str
+name: str
+definitie: str
+bron: str
+toelichting: str
+created: datetime
+modified: datetime
+stereotype: str
+to_dict()
}
class UMLBase {
+author: str
+version: str
+phase: str
+status: str
+uri: str
+visibility: str
+alias: str
}
class UMLTagsClazz {
MIM-specific tags
GEMMA tags
History indicators
}
class UMLTagsAttr {
Attribute-specific tags
}
class Package {
+parent_package_id
+subpackages
+classes
+enumerations
}
class Class {
+package_id
+is_datatype
+attributes
+associations
}
UML_Generic <|-- UMLBase
UMLBase <|-- Package
UMLBase <|-- Class
UMLBase <|-- UMLTagsClazz
UMLBase <|-- UMLTagsAttr
Two-Phase Parsing¶
XMI parsers process source files in two phases to resolve forward references:
graph LR
subgraph "Phase 1: Structure"
A["Load XML"] --> B["Parse packages"]
B --> C["Classes & Attributes"]
C --> D["Enumerations"]
end
subgraph "Phase 2: Relationships"
E["Associations"] --> F["Generalizations"]
F --> G["Diagram links"]
end
D --> E
style A fill:#fff2cc,stroke:#d6b656
style E fill:#FFF4E0,stroke:#d79b00
Phase 1 creates all entities, phase 2 establishes relationships between them. This is necessary because XMI files can define relationships before the related entities appear in the document.
Template Method Pattern¶
Parser, Renderer and Transformer base classes define the interface; subclasses implement the specific logic.
class Renderer(ABC):
@abstractmethod
def render(self, args, schema):
"""Subclasses implement this."""
pass
class ModelRenderer(Renderer):
def getModels(self, args, schema):
"""Shared logic for model retrieval."""
...
Decorator Pattern¶
The @register decorator combines class registration with the Registry:
@ParserRegistry.register("xmi", descr="Standard XMI 2.1 parser")
class XMIParser(Parser):
def parse(self, args, schema):
...
This makes adding a new parser a matter of:
- Creating a new class that extends
Parser - Adding the
@ParserRegistry.register("name")decorator - Implementing the
parse()method