July 16, 2026
The increasing reliance on automatic code generation in mobile application development often leads to code that neglects fundamental design principles and architectural quality. In this work, we address this challenge by capturing and exploiting the inherent variability of software design patterns to systematically generate customizable and well-structured mobile applications. We propose the use of the Universal Variability Language (UVL) to explicitly model the structural and behavioral variation points of common design patterns, such as Singleton, Strategy, Observer, Adapter, and Factory Method. These models are integrated with reusable Jinja templates, enabling code generation in Swift. Our approach leverages Software Product Line (SPL) engineering principles, treating design patterns as configurable assets within a product line and supporting automated generation of custom design patterns. We also analyze the configuration space of the modeled patterns, offering insights into their variability complexity. By formalizing design pattern variability and embedding it into the generation process, our work bridges model-driven engineering with practical mobile development, promoting the production of maintainable, reusable, and architecturally sound applications.
<ccs2012> <concept> <concept_id>10011007.10011074.10011092.10011096.10011097</concept_id> <concept_desc>Software and its engineering Software product lines</concept_desc> <concept_significance>500</concept_significance> </concept> <concept> <concept_id>10011007.10010940.10010971.10011682</concept_id> <concept_desc>Software and its engineering Abstraction, modeling and modularity</concept_desc> <concept_significance>300</concept_significance> </concept>
</ccs2012>
Mobile applications today are developed under increasing pressure to deliver rapidly, support multiple platforms, and adapt to evolving user expectations. To meet these demands, many developers turn to automatic code generation tools, especially those powered by large language models (LLMs) [1]. These tools can quickly produce code in languages such as Swift, Kotlin, or JavaScript. However, the generated code often lacks architectural quality and omits fundamental design principles [2]. In particular, the systematic application of well-established software design patterns (i.e.,key reusable solutions to recurring design problems) has become less explicit in this new paradigm [3].
Design patterns, as introduced by Gamma et al. [4], have long served as foundational building blocks for software design, facilitating code reuse, flexibility, and maintainability. In the context of mobile development, design patterns are particularly valuable. They help organize complex user interfaces [5], manage application state [6], user interactions [7], and support reusable business logic across platforms [8]. Nevertheless, with increasing reliance on automatic code generation, developers may no longer recognize when or how to apply these patterns correctly [8].
Moreover, design patterns do not only provide reusable solutions at the code level; they also reflect architectural choices that vary depending on the needs of each application. This idea connects naturally with the principles of Software Product Line (SPL) engineering [9], [10], which focuses on developing families of related software products by managing their commonality and variability. In an SPL, software artifacts are designed to be reused across products, with variation points that allow for customization. Feature models [11], [12] are commonly used to capture these options and guide the generation of concrete products. In the mobile development domain, where applications often need to be customized for different devices, brands, or user preferences, the combination of SPLs and design patterns provides a structured way to generate tailored applications from a shared architecture.
In SPL engineering, existing work often considers design patterns as tools to implement variability in software systems. For example, the State or Strategy patterns are commonly used in SPLs to enable the selection of alternative behaviors [10], [13]. However, while patterns are often applied to handle variability, the variability internal to the patterns themselves has received comparatively little attention [14].
Before design patterns can be applied in a reusable and configurable way, we need to understand that they are not fixed templates. In practice, each design pattern has its own internal variability. For instance, the Observer pattern can be instantiated with one or many observers, and the notification can be synchronous or asynchronous. Similarly, the Singleton pattern has variants such as eager vs. lazy initialization, thread-safe vs. unsafe, or per-instance vs. global. This internal variability is often implicit in design pattern catalogs and tutorials [4], [15], [16]. As a result, developers applying these patterns manually (or through code generation) are left with an under-specified solution space. This makes automatic generation difficult to control, and pattern application prone to errors or misuse.
This article addresses two key problems: (1) how can we explicitly model the variability of commonly used design patterns?; and (2) how can we automatically generate Swift code from such models to produce correct and customizable pattern implementations for mobile applications? To solve these problems, we propose the use of the Universal Variability Language (UVL) [17] to specify the variability of design patterns using feature modeling. UVL has become the de-facto standard for variability modeling in the SPL community in recent years [18]. UVL supports advanced constructs [19] such as attribute-based constraints, typed features beyond Boolean such as numeric and string features, and clonable elements (akafeature cardinalities [20]), which are essential to accurately describe pattern variants. We then use a template-based code generation [21] approach to implement each pattern, supporting variability directly in the code generation process [22]. In a template-based approach, a text-based programming language (e.g.,Swift, Kotlin, JavaScript, Python) is enriched with directives to automatically generate custom content. Specifically, we leverage the Jinja1 template engine due to its language-agnostic nature, which allows its integration with various mobile programming languages, including Swift, Kotlin, and Java. Furthermore, Jinja supports all UVL extensions by using powerful directives to manage variability [22]. These directives facilitate the substitution and replacement of elements, the assignment of values, and the use of control structures such as if/elif/else, for-loops, macros, and blocks. Our main contributions are as follows:
The explicit modeling of design pattern variability using UVL feature models, capturing both structural and behavioral variation points within each pattern.
The definition of reusable Jinja templates for each pattern, enabling scalable and customizable code generation in Swift for mobile applications.
The integration with existing SPL tools, such as UVengine2 [22], to support automatic configuration and generation of pattern-based architectures tailored to user needs.
The systematic analysis of the configuration space of each design pattern, providing insight into the variability complexity and supporting informed decision-making during product derivation.
By formalizing the variability inherent in design patterns, we lay the groundwork for tools that can assist developers (or even Artificial Intelligence assistants) in generating structured, reusable, and well-architected mobile applications. Our approach bridges the gap between model-driven engineering and practical code generation, ensuring that software quality and design best practices are not lost in the automation process.
The rest of the paper is organized as follows. Section 2 introduces the main concepts about SPL and UVL. Section 3 presents our approach for modeling and mananing the variability of the design patterns, while Section 4 details the variability and implementation of each the design pattern. Section 5 evaluates our approach. Section 6 discusess related work and Section 7 concludes the paper.
This section provides the foundational knowledge necessary to understand our work, focusing on the core principles of SPL engineering and the UVL language.
SPL engineering is a paradigm for developing families of related software systems from a shared set of reusable assets in a planned and systematic manner [9], [10]. The central idea of SPLs is to balance the trade-off between the economies of scale achieved through mass production and the need for customization to meet specific market or customer demands. This is accomplished by explicitly managing two key concepts: commonality, which refers to the features and assets shared by all products in the line, and variability, which represents the points where products can differ. The SPL approach is typically organized into two main processes:
This phase focuses on the development and maintenance of reusable assets for the entire SPL. It includes activities such as domain analysis (identifying common and variable features), domain design (creating a common architecture), and domain implementation (developing reusable components).
This phase involves deriving specific products from the reusable assets created during domain engineering. It consists of selecting a valid combination of features from the variability model, which then guides the configuration and assembly of the reusable assets to generate a final product.
The SPL engineering process is driven by the variability model (or feature model), which specifies the commonality and variability of the entire SPL. Over the years, multiple languages and formats have been proposed to define feature models; however, in recent years, the UVL language has emerged as the de-facto standard within the SPL community [17]. Listing [uvl:ExampleFM] shows an example of a UVL model using its core Boolean level to represent the variability of a mobile application development process. Traditionally, feature models have been represented as feature diagrams, as illustrated in Figure 1, but UVL offers a concise and expressive syntax for defining feature models.
A UVL model consists of two main parts: the features section, which contains the feature tree, and the constraints section, which contains the
cross-tree constraints. Features are organized in a tree-like hierarchy, definings relationships using keywords such as: mandatory to indicate that a child feature must be selected (e.g., the
Platform feature); optional to indicate that a child feature may be selected (e.g., the Connectivity feature); or to indicate
that at least one child feature must be selected in a group (e.g., or Storage); and alternative to indicate that exactly one child feature must be selected within a group (e.g.,). Cross-tree
constraints are propositional formulas with logical operators: ! (not), & (and), | (or), => (implies), <=> (equivalence), allowing further restrict valid feature selections
(e.g.,Cloud => WiFi | 5G).
Furthermore, UVL also supports specifying feature attributes to include additional information about the features by attaching key–value properties to features, and group cardinalities [a..b] to specify the number of features that can be selected from a group. Other advanced variability constructs supported by UVL are typed features (Integer, Real, or String features) that require assigning a concrete value during configuration; and
feature cardinalities (akaclonable features), which allows creating multiple instances (clones) of a feature and configuring each clone and its subtree differently.
features
MobileApp
mandatory
Platform
or
Android
iOS
Development
alternative
Native
alternative
Swift
Kotlin
CrossPlatform
alternative
Flutter
React
Xamarin
Storage
or
Local
Cloud
optional
Connectivity
or
WiFi
"5G"
Bluetooth
NFC
constraints
Cloud => WiFi | "5G"
(Android & iOS) => CrossPlatform
(Android & !iOS) => Kotlin
(!Android & iOS) => Swift
Building upon the foundations of SPL engineering [9] and Generative Programming [13], our work proposes a novel approach to address the challenge of creating well-architected and customizable mobile applications. The core idea is to treat design patterns themselves as reusable assets within an SPL, explicitly modeling and exploiting their inherent variability to systematically generate mobile application code. This shifts the focus from using design patterns as a static solution for implementing variability to treating them as a configurable, generative artifact.
This approach aligns directly with the two core phases of the SPL engineering process introduced in Section 2, as illustrated in the conceptual flow depicted in Figure 2. The process begins in the Domain Engineering phase, where we formalize the variability of design patterns. This is the most crucial step and the main contribution of our work. We identify the structural and behavioral variation points of each pattern and encode them as UVL feature models. In parallel, we define reusable code generation templates for each pattern. In this context, each design pattern is treated as a reusable asset within the SPL, with its own internal variability explicitly captured in a UVL feature model. This makes it possible to configure and derive pattern implementations in a systematic and automated way.
Feature models: We define the configurable options of each design pattern using UVL. For example, for a specific pattern, we model its structural and behavioral alternatives, and the constraints between them, allowing for fine-grained control over its final implementation. These models serve as formal specifications of the pattern’s variability.
Implementation artifacts: For each design pattern, we create language-specific templates (e.g.,written in Swift for iOS) that contain placeholders and Jinja directives based on the features of the UVL model. These templates are the blueprints for the final code, ready to be populated with specific feature selections.
In the Application Engineering phase, a specific mobile application product is derived by selecting a valid configuration of features for each required design pattern. A developer or product manager selects the desired features from the feature model generating a configuration of the feature model (normally stored in a JSON file). The final step is the generation of the source code by instantiating the Jinja templates with the provided configuration. As the core engine for this phase, we use a generic variability resolution engine: UVengine [22], a tool for resolving the variability of UVL models over Jinja templates. The output is a complete, custom-tailored desing pattern ready to be incorporated in the mobile application.
In the following section, we provide the details of how to formalize the variability of each design pattern in UVL and how to implement such variability using Jinja templates for Swift code.
This section focuses on the formalization and systematic implementation of the internal variability of design patterns as configurable assets within the SPL engineering process. First, we describe how to explicitly model the variation points and variants of each pattern using UVL [17]. Subsequently, we explain how this specification is translated into reusable implementation artifacts through parameterized code templates (using Jinja), which are capable of automatically generating concrete versions of the pattern in response to different application configurations.
We present the catalog of design patterns selected for variability modeling and automated code generation. We assign identifiers to each pattern (e.g.,DP1, DP2), in order to facilitate references throughout the following sections.
The patterns chosen represent three classical groups (creational, behavioral, structural) and are among those most frequently used in mobile development. Namely, Singleton (DP1), Strategy (DP2), Observer (DP3), Adapter (DP4), and Factory Method (DP5). Each pattern exhibits distinct forms of internal variability, both in structural and behavioral aspects. This diversity allows us to generalize the feasibility and scalability of our approach. For each pattern, we systematically:
Identify the main variation points (e.g.,instantiation strategy, interface alternatives, cardinality, etc.).
Capture alternatives, constraints, and dependencies in a UVL feature model.
Provide a reusable, parameterized Jinja code template that materializes the selected features.
Analyze the configuration space to determine the expressiveness and boundaries of their variability.
This approach ensures not only a comprehensive exploration of variability (covering both fine-grained options and “macro” structural features), but also a uniform methodology for the subsequent sections, where each pattern will be formalized, implemented, and its configuration space evaluated (Sections 4.2–4.6).
By covering a representative sample of patterns and modeling their variability explicitly, we lay the ground for future expansion to other patterns and domains. Table 1 summarizes the selected patterns and their main sources of variability, serving as a roadmap for the remainder of this section.
| Design Pattern | Structural Variability |
Behavioral Variability |
Key Constraints |
|---|---|---|---|
| DP1: Singleton | Initialization strategy. | Thread safety, access method. | Eager vs lazy, valid combinations. |
| DP2: Strategy | Number and type of strategies. | Dynamic selection mechanism. | Caching, setter/factory selection. |
| DP3: Observer | Observer cardinality. | Sync/async notification. | One-to-many, notification mode. |
| DP4: Adapter | Adaptee binding mechanism. | Interface compatibility. | Inheritance or composition. |
| DP5: Factory Method | Product class alternatives. | Instantiation parameterization. | Subclass responsibility, type safety. |
The Singleton pattern is one of the most widely used in mobile application development to ensure the existence of a single instance of a class and to provide a global point of access to it. However, this pattern is not monolithic: it exhibits relevant internal variability that includes aspects such as initialization strategy, access method, and concurrency safety.
The UVL model in Listing [uvl:Singleton] captures the main variation points of the Singleton pattern. It defines:
either Eager (the instance is created at class loading time) or Lazy (the instance is created on demand).
the instance can be retrieved through a getInstance() method or via a static field.
synchronization can be added for concurrent scenarios, but it is only applicable in the case of Lazy initialization.
defined as a String feature, allowing customization of the generated class name.
features
"Singleton Pattern" {abstract}
mandatory
Initialization {abstract}
alternative
Eager
Lazy
"Access Method" {abstract}
alternative
"getInstance()"
"Static Field"
optional
"Thread Safety"
String "Class Name"
constraints
Eager => !"Thread Safety"
"Static Field" => Eager
Additionally, two constraints are defined to ensure configuration validity: (1) if initialization is Eager, the Thread Safety option cannot be selected; (2) the Static Field access method is only valid in
combination with Eager initialization. This model not only formalizes the available options but also prevents inconsistent configurations, ensuring correctness in the generated code.
{# singleton.swift.j2 #}
{% if features.get("Thread Safety") %} import Dispatch {% endif %}
{% set class_name = features.get("Class Name", "Singleton") %}
class {{ class_name }} {
{% if features.get("Initialization") == "Eager" %}
static let shared = {{ class_name }}()
{% elif features.get("Initialization") == "Lazy" %}
private static var _instance: {{ class_name }}?
{% endif %}
private init() {} // Private constructor
{% if features.get("Access Method") == "getInstance()" %}
static func getInstance() -> {{ class_name }} {
{% if features.get("Initialization") == "Eager" %}
return shared
{% else %}
// Lazy initialization and Thread Safety options ...
{% endif %}
}
{% elif features.get("Access Method") == "Static Field" %}
static var instance: {{ class_name }} {
// Access through static property ...
}
{% endif %}
}
Listing [jinja:Singleton] shows an excerpt of the Jinja template that implements the variability of the Singleton pattern in Swift. The template uses directives to
activate or deactivate code fragments depending on the features selected in the UVL model. For instance, the Thread Safety option imports the Dispatch library (line 3) and introduces a synchronization queue to protect instance
creation. The initialization mechanism is controlled with conditionals: in Eager mode, the instance is created statically (line 8), while in Lazy mode, it is delayed until the first invocation (line 10). The access method can be
implemented either through a getInstance() function (lines 16–22) or through a static property instance (lines 24–26). Finally, the class name is parameterized via the UVL feature Class Name (line 4), enabling reuse
in different contexts.
The configuration space of the Singleton pattern is relatively small but sufficiently expressive to cover the main usage scenarios in mobile applications: 2 initialization options (Eager vs. Lazy); 2 access methods
(getInstance() vs. Static Field); and the presence or absence of Thread Safety (restricted to Lazy initialization). Considering the defined constraints, the model produces a total of four valid
configurations3:
{ Eager, getInstance() }
{ Eager, Static Field }
{ Lazy, getInstance() }
{ Lazy, getInstance(), Thread Safety }
This analysis confirms that the UVL model captures the essential variability without introducing inconsistent combinations.
The Strategy pattern is widely employed in mobile application development as it defines a family of algorithms, encapsulates them, and enables their interchangeability, thereby allowing the algorithm to evolve independently of the clients that employ it. Nevertheless, this pattern is not monolithic: it exhibits significant internal variability that encompasses aspects such as the mechanism for selecting the strategy, the degree of coupling with the context, and performance considerations in resource-constrained environments.
features
"Strategy Pattern" {abstract}
mandatory
"Strategy Interface" {abstract}
alternative
"Abstract Class"
Interface
Protocol
"Strategy Selection" {abstract}
alternative
"Constructor Injection"
"Setter Method"
"Factory Method"
"Context Behavior" {abstract}
alternative
"Delegate to Strategy"
"Template Method"
optional
"Strategy Validation"
"Default Strategy"
"Strategy Caching"
String "Context Class Name"
String "Strategy Interface Name"
Integer "Number of Strategies" cardinality [2..10]
constraints
"Abstract Class" => !Protocol
Protocol => Interface
"Factory Method" => "Strategy Validation"
"Template Method" => "Abstract Class"
"Number of Strategies" > 1
The UVL model in Listing [uvl:Strategy] captures the main variation points of the Strategy pattern. It defines:
The abstraction of the strategy can be realized through an interface (if supported by the target language), a protocol (Swift’s interface mechanism), or an abstract class (an inheritance-based approach).
The process of selecting and configuring strategies can be performed by means of constructor injection (the strategy is set at creation time), setter method (the strategy can be replaced during execution), or factory method (the strategy is created and selected through a factory).
The context may either delegate directly to the strategy (simple delegation) or apply the template method (in which the context defines the overall structure of the algorithm while strategies implement specific steps).
An optional feature enables validation of the strategy’s suitability prior to its execution.
An optional feature that provides a fallback strategy when no specific strategy is set.
An optional feature that enables caching of strategy instances to avoid repeated instantiation, improving performance in scenarios with frequent strategy switches.
They allow customization of generated class and interface names.
An integer feature with cardinality that specifies how many concrete strategy implementations will be generated (range 2–10).
Additionally, several constraints ensure configuration validity: (1) Abstract Class approach is incompatible with Protocol; (2) Protocol requires Interface selection; (3) Factory Method
requires Strategy Validation for proper error handling; (4) Template Method requires Abstract Class approach; (5) and Number of Strategies must be greater than 1.
{# strategy.swift.j2 #}
{% set ctx_name = features.get("Context Class Name", "Context") %}
{% set strat_name = features.get("Strategy Interface Name", "Strategy") %}
{% set num_strategies = features.get("Number of Strategies", 2) %}
// Strategy Protocol
{% if features.get("Strategy Interface") == "Protocol" %}
protocol {{ strat_name }} {
func execute()
}
{% else %}
... // Class-based approach
{% endif %}
// Concrete Strategies ({{ num_strategies }} implementations generated)
{% for i in range(1, num_strategies + 1) %}
class ConcreteStrategy{{ i }}: {{ strat_name }} {
func execute() { print("Executing strategy {{ i }}") }
}
{% endfor %}
class {{ ctx_name }} {
private var strategy: {{ strat_name }}
{% if features.get("Strategy Caching") %}
private var strategyCache: [String: {{ strat_name }}] = [:]
{% endif %}
init(strategy: {{ strat_name }}) { self.strategy = strategy }
{% if features.get("Strategy Selection") == "Factory Method" %}
func setStrategy(byType strategyType: String) {
// Factory-based instantiation with optional caching
// Full implementation available at GitHub repository
}
{% endif %}
func executeStrategy() { strategy.execute() }
}
Listing [jinja:Strategy] shows a Jinja template excerpt for the Strategy pattern in Swift. The template begins by extracting configuration parameters from the UVL
features (lines 2–4), setting default values for the context name, strategy interface name, and number of strategies. The Strategy Interface selection (lines 7–13) determines whether to generate a Swift protocol or use a class-based approach,
though the class-based implementation is abbreviated in this excerpt.
The template generates concrete strategy implementations dynamically (lines 16–26) based on the Number of Strategies feature, creating the specified number of ConcreteStrategy classes that conform to the selected interface
approach. Each generated strategy includes a simple execution method with pattern-specific logic. The Context class implementation (lines 29–71) varies based on the selected strategy selection mechanism. Constructor Injection is
always supported through the init method (lines 35–37), while optional setter methods are generated when Setter Method or Factory Method selection is chosen (lines 39–43). The Factory Method approach (lines 45–66)
includes string-based strategy instantiation with a switch statement that maps strategy names to concrete implementations. Performance optimization through Strategy Caching is conditionally included (lines 31–33, 47–52, 62–64), maintaining a
dictionary cache of strategy instances to avoid repeated instantiation.
The configuration space of the Strategy pattern is moderately complex, reflecting the diverse architectural needs in mobile application development. The model supports 3 strategy interface approaches, 3 selection mechanisms, 2 context behaviors, and
various optional features, combined with a configurable number of strategies (2–10). Considering the defined constraints, the model produces up to 120 distinct configurations. The constraints significantly reduce the theoretical configuration space by
eliminating inconsistent combinations, such as Abstract Class with Protocol or Template Method without Abstract Class. As examples, we show three representative valid configurations:
{ Protocol, Interface, Constructor Injection, Delegate to Strategy, 3 strategies }
{ Protocol, Interface, Setter Method, Delegate to Strategy, Strategy Caching, 5 strategies }
{ Abstract Class, Factory Method, Template Method, Strategy Validation, Strategy Caching, 4 strategies }
The Observer pattern is extensively used in mobile application development to establish one-to-many dependencies between objects, allowing multiple observers to be automatically notified when a subject’s state changes. In mobile applications, this pattern is particularly valuable for implementing reactive user interfaces, data binding mechanisms, model-view synchronization, and event-driven architectures. It exhibits internal variability such as different notification strategies, observer management approaches, and performance optimizations for resource-constrained mobile environments.
The UVL model in Listing [uvl:Observer] defines the main variation points of the Observer pattern:
It can be implemented using an Abstract Subject (inheritance-based approach with common functionality) or Protocol Subject (interface-based approach for maximum flexibility).
The observer abstraction can be defined as a Protocol (Swift’s interface mechanism) or as an Abstract Class (when shared behavior among observers is needed).
The communication mechanism between subject and observers can follow Push Model (subject sends data directly to observers), Pull Model (observers query the subject for updated data), or Hybrid Model (combination of
both approaches).
Optional feature that provides sophisticated observer lifecycle management, including registration, deregistration, and observer validation.
Optional feature enabling asynchronous notification delivery, preventing UI blocking and improving performance in mobile applications.
Optional feature allowing selective notification based on observer interests or event types.
It enables prioritized notification ordering, ensuring critical observers receive updates first.
It implements weak reference patterns to prevent retain cycles and memory leaks in mobile applications.
It supports typed events and filtering capabilities.
They allow customization of generated class and interface names.
An integer feature with cardinality specifying how many concrete observer implementations will be generated (range 1-10).
features
"Observer Pattern" {abstract}
mandatory
"Subject Type" {abstract}
alternative
"Abstract Subject"
"Protocol Subject"
"Observer Interface" {abstract}
alternative
Protocol
"Abstract Class"
"Notification Method" {abstract}
alternative
"Push Model"
"Pull Model"
"Hybrid Model"
optional
"Observer Management"
"Async Notifications"
"Filtered Notifications"
"Observer Priority"
"Weak References"
"Event Types"
String "Subject Name"
String "Observer Name"
String "Concrete Observer Name"
Integer "Number of Observers" cardinality [1..10]
constraints
"Observer Priority" => "Observer Management"
"Filtered Notifications" => "Event Types"
"Async Notifications" => "Protocol Subject"
"Number of Observers" > 0
Several constraints enforce valid configurations: (1) Observer Priority requires Observer Management selection; (2) Filtered Notifications requires Event Types for Efficiency. Without event types, all
observers would receive all notifications, which would make the concept of “filtering” useless; (3) Async Notifications requires Protocol Subject; (4) and The number of observers must be greater than 0.
Listing [jinja:Observer] shows a Jinja template excerpt for the Observer pattern in Swift. The template begins by extracting configuration parameters from the UVL
features (lines 2–3), setting default values for the subject name and observer interface name. The Observer Interface is consistently implemented as a protocol (lines 5–12) with AnyObject conformance to support weak references, a
critical requirement for mobile applications to prevent memory leaks.
{# swift_observer.j2 #}
{% set subject_name = features.get("Subject Name", "Subject") %}
{% set obs_name = features.get("Observer Name", "Observer") %}
protocol {{ obs_name }}: AnyObject {
var observerID: String { get }
{% if features.get("Push Model") %}
func update(data: [String: Any])
{% elif features.get("Pull Model") %}
func update(subject: {{ subject_name }})
{% endif %}
}
class {{ subject_name }} {
private var observers: [{{ obs_name }}] = []
func attach(_ observer: {{ obs_name }}) {
observers.append(observer)
}
func notify({% if features.get("Push Model") %}data: [String: Any]{% endif %}) {
for observer in observers {
// Notification method varies: Push/Pull/Hybrid models supported
// Additional features: async delivery, filtering, priority ordering
}
}
}
The notification method selection drives the core communication mechanism between subject and observers. In Push Model mode (lines 7–8, 21, 23–24), the subject actively sends data to observers through the update method with a data
parameter. In Pull Model mode (lines 9–10, 25–26), observers receive minimal notification and must query the subject directly for updated information, providing more control over data access patterns.
The Subject implementation (lines 14–30) maintains an array of observers and provides basic attachment and notification functionality. The notify method varies its signature based on the selected notification model: Push Model includes a
data parameter (line 21), while Pull Model requires no additional parameters as observers access the subject directly. While this excerpt demonstrates the core variability implementation, the complete template includes additional features such as observer
management, asynchronous notifications, and filtering capabilities based on the selected optional features from the UVL model.
The configuration space of the Observer pattern reflects moderate to high complexity, suitable for the diverse architectural requirements in mobile application development. The model supports 2 subject types, 2 observer interface approaches, 3 notification methods, and 6 optional features, combined with configurable observer counts (1–10). While the constraints reduce the theoretical configuration space by eliminating architecturally inconsistent combinations, such as asynchronous notifications without protocol-based subjects; the model still produces 648 distinct configurations. This configuration space addresses the spectrum of reactive programming needs in mobile development, from simple notification mechanisms to sophisticated event-driven architectures. Representative valid configurations include:
{ Protocol Subject, Protocol, Push Model, 2 observers }.
{ Protocol Subject, Protocol, Pull Model, Observer Management, Weak References, 5 observers }.
{ Protocol Subject, Protocol, Hybrid Model, Async Notifications, Filtered Notifications, Event Types, Observer Priority, 8 observers }.
The Adapter pattern is a structural design pattern commonly employed in mobile application development to enable interoperability between classes featuring incompatible interfaces. This approach is particularly advantageous within mobile applications for integrating third-party libraries, legacy codebases, external APIs, and platform-specific components that diverge from expected interface specifications. Notably, the Adapter pattern is not monolithic; it exhibits significant internal heterogeneity, encompassing various adaptation strategies, interface compatibility techniques, and data transformation procedures tailored to the diverse requirements encountered in contemporary mobile development scenarios.
features
"Adapter Pattern" {abstract}
mandatory
"Adapter Type" {abstract}
alternative
"Object Adapter"
"Class Adapter"
"Target Interface" {abstract}
alternative
Protocol
"Abstract Class"
"Adapter Implementation" {abstract}
alternative
"Full Implementation"
"Partial Implementation"
optional
"Method Mapping"
"Data Conversion"
"Error Handling"
"Multiple Adaptees"
String "Target Interface Name"
String "Adapter Class Name"
String "Adaptee Class Name"
Integer "Number of Methods" cardinality [1..10]
constraints
"Class Adapter" => "Abstract Class"
"Multiple Adaptees" => "Object Adapter"
"Partial Implementation" => "Error Handling"
"Number of Methods" > 0
The UVL model in Listing [uvl:Adapter] captures the main variation points of the Adapter pattern. It defines:
The adaptation mechanism can be implemented using Object Adapter (composition-based approach that wraps the adaptee) or Class Adapter (inheritance-based approach using protocol extensions in Swift).
It can be defined as a Protocol (Swift’s interface mechanism for maximum flexibility) or Abstract Class (when shared implementation is needed across adapters).
The completeness of interface adaptation can be Full Implementation (all target methods implemented) or Partial Implementation (selective method adaptation with error handling for unimplemented methods).
Optional feature that provides sophisticated mapping between target and adaptee method signatures, parameter transformations, and naming conventions.
Optional feature enabling automatic data type conversion between incompatible target and adaptee data types, essential for API integration scenarios.
Optional feature providing robust error handling mechanisms for adaptation failures, type conversion errors, and method invocation issues.
Optional feature supporting adaptation of multiple incompatible classes through a single adapter interface, useful for aggregating legacy systems.
String features allowing customization of generated class and interface names for different contexts.
An integer feature with cardinality [1..10] specifying how many methods will be adapted in the interface.
Additionally, several constraints ensure configuration validity: (1) Class Adapter requires Abstract Class for inheritance-based adaptation; (2) Multiple Adaptees requires Object Adapter for
composition flexibility; (3) Partial Implementation requires Error Handling for unimplemented method management; and (4) Number of Methods must be greater than 0.
Listing [jinja:Adapter] shows an excerpt of the Jinja template that implements the variability of the Adapter pattern in Swift. The template uses conditional
directives to generate different structural approaches based on the selected features from the UVL model. The template begins by extracting configuration parameters (lines 2–4), setting default values for the Target Interface Name,
Adapter Class Name, and Adaptee Class Name. The target interface is consistently implemented as a protocol (lines 6–8), with the Data Conversion feature determining whether methods return specific types or void. The
adaptee class represents the legacy component being adapted (lines 9–16), with Data Conversion controlling return type compatibility. The core adaptation logic switches between Object Adapter (lines 18–31) using composition with a
private adaptee instance and initialization, versus Class Adapter (lines 32–41) using Swift protocol extensions for inheritance-based adaptation. The request method implementation varies based on Data Conversion,
either performing type conversion from Int to String (lines 26–27, 35–36) or simple method delegation (lines 29, 38). While this excerpt demonstrates the fundamental adaptation mechanisms, the complete template includes additional
features such as method mapping, error handling, and multiple adaptee support based on selected optional features.
The configuration space of the Adapter pattern reflects moderate complexity, addressing diverse integration challenges in mobile application development. The model supports 2 adapter types, 2 target interface approaches, 2 implementation completeness levels, and 4 optional features, combined with configurable method counts (1–10).
Considering the defined constraints, the model produces up to 120 distinct valid configurations that takes care of architecturally inconsistent combinations, such as Class Adapter without Abstract Class or
Multiple Adaptees without Object Adapter flexibility. Representative valid configurations include:
{ Object Adapter, Protocol, Full Implementation, Data Conversion, 3 methods }
{ Class Adapter, Abstract Class, Partial Implementation, Error Handling, Method Mapping, 5 methods }
{ Object Adapter, Protocol, Full Implementation, Multiple Adaptees, Data Conversion, Error Handling, 8 methods }
The Factory Method pattern occupies a central position in mobile software development when the objective is to delegate object instantiation to subclasses, thereby enhancing the architectural flexibility and extensibility of the system. However, its application presents diverse alternatives and differentiated decisions, each of which can have significant consequences for the final design.
features
"Factory Method Pattern" {abstract}
mandatory
"Creator Type" {abstract}
alternative
"Abstract Creator"
"Concrete Creator"
"Product Interface" {abstract}
alternative
Protocol
"Abstract Class"
"Base Class"
"Factory Method" {abstract}
alternative
"Abstract Method"
"Default Implementation"
optional
"Product Registration"
"Parameter Passing"
"Error Handling"
"Generic Support"
"Lazy Loading"
String "Creator Class Name"
String "Product Interface"
String "Factory Method Name"
Integer "Number of Products" cardinality [2..10]
constraints
"Abstract Method" => "Abstract Creator"
"Default Implementation" => "Concrete Creator"
"Product Registration" => "Generic Support"
"Number of Products" > 1
The UVL model in Listing [uvl:FactoryMethod] captures the main variation points of the Factory Method pattern:
The pattern accommodates both an Abstract Creator that specifies the factory interface, and a Concrete Creator that provides the factory implementation.
It is manifested as a Protocol, Abstract Class, or Base Class, enabling a spectrum of polymorphism and type abstraction suitable for different software requirements.
There are two main options available for implementation: an Abstract Method (which requires subclasses to provide the instantiation logic) or a Default Implementation (the base class provides a default creation method).
The model encompasses additional extensions such as Product Registration (enabling runtime registration of available products), Parameter Passing (the factory method can accept arguments that influence instance creation),
Error Handling, Generic Support, and Lazy Loading.
Configuration is further customized through Creator Class Name, Product Interface Name, and Factory Method Name (all string attributes). The number of products is set via the integer attribute
Number of Products, with a minimum cardinality of two.
{# factory_method.swift.j2 #}
{% set creator = features.get("Creator Class Name", "Creator") %}
{% set product = features.get("Product Interface", "Product") %}
protocol {{ product }} {
func operation(){% if features.get("Parameter Passing") %} -> String{% endif %}
}
class ConcreteProduct: {{ product }} {
func operation(){% if features.get("Parameter Passing") %} -> String{% endif %} {
print("ConcreteProduct: operation")
{% if features.get("Parameter Passing") %}
return "result"
{% endif %}
}
}
{% if features.get("Abstract Creator") %}
class {{ creator }} {
func createProduct() -> {{ product }} {
fatalError("Override required")
}
}
{% else %}
class {{ creator }} {
func createProduct({% if features.get("Parameter Passing") %}type: String{% endif %}) -> {{ product }} {
return ConcreteProduct()
}
}{% endif %}
Listing [jinja:FactoryMethod] shows an excerpt of the Jinja template that implements the variability of the Factory Method pattern in Swift. The template
uses directives to activate or deactivate code fragments depending on the features selected in the UVL model. The template begins by extracting configuration parameters from the UVL features (lines 2–3), setting default values for the
Creator Class Name and Product Interface Name. The product interface is consistently implemented as a protocol (lines 5–7), with the Parameter Passing feature determining whether methods include return types or
additional parameters. The core factory method implementation switches between Abstract Creator (lines 18–23) using fatalError("Override required") to enforce subclass implementation, versus Concrete Creator (lines
24–29) providing a default implementation that returns a concrete product instance. The createProduct method varies its signature based on Parameter Passing, either accepting type arguments (line 26) or requiring no parameters for
simple instantiation.
The configuration space of the Factory Method pattern reflects moderate to high complexity, addressing diverse object creation challenges in mobile application development. The model supports 2 creator types, 3 product interface approaches, 2 factory method implementations, and 5 optional features, combined with configurable product counts (2–10).
Considering the defined constraints, the model produces 288 valid configurations that consider architecturally inconsistent combinations, such as avoiding Abstract Method without Abstract Creator or
Product Registration without Generic Support flexibility. Representative valid configurations include:
{ Abstract Creator, Protocol, Abstract Method, Parameter Passing, 3 products }
{ Concrete Creator, Base Class, Default Implementation, Error Handling, Lazy Loading, 5 products }
{ Abstract Creator, Protocol, Abstract Method, Product Registration, Generic Support, Parameter Passing, Error Handling, 8 products }
We make our UVL models and Jinja templates publicly available and conducted experiments to analyse the variability complexity and the configuration space of the design patterns. In addition, we assess the development effort involved by comparing manual and generated implementations, illustrating this with a case study on the Singleton pattern.
Following open science best practices, our UVL models dataset as well as the Jinja templates and all associated resources are publicly available online in GitHub:
Website and instructions: https://trran.github.io/UVL2Pat/
GitHub: https://github.com/trran/UVL2Pat
We use the tools flamapy [23] and FM Fact Label [24], that are part of the UVL ecosystem [25], to analyze the complexity and configuration space of the feature models specifed in UVL. Those are online web-based tools, and thus, not additional setup is required.
The complexity of a feature model can be characterized by its number of features and its number of valid products (configurations) [26], [27]. The goal is to analyze the complexity of the UVL models and the configuration space of the design patterns.
Figure 3 summarizes the patterns according to the number of features captured in their UVL models, while Figure 4 compares them with respect to the size of their configuration spaces. From Figure 3, we observe that the variability of the patterns is almost evenly distributed. Except for simpler patterns such as Singleton, which exhibit nine features, the others patterns involve a richer feature set (from 18 to 21 different features), as they encapsulate more variation points related to roles, interactions, and optional extensions. Despite the number of features is similar along the patterns, they present different type of features and variation points for each pattern (as shown in Section 4). This suggests that different patterns introduce different levels of modeling effort: some can be formalized with minimal feature hierarchies, while others require a more fine-grained specification to capture their inherent flexibility.
Figure 4 further highlights the impact of variability on the configuration space. The Singleton pattern results in only four valid configurations due to its restricted options and constraints. The Factory Method and Observer patterns, on the other hand, generate a significantly larger configuration space (288 and 648 configurations respectively), which reflects their combinatorial variability when multiple roles and communication mechanisms are considered. The Strategy and Adapter patterns occupy an intermediate position: although their feature models are not as complex as Factory Method or Observer, their configuration spaces are still non-trivial due to the inclusion of optional features and multiple mapping options.
These findings lead to several insights. First, the formalization of design patterns as feature models makes their variability explicit and comparable, which is often overlooked in traditional pattern catalogs. Second, the analysis of configuration spaces reveals the potential challenges in automated derivation: while some patterns can be generated with almost no configuration overhead, others may require careful selection of features to avoid overwhelming the developer with unnecessary options. Finally, the results confirm that our approach scales from simple to more complex patterns, showing that UVL and template-based generation are suitable for capturing and exploiting variability across the full spectrum of design patterns.
To further illustrate the benefits of our approach, we compared a manually implemented Singleton, inspired by a real-world chat service, with the automatically generated version produced by our UVL + Jinja pipeline. The manual implementation consisted
of multiple classes (e.g.,FriendsChatService, Message, and two view controllers) and approximately 130 lines of code. In contrast, the generated version focused exclusively on the structural essence of the pattern, with only 55
lines of code and built-in thread safety. Table 2 summarizes the comparison. The results show that our approach reduces boilerplate code by more than 50%, lowers cyclomatic complexity, and introduces
configuration options (e.g.,lazy initialization, thread safety) not present in the manual implementation. While domain-specific logic still requires manual coding, the proposed approach accelerates prototyping and ensures architectural consistency.
| Metric | Manual (Chat) | Generated (UVL+Jinja) |
|---|---|---|
| Lines of Code (LOC) | \(\sim\)130 | \(\sim\)55 |
| Number of Classes | 6 | 1 |
| Cyclomatic Complexity | \(\geq\) 10 | \(\leq\) 3 |
| Estimated Dev. Time | 3–4 h | 15–20 min |
| Configurability | Fixed | Parametrizable |
| Reusability | Limited to case | Generic |
This work presents certain limitations that may affect the evaluation validity. First, the evaluation has focused on a reduced set of patterns and exclusively on one language (Swift). It remains necessary to broaden the empirical validation to include more design patterns such as Decorator, Proxy, or State, among others; as well as other programming languages used in mobile applications such as Kotlin, Java, or Dart. Another threat is the lack of a comparison between the effort and benefits relative to the automated generation of the patterns’ variants and the manual development of those variants.
This section discusses prior research related to our work on modeling and exploiting design pattern variability for mobile application generation. We organize the discussion into two main areas: approaches that combine SPL with design patterns, and existing generative techniques in software development.
The intersection of SPLs and design patterns has been explored primarily through patterns as variability implementation mechanisms in SPLs [10], [13]. The Strategy pattern implements behavioral variability, Decorator enables dynamic feature addition, Template Method varies algorithms through inheritance, and Observer facilitates feature addition/removal. However, our work addresses a different challenge: the internal variability within patterns themselves, which has received limited attention [14].
Our work, however, tackles a different problem: the variability within the patterns themselves. The intrinsic configurable nature of design patterns has been largely overlooked, with variations often being implicit in standard catalogs [4], [15], [16]. A notable exception is the work by Seidl et al. [14], who introduced the concept of variability-aware design patterns to bridge the gap between SPLs and design patterns. Their approach uses role models to capture a design pattern and its connection to an SPL feature model, allowing for systematic application and generation of pattern-based artifacts. Other related efforts include those that combine feature models with architectural patterns. Font et al. [28] proposed a method for building SPLs from conceptualized model patterns. Degueule et al. [29] developed a tooling solution that integrates architectural pattern catalogs with variability models in CVL (the obsolete Common Variability Language) [30] to automatically synthesize architectural variants. While these works are highly relevant, they often rely on predefined pattern models and explicit metamodels. Our approach, by using a modern and extensible language such as UVL and template-based code generation, provides a flexible and scalable method to formalize and exploit design pattern variability. Moreover, UVL enables the automated analysis of the configuration space of the design patterns by using the tool support of the UVL ecosystem [31] such as flamapy [23], flamapyIDE [32], FeatureIDE [33], FM Fact Label [24], and UVengine [22].
The field of Generative Programming [13] focuses on creating software from high-level specifications. The implementation of SPLs and the automatic generation of products relies on different approaches. These methods can be broadly categorized into composition-based and annotation-based techniques [10], [34]. Compositional approaches, such as those used in Feature-Oriented Software Development (FOSD) [10], Aspect-Oriented Programming (AOP) [35], or Delta-Oriented Programming (DOP) [36], among others, rely on composing code fragments or assets to build a complete product. In contrast, annotation-based techniques use annotations or directives within a code base to mark variation points, which are then processed by a generator to create a specific product variant (e.g.,preprocessors [37] or configuration parameters). Although the literature often recommends the use of compositional over annotation-based approaches due to their advantages in managing complexity and reusability [10], the industry has adopted annotative approaches because of their simplicity and lower entry barrier [34], [37].
A widely adopted and effective technique within this field is template-based code generation [21]. This approach enriches a base language with special directives to automatically produce customized content. While sharing similarities with annotation-based techniques, our approach leverages the power of the Jinja
templates, which offer significant advantages over simpler annotative-based approaches. Unlike tools that are limited to basic #ifdef annotations [37] or simple parameter substitution [38], Jinja provides a rich set of directives for
managing complex variability. This includes advanced control structures such as if/elif/else, for-loops, macros, and blocks, which enable sophisticated logic for substitution and replacement of code elements.
Furthermore, Jinja’s language-independent nature makes it a highly versatile tool, capable of being used to generate code for any text-based language, including mobile applications. To effectively bridge the gap between our high-level variability models
and the code generation process, our approach relies on UVengine [22]. This universal variability resolution engine is
specifically designed to resolve UVL models over Jinja templates. Its utility has been demonstrated in practical scenarios across diverse domains and artifact types, such as: Visualization pipelines to generate data visualization charts with
configurable visual components and datasets [39], [40]; Kubernetes configurations to derive deployment descriptors for microservices and containers, adapting resource constraints based on the Kubernetes feature
model [41]; and Data migration processes to generate reusable transformation scripts in data migration between
heterogeneous content management systems [42]. However, to the best of
our knowledge, our work is the first to apply this specific generative approach to model and exploit the internal variability of design patterns for the purpose of generating customized mobile application code.
While automatic code generation is increasingly popular, especially with the rise of large language models (LLMs) [1], the resulting code often lacks architectural quality and adherence to fundamental design principles [2], [3]. Our work addresses this gap by providing a structured, model-driven approach that ensures the systematic application of design patterns, thereby promoting the generation of well-architected mobile applications. The specific context of mobile application development presents a unique challenge, as it requires a high degree of adaptability to different platforms (e.g.,iOS, Android) and evolving user interfaces, while maintaining performance and code quality. By focusing on Swift and the generation of customizable design patterns, our work provides a concrete solution to the specific challenges faced by mobile developers who require scalable and maintainable solutions across different platforms and user expectations.
In this work, we have addressed the following research question: How can the variability of design patterns be modeled and exploited to generate customized and architecturally robust mobile applications? Our principal contribution is the formalization of the internal variability of classic design patterns through the Universal Variability Language (UVL), combined with their integration into Jinja templates for the automatic generation of Swift code. This approach enables design patterns to be treated as configurable assets within an SPL approach, opening the door to mobile applications that are more flexible, maintainable, and adaptable.
The analysis of the configuration space demonstrates that the variability of each pattern can be systematically and consistently captured, thus avoiding invalid configurations and ensuring correct implementations. This evidences the viability of bridging model-driven engineering with practical mobile code generation, guaranteeing architectural quality even in the context of AI-assisted development or rapid code generation tools.
For future work, we plan to expand our research in several directions: (1) expanding our catalog of supported patterns; (2) incorporating quality and maintainability metrics in the evaluation; (3) exploring integration with generative AI tools; and (4) applying this methodology to industrial-scale mobile development case studies.
This work is supported by Mescyt, Universidad San Jorge, ITIS/Universidad de Málaga, and Sergio Jiménez González (Hotel Las Galias), to whom we are deeply grateful.
UVengine: https://uvengine.github.io/↩︎
We exclude the String feature Class Name from the analysis since it does not affect the configuration space.↩︎