The Adapter Design Pattern, categorized under Structural design patterns,
acts as a bridge between two incompatible interfaces, enabling them to work together.
The pattern introduces an intermediary interface (the Adapter) that converts one interface
into another, allowing classes with incompatible interfaces to collaborate seamlessly.
Why Should PHP Programmers Study the Adapter Design Pattern?
Legacy System Integration:
Enables modern PHP systems to work with older codebases without large rewrites.
Improved Code Reusability:
Adapts existing components instead of rewriting working logic.
Better Modularity:
Keeps interface translation separate from business logic.
Flexible System Evolution:
Supports adding new functionality without disturbing existing architecture.
Open/Closed Principle:
Extends behavior without modifying existing classes.
Simplified Interfaces:
Wraps complex APIs into developer-friendly interfaces.
Versatile Adaptation:
Multiple adapters can support multiple integration scenarios.
Adapter — UML
Target
Defines the interface the Client expects
Represents standard system behavior
Used directly by the Client
Client
Works through the Target interface
Is unaware of the Adaptee
Relies on Adapter for compatibility
Adaptee
Has an incompatible interface
Contains useful existing functionality
Cannot be easily modified
Adapter
Implements the Target interface
Translates Client requests
Connects incompatible interfaces
Target.php
interface Target
{
public function request(): string;
}
Adaptee.php
class Adaptee
{
public function specificRequest(): string
{
return "Specific request from Adaptee.";
}
}
Adapter.php
class Adapter implements Target
{
private $adaptee;
public function __construct(Adaptee $adaptee)
{
$this->adaptee = $adaptee;
}
public function request(): string
{
return "Adapter: " . $this->adaptee->specificRequest();
}
}
index.php
require_once 'Target.php';
require_once 'Adaptee.php';
require_once 'Adapter.php';
function clientCode(Target $target)
{
echo $target->request();
}
echo "Client code with Adaptee:<br/>";
$adaptee = new Adaptee();
echo $adaptee->specificRequest();
echo "<br/><br/>";
echo "Client code with Adapter:<br/>";
$adapter = new Adapter($adaptee);
clientCode($adapter);
Key Idea
The Adapter pattern wraps an existing class and translates method calls from the
Target interface into calls understood by the Adaptee,
allowing incompatible interfaces to work together without modification.
S.W.O.T. Analysis — Adapter Pattern (PHP)
Strengths
Enables seamless interface compatibility
Promotes reuse of legacy and third-party code
Decouples client logic from implementation
Weaknesses
Adds abstraction layers
May introduce slight performance overhead
Often temporary when legacy systems are replaced
Opportunities
Legacy modernization
Third-party API standardization
Cross-language integration
Threats
Bypassing adapters leads to inconsistency
Overuse can clutter architecture
Direct refactoring may be a better long-term solution
The Adapter Design Pattern is classified as a
Structural design pattern.
Its main role is to act as a bridge between two incompatible interfaces,
allowing them to work together.
It achieves this by creating a new interface—the Adapter—which enables
an existing class to be used with other classes without changing its source code.
In essence, the Adapter pattern converts the interface of one class into an interface
expected by the Client.
Why Should Java Programmers Study the Adapter Design Pattern?
Integration with Legacy Code:
Java has a long history and many legacy systems. The Adapter pattern allows older components
to work with newer implementations without a complete rewrite.
Facilitating Third-Party Libraries:
Java’s ecosystem includes countless third-party libraries whose interfaces may not align
with your system. Adapters bridge this gap cleanly.
Enhancing Code Reusability:
Existing components with useful behavior can be reused by adapting their interfaces
instead of rebuilding functionality from scratch.
Promotion of Modularity:
By separating interface adaptation from core logic, the Adapter pattern improves
maintainability and clarity.
Incremental System Evolution:
New behaviors can be introduced gradually without destabilizing existing workflows.
Open/Closed Principle Adherence:
The Adapter pattern aligns with SOLID principles by extending behavior
without modifying existing code.
Simplification of Interfaces:
Adapters can present a simpler, more convenient interface when the original one is too complex.
Multiple Interface Adaptations:
Multiple adapters can be built for the same class to support different systems or APIs.
Given Java’s prominence in enterprise and open-source environments, understanding the Adapter
pattern is essential for building adaptable, maintainable systems that evolve gracefully.
Structural Pattern — Adapter (for Java Students)
Target
Defines the interface the Client expects to use
Represents standard system behavior
Used directly by the Client
Client
Works with objects through the Target interface
Does not know about the Adaptee’s interface
Uses Adapter to communicate correctly
Adaptee
Has an existing interface incompatible with Target
Adapter Design Pattern and Its Relevance to C# Programmers
Let’s discuss the Adapter Design Pattern and why it matters for C# developers.
Adapter Design Pattern
The Adapter Design Pattern (often called the Wrapper) is a
structural design pattern.
Its primary intent is to bridge the gap between two incompatible interfaces.
Essentially, this pattern involves creating a new interface (the adapter) that allows an existing class
to work with other classes without modifying its source code.
It serves as an intermediary that translates requests from one interface to another.
Why Should C# Programmers Study the Adapter Design Pattern?
Integrate Legacy Systems:
In enterprise environments, there’s often a need to integrate legacy systems with newer systems.
The Adapter pattern makes old systems work with new ones without widespread changes.
Promote Code Reusability:
C# developers may have libraries or components with useful features that don’t match the required interface.
Instead of rewriting, they can adapt the component, promoting reuse.
Enhance Modularity:
The Adapter pattern separates the adaptation logic from core functionality,
producing a more modular and maintainable codebase.
Expand Third-Party Library Compatibility:
Third-party libraries often come with interfaces that don’t fit seamlessly.
Adapters mold these components into shapes your system expects.
Flexible System Evolution:
As systems evolve, new interfaces emerge. The Adapter pattern helps incorporate new behavior
without disrupting existing workflows.
Open/Closed Principle:
The Adapter pattern supports the SOLID Open/Closed Principle:
software entities should be open for extension but closed for modification.
Simplify Complex Interfaces:
When an interface is complicated, an adapter can provide a simpler, more convenient entry point.
Multiple Adapters:
For a single interface, multiple adapters can be created to support different systems,
giving flexibility in how systems interact.
In the context of C#, with its rich standard library and extensive ecosystem of third-party components,
understanding the Adapter Design Pattern is crucial. It provides a structured way to ensure components and systems
work in harmony, improving integration, modularity, and maintainability.
By mastering this pattern, C# developers can build adaptable and resilient software architectures.
Adapter Pattern Example in C#
The Adapter pattern allows objects with incompatible interfaces to collaborate.
The main roles are Client, Target, Adapter, and Adaptee.
Structure
Target Interface
Adapter Class
Adaptee Class
Client Class
1) ITarget.cs — Target Interface
public interface ITarget
{
string GetRequest();
}
Explanation:ITarget defines the interface expected by the Client.
2) Adaptee.cs — Adaptee Class
public class Adaptee
{
public string GetSpecificRequest()
{
return "Specific request.";
}
}
Explanation:Adaptee contains useful logic but exposes an incompatible interface.
3) Adapter.cs — Adapter Class
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public string GetRequest()
{
return $"This is '{_adaptee.GetSpecificRequest()}'";
}
}
Explanation:Adapter translates the Adaptee interface into the Target interface.
4) Client.cs — Client Class
public class Client
{
public void Main()
{
Adaptee adaptee = new Adaptee();
ITarget target = new Adapter(adaptee);
Console.WriteLine("Adaptee interface is incompatible with the client.");
Console.WriteLine("But with adapter client can call it's method.");
Console.WriteLine(target.GetRequest());
}
}
Explanation: The Client depends only on ITarget and knows nothing about Adaptee.
5) Program.cs — Entry Point
class Program
{
static void Main(string[] args)
{
new Client().Main();
}
}
Order of Creation
ITarget.cs
Adaptee.cs
Adapter.cs
Client.cs
Program.cs
Expected Output
Adaptee interface is incompatible with the client.
But with adapter client can call it's method.
This is 'Specific request.'
This demonstrates how incompatible interfaces can work together without modifying existing code.
S.W.O.T. Analysis of the Adapter Design Pattern in C#
Strengths
Compatibility: Bridges incompatible interfaces, allowing legacy code integration with modern C# applications.
Reusability: Increases code reuse by adapting existing functionality to meet new requirements.
Encapsulation: Encapsulates conversion logic, keeping it separate from business logic.
Weaknesses
Added Complexity: Adds complexity when adapting simple interfaces, especially for small projects.
Overhead: Runtime overhead may occur due to additional layers of indirection.
Dependency Risk: Over-reliance on adapters can lead to fragile dependencies in dynamic environments.
Opportunities
Legacy Systems: Enables integration of legacy systems into modern .NET environments.
API Bridging: Simplifies bridging third-party APIs with custom implementations in C#.
Cross-Team Collaboration: Facilitates collaboration by allowing teams to work with preferred interfaces.
Threats
Overuse: Excessive use can clutter code with adapters, reducing clarity.
Confusion: Mismanagement of adapter roles may confuse developers during maintenance.
Direct Solutions: Simpler approaches might solve certain compatibility problems without needing adapters.
The Adapter pattern acts as a bridge between two incompatible interfaces.
It allows two classes with incompatible interfaces to work together by converting the
interface of one class into an interface that the other expects.
The Adapter pattern can be seen as a wrapper that modifies an existing class’s
interface without altering its underlying code.
Core Roles
Target – The interface the client expects.
Adaptee – The existing interface that must be adapted.
Adapter – Converts the Adaptee interface into the Target interface.
Why PHP Developers Should Study the Abstract Factory Design Pattern
Here are some reasons why studying the Abstract Factory design pattern can be beneficial for a PHP developer:
Key Benefits
Enhanced Flexibility:
Enables easy switching between different object families without altering the client code.
Consistent Object Creation:
Ensures all related objects are created consistently, reducing potential mismatches and errors.
Code Reusability:
Promotes code reuse by encapsulating object creation logic in a single, maintainable place.
Separation of Concerns:
Separates object creation from business logic, leading to cleaner, more organized code.
Easier Maintenance:
Simplifies maintenance by localizing changes in one place when modifying object creation.
Improved Testability:
Makes unit testing easier by allowing the injection of mock or stub objects.
Supports Scalability:
Facilitates scalable applications by enabling the addition of new product families with minimal changes.
Abstract Factory — UML (for PHP Students)
Abstract Factory:
Declares an interface for operations that create abstract product objects.
Concrete Factory:
Implements the operations to create concrete product objects.
Abstract Product:
Declares an interface for a type of product object.
Concrete Product:
Defines a product object to be created by the corresponding concrete factory.
Client:
Uses only interfaces declared by Abstract Factory and Abstract Product classes.
Abstract Factory Example in PHP 8.1
Below is a detailed example of the Abstract Factory Design Pattern in PHP 8.1,
with the code divided into multiple files and an index.php runner that demonstrates usage.
Step-by-Step Class Creation Order
Product Interfaces
AbstractProductA.php
AbstractProductB.php
Concrete Products
ProductA1.php
ProductA2.php
ProductB1.php
ProductB2.php
Abstract Factory Interface
AbstractFactory.php
Concrete Factories
ConcreteFactory1.php
ConcreteFactory2.php
Client
Client.php
Index File
index.php
Product Interfaces
AbstractProductA.php
<?php
interface AbstractProductA {
public function usefulFunctionA(): string;
}
?>
AbstractProductA: Defines an interface for a type of product object.
AbstractProductB.php
<?php
interface AbstractProductB {
public function usefulFunctionB(): string;
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string;
}
?>
AbstractProductB: Defines an interface for another product type and a method that collaborates with
AbstractProductA.
Concrete Products
ProductA1.php
<?php
require_once 'AbstractProductA.php';
class ProductA1 implements AbstractProductA {
public function usefulFunctionA(): string {
return "The result of the product A1.";
}
}
?>
ProductA1: Concrete implementation of AbstractProductA.
ProductA2.php
<?php
require_once 'AbstractProductA.php';
class ProductA2 implements AbstractProductA {
public function usefulFunctionA(): string {
return "The result of the product A2.";
}
}
?>
ProductA2: Another concrete implementation of AbstractProductA.
ProductB1.php
<?php
require_once 'AbstractProductB.php';
require_once 'AbstractProductA.php';
class ProductB1 implements AbstractProductB {
public function usefulFunctionB(): string {
return "The result of the product B1.";
}
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string {
$result = $collaborator->usefulFunctionA();
return "The result of the B1 collaborating with ({$result})";
}
}
?>
ProductB1: Concrete implementation of AbstractProductB.
ProductB2.php
<?php
require_once 'AbstractProductB.php';
require_once 'AbstractProductA.php';
class ProductB2 implements AbstractProductB {
public function usefulFunctionB(): string {
return "The result of the product B2.";
}
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string {
$result = $collaborator->usefulFunctionA();
return "The result of the B2 collaborating with ({$result})";
}
}
?>
ProductB2: Another concrete implementation of AbstractProductB.
Abstract Factory Interface
AbstractFactory.php
<?php
interface AbstractFactory {
public function createProductA(): AbstractProductA;
public function createProductB(): AbstractProductB;
}
?>
AbstractFactory: Defines an interface for creating abstract product objects.
Concrete Factories
ConcreteFactory1.php
<?php
require_once 'AbstractFactory.php';
require_once 'ProductA1.php';
require_once 'ProductB1.php';
class ConcreteFactory1 implements AbstractFactory {
public function createProductA(): AbstractProductA {
return new ProductA1();
}
public function createProductB(): AbstractProductB {
return new ProductB1();
}
}
?>
ConcreteFactory1: Creates a matching family: ProductA1 and ProductB1.
ConcreteFactory2.php
<?php
require_once 'AbstractFactory.php';
require_once 'ProductA2.php';
require_once 'ProductB2.php';
class ConcreteFactory2 implements AbstractFactory {
public function createProductA(): AbstractProductA {
return new ProductA2();
}
public function createProductB(): AbstractProductB {
return new ProductB2();
}
}
?>
ConcreteFactory2: Creates a matching family: ProductA2 and ProductB2.
Client
Client.php
<?php
require_once 'AbstractFactory.php';
class Client {
private AbstractProductA $productA;
private AbstractProductB $productB;
public function __construct(AbstractFactory $factory) {
$this->productA = $factory->createProductA();
$this->productB = $factory->createProductB();
}
public function run(): void {
echo $this->productB->usefulFunctionB() . "\n";
echo $this->productB->anotherUsefulFunctionB($this->productA) . "\n";
}
}
?>
Client: Uses the abstract factory to create and use the product objects, and demonstrates collaboration
between products.
Index File
index.php
<?php
require_once 'ConcreteFactory1.php';
require_once 'ConcreteFactory2.php';
require_once 'Client.php';
function main() {
echo "Testing with ConcreteFactory1:\n";
$factory1 = new ConcreteFactory1();
$client1 = new Client($factory1);
$client1->run();
echo "\nTesting with ConcreteFactory2:\n";
$factory2 = new ConcreteFactory2();
$client2 = new Client($factory2);
$client2->run();
}
main();
?>
What You Should See When Running the Code
Testing with ConcreteFactory1:
The result of the product B1.
The result of the B1 collaborating with (The result of the product A1.)
Testing with ConcreteFactory2:
The result of the product B2.
The result of the B2 collaborating with (The result of the product A2.)
This output demonstrates that the Client can work with any factory, creating different products and using
their methods interchangeably.
S.W.O.T. Analysis of the Creational Pattern: Abstract Factory (PHP)
Strengths
Consistency: Provides a consistent interface for creating related objects without specifying concrete classes.
Flexibility: Allows easy substitution of entire families of products in PHP projects.
Weaknesses
Complexity: Can introduce unnecessary complexity by requiring additional classes for every product variation.
Learning Curve: Requires a steeper learning curve for beginners due to abstract concepts.
Opportunities
Scalability: Scale applications by adding new products without modifying existing code.
Cross-Platform: Helps support cross-platform PHP applications with consistent abstractions.
Threats
Overengineering: Risk of using this pattern where a simpler approach would be clearer.
Performance Impact: Extra layers of abstraction may add overhead in performance-sensitive PHP systems.
Abstract Factory Design Pattern for Java Developers
Definition
The Abstract Factory pattern provides an interface for creating families of related or dependent objects
without specifying their concrete classes.
๐ 5 Reasons to Study the Abstract Factory Pattern (for Java Developers)
Modularity
Encourages code that’s easier to maintain by abstracting object creation for UI themes or database drivers.
Scalability
Enables building complex systems—like GUI frameworks—where object families must evolve without breaking code.
Flexibility
Supports switching between different product variants (e.g., different OS widgets) with minimal code changes.
Consistency
Helps ensure that Java components from the same family are used together, avoiding incompatibility bugs.
Testability
Facilitates mocking dependencies in unit tests by abstracting object creation behind factory interfaces.
Code: Abstract Factory Pattern in Java (GoF Structure)
Below is a complete Java example of the Abstract Factory design pattern (a Creational pattern),
using the same names and structure as described in the Gang of Four book
Design Patterns: Elements of Reusable Object-Oriented Software.
This is organized for first-year college students and uses an order of creation that avoids dependency problems.
Abstract Factory Pattern in Java
Pattern Category: Creational
Pattern Name: Abstract Factory
Programming Language: Java
Target Audience: First-year college students
Purpose
The Abstract Factory pattern provides an interface for creating families of related or dependent objects
without specifying their concrete classes.
Class/File Creation Order (to avoid dependency errors)
AbstractProductA.java
AbstractProductB.java
ProductA1.java
ProductA2.java
ProductB1.java
ProductB2.java
AbstractFactory.java
ConcreteFactory1.java
ConcreteFactory2.java
Client.java
Main.java
Class Descriptions and Code
1) AbstractProductA.java
Role: Abstract product A declares an interface for a type of product object.
// Abstract product A declares an interface for a type of product object
public interface AbstractProductA {
void interact();
}
Interface:AbstractProductA — base for all ProductA types.
Method:interact() — implemented later by concrete variants.
2) AbstractProductB.java
Role: Abstract product B declares an interface for a type of product object.
// Abstract product B declares an interface for a type of product object
public interface AbstractProductB {
void interactWith(AbstractProductA a);
}
Interface:AbstractProductB — base for all ProductB types.
Method:interactWith(AbstractProductA a) — defines collaboration with ProductA.
public class ProductB1 implements AbstractProductB {
@Override
public void interactWith(AbstractProductA a) {
System.out.print("ProductB1 interacting with ");
a.interact();
}
}
6) ProductB2.java
Role: Another concrete variant of product B.
public class ProductB2 implements AbstractProductB {
@Override
public void interactWith(AbstractProductA a) {
System.out.print("ProductB2 interacting with ");
a.interact();
}
}
7) AbstractFactory.java
Role: Abstract Factory declares an interface for creating abstract products A and B.
// Abstract Factory declares an interface for creating abstract products A and B
public interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
createProductA() returns an AbstractProductA variant.
createProductB() returns an AbstractProductB variant.
8) ConcreteFactory1.java
Role: Creates one consistent family: ProductA1 and ProductB1.
public class ConcreteFactory1 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ProductA1();
}
@Override
public AbstractProductB createProductB() {
return new ProductB1();
}
}
9) ConcreteFactory2.java
Role: Creates another consistent family: ProductA2 and ProductB2.
public class ConcreteFactory2 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ProductA2();
}
@Override
public AbstractProductB createProductB() {
return new ProductB2();
}
}
10) Client.java
Role: Uses only the abstract factory and abstract products (no concrete classes).
// Client uses only interfaces declared by AbstractFactory and products
public class Client {
private AbstractProductA productA;
private AbstractProductB productB;
public Client(AbstractFactory factory) {
productA = factory.createProductA();
productB = factory.createProductB();
}
public void run() {
productB.interactWith(productA);
}
}
11) Main.java
Role: Demonstrates switching factories without changing client code.
public class Main {
public static void main(String[] args) {
System.out.println("Using ConcreteFactory1:");
AbstractFactory factory1 = new ConcreteFactory1();
Client client1 = new Client(factory1);
client1.run();
System.out.println("\nUsing ConcreteFactory2:");
AbstractFactory factory2 = new ConcreteFactory2();
Client client2 = new Client(factory2);
client2.run();
}
}
Result
Using ConcreteFactory1:
ProductB1 interacting with ProductA1 interacting
Using ConcreteFactory2:
ProductB2 interacting with ProductA2 interacting
Summary
The Abstract Factory pattern allows you to create families of related objects without binding your code
to their concrete classes. This example follows the GoF naming and structure, making it ideal for teaching.
Benefits
Promotes consistency among products.
Isolates concrete classes.
Simplifies updates when adding new families.
๐ง S.W.O.T. Analysis of Abstract Factory
Strengths
Promotes clean separation between interface and implementation layers.
Simplifies the addition of new product families.
Reduces code duplication across product variants.
Weaknesses
Increases complexity with multiple classes and interfaces.
Can lead to over-engineering in simple applications.
May obscure concrete object behavior from developers.
Opportunities
Ideal for plug-in architectures and enterprise-level system design.
Enables cross-platform UI development using consistent abstractions.
Misuse can make debugging object creation chains difficult.
May hinder performance if used inappropriately in resource-constrained apps.
Could confuse team members unfamiliar with design patterns.
Summary: For a Java developer, studying the Abstract Factory pattern strengthens your ability to build
scalable, modular, and consistent systems—especially when working with product families and large-scale architectures.
The Abstract Factory pattern helps you create groups of related objects
without knowing the exact classes that will be used.
Think of it like building a theme system for an app — you might use a
Light Theme or a Dark Theme. Each theme has its own
Button, TextBox, and Checkbox.
The Abstract Factory pattern gives you a factory for each theme that knows how to create
all the UI parts that match. You don’t care how the components are created —
you just ask the factory to give them to you.
๐งฑ Why Use It?
You want to create families of related objects (matching buttons and inputs).
You want to enforce consistency across object groups (Light Theme components work together).
You want to avoid writing if / switch logic everywhere just to choose versions.
๐งช A Real-World Analogy
Imagine a furniture factory.
You can order a full set of Victorian-style furniture: chair, bed, and sofa.
Or a full set of Modern-style furniture: chair, bed, and sofa.
You don’t build the chair yourself — you tell the factory:
“Give me a Victorian chair.”
“Give me a Modern bed.”
Each style has its own factory, and each factory knows how to build its own version
of each piece.
๐ง Summary
Concept
Explanation
Abstract Factory
A factory that creates related products as a matching family.
Purpose
Produce related objects that should be used together.
Benefit
Keeps object creation consistent, organized, and clean.
Client code
Doesn’t care about exact classes — it uses interfaces.
Defines functions for creating a family of related objects.
Only describes method names, not real object creation.
Helps ConcreteFactory classes follow the same structure.
ConcreteFactory
Implements the creation functions from AbstractFactory.
Returns real JavaScript objects for one product family.
Ensures its created objects are compatible together.
AbstractProduct
Describes required behavior of a specific product type.
Acts like an interface JavaScript developers follow.
Contains no real working code inside.
ConcreteProduct
Real JavaScript objects created by a ConcreteFactory.
Implements all behavior defined by its AbstractProduct.
Matches the product family of its factory.
Client
Works only with AbstractFactory and AbstractProduct types.
Never depends on ConcreteProduct classes directly.
Can switch product families by changing factories.
Project Overview
This project demonstrates the Abstract Factory design pattern, following the class structure and naming conventions
from pages 84–85 of the GoF book Design Patterns: Elements of Reusable Object-Oriented Software.
Each product family consists of related products (A and B). The client uses the abstract factory and product interfaces,
remaining completely decoupled from concrete implementations.
// Abstract class declaring creation methods for abstract product families
class AbstractFactory {
// Method to create a product of type A
createProductA() {
throw new Error("createProductA() must be implemented."); // Forces subclasses to override
}
// Method to create a product of type B
createProductB() {
throw new Error("createProductB() must be implemented."); // Forces subclasses to override
}
}
module.exports = AbstractFactory;
AbstractProductA.js
// Abstract base class for product family A
class AbstractProductA {
// Abstract method that all ProductA variants must implement
usefulFunctionA() {
throw new Error("usefulFunctionA() must be implemented.");
}
}
module.exports = AbstractProductA;
AbstractProductB.js
// Abstract base class for product family B
class AbstractProductB {
// Abstract method that must be implemented by ProductB variants
usefulFunctionB() {
throw new Error("usefulFunctionB() must be implemented.");
}
// Another abstract method that collaborates with ProductA
anotherUsefulFunctionB(collaborator) {
throw new Error("anotherUsefulFunctionB() must be implemented.");
}
}
module.exports = AbstractProductB;
๐งฑ Concrete Products
ProductA1.js
const AbstractProductA = require('./AbstractProductA');
// Concrete implementation of AbstractProductA
class ProductA1 extends AbstractProductA {
usefulFunctionA() {
return 'ProductA1: The result of the product A1.'; // Simulate behavior
}
}
module.exports = ProductA1;
ProductA2.js
const AbstractProductA = require('./AbstractProductA');
// Concrete implementation of AbstractProductA
class ProductA2 extends AbstractProductA {
usefulFunctionA() {
return 'ProductA2: The result of the product A2.'; // Simulate behavior
}
}
module.exports = ProductA2;
ProductB1.js
const AbstractProductB = require('./AbstractProductB');
// Concrete implementation of AbstractProductB
class ProductB1 extends AbstractProductB {
usefulFunctionB() {
return 'ProductB1: The result of the product B1.';
}
anotherUsefulFunctionB(collaborator) {
return `ProductB1: Collaborating with (${collaborator.usefulFunctionA()})`;
}
}
module.exports = ProductB1;
ProductB2.js
const AbstractProductB = require('./AbstractProductB');
// Concrete implementation of AbstractProductB
class ProductB2 extends AbstractProductB {
usefulFunctionB() {
return 'ProductB2: The result of the product B2.';
}
anotherUsefulFunctionB(collaborator) {
return `ProductB2: Collaborating with (${collaborator.usefulFunctionA()})`;
}
}
module.exports = ProductB2;
๐️ Concrete Factories
ConcreteFactory1.js
const AbstractFactory = require('./AbstractFactory');
const ProductA1 = require('./ProductA1');
const ProductB1 = require('./ProductB1');
// ConcreteFactory1 creates ProductA1 and ProductB1
class ConcreteFactory1 extends AbstractFactory {
createProductA() {
return new ProductA1();
}
createProductB() {
return new ProductB1();
}
}
module.exports = ConcreteFactory1;
ConcreteFactory2.js
const AbstractFactory = require('./AbstractFactory');
const ProductA2 = require('./ProductA2');
const ProductB2 = require('./ProductB2');
// ConcreteFactory2 creates ProductA2 and ProductB2
class ConcreteFactory2 extends AbstractFactory {
createProductA() {
return new ProductA2();
}
createProductB() {
return new ProductB2();
}
}
module.exports = ConcreteFactory2;
๐จ๐ป Client
Client.js
// Client works with products only via their abstract interfaces
class Client {
constructor(factory) {
this.productA = factory.createProductA();
this.productB = factory.createProductB();
}
run() {
console.log(this.productB.usefulFunctionB());
console.log(this.productB.anotherUsefulFunctionB(this.productA));
}
}
module.exports = Client;
๐ index.js (Runner)
index.js
const ConcreteFactory1 = require('./ConcreteFactory1');
const ConcreteFactory2 = require('./ConcreteFactory2');
const Client = require('./Client');
console.log('Client: Testing client code with the first factory type...');
const client1 = new Client(new ConcreteFactory1());
client1.run();
console.log('\nClient: Testing the same client code with the second factory type...');
const client2 = new Client(new ConcreteFactory2());
client2.run();
๐งช Expected Output
Client: Testing client code with the first factory type...
ProductB1: The result of the product B1.
ProductB1: Collaborating with (ProductA1: The result of the product A1.)
Client: Testing the same client code with the second factory type...
ProductB2: The result of the product B2.
ProductB2: Collaborating with (ProductA2: The result of the product A2.)
๐ References
Design Patterns: Elements of Reusable Object-Oriented Software, GoF
Pages 84–85 — Abstract Factory Pattern
✅ To Run
node index.js
Ensure all files are in the same directory or adjust module paths accordingly.
SWOT
Strengths
Consistent Families: Ensures related objects work together properly by producing them from the same family.
Flexible Creation: Swap entire sets of products without changing the code that uses them.
Organized Code: Encourages modular thinking and clearer architecture.
Weaknesses
Many Classes: Can overwhelm beginners due to multiple factory and product files.
Hard Setup: Looks bulky and abstract before the payoff becomes visible.
Abstract Thinking: Requires comfort with polymorphism and interface-style coding.
Opportunities
Cross-Platform Skills: Shows how apps adapt to web/mobile/game environments with matching families.
Design Practice: Builds strong understanding of abstraction and architecture.
Team Projects: Helps students learn consistent structure across modules.
Threats
Over-Engineering: Using factories when simple constructors would do can reduce clarity.
Learning Fatigue: Too many layers can frustrate beginners.
Integration Conflicts: Mixing creational patterns can confuse which one to use.