In this lesson, we will integrate design patterns into a practical project: building a smart home system. You'll learn how to create and adapt various smart home devices using the Singleton
, Builder
, Composite
, and Abstract Factory
patterns in JavaScript. By the end, you will have a solid understanding of how these design patterns can make your smart home system more efficient, modular, and easier to maintain.
-
Singleton Pattern:
- Purpose: Ensures a class has only one instance and provides a global point of access to it.
- Steps:
- Define a class (
Singleton
) with a static method to hold the single instance. - Implement the
getInstance
method to manage instance creation and access.
- Define a class (
-
Builder Pattern:
- Purpose: Constructs complex objects step by step, providing a flexible solution for object creation.
- Steps:
- Define a class (
SmartHomeDevice
) for the object. - Create a builder class (
SmartHomeDeviceBuilder
) with methods to set object properties and a method to return the final object.
- Define a class (
-
Composite Pattern:
- Purpose: Treats individual objects and compositions of objects uniformly.
- Steps:
- Define a base class (
DeviceComponent
) for the composite structure. - Implement
LeafDevice
for individual objects andCompositeDevice
for composite objects.
- Define a base class (
-
Abstract Factory Pattern:
- Purpose: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- Steps:
- Define an abstract factory class (
SmartDeviceFactory
). - Implement concrete factories (
LightFactory
,FanFactory
) to create specific sensor and actuator objects.
- Define an abstract factory class (
To start, we implement the Singleton pattern in JavaScript to ensure that a class has only one instance and provides a global point of access to it.
JavaScript1class Singleton { 2 constructor() { 3 if (Singleton.instance) { 4 return Singleton.instance; 5 } 6 Singleton.instance = this; 7 // Additional initialization code can go here 8 } 9 10 static getInstance() { 11 if (!Singleton.instance) { 12 Singleton.instance = new Singleton(); 13 } 14 return Singleton.instance; 15 } 16} 17 18// Usage 19const singleton1 = Singleton.getInstance(); 20const singleton2 = Singleton.getInstance(); 21console.log(singleton1 === singleton2); // Output: true
The getInstance
method checks if an instance exists and creates one if it does not.
Next, we use the Builder pattern to construct complex SmartHomeDevice
objects step by step.
JavaScript1class SmartHomeDevice { 2 constructor() { 3 this.sensors = []; 4 this.actuators = []; 5 this.name = ""; 6 } 7} 8 9class SmartHomeDeviceBuilder { 10 constructor() { 11 this.device = new SmartHomeDevice(); 12 } 13 14 addSensors(sensors) { 15 this.device.sensors = sensors; 16 return this; 17 } 18 19 addActuators(actuators) { 20 this.device.actuators = actuators; 21 return this; 22 } 23 24 setName(name) { 25 this.device.name = name; 26 return this; 27 } 28 29 build() { 30 return this.device; 31 } 32} 33 34// Usage 35const builder = new SmartHomeDeviceBuilder(); 36const device = builder 37 .setName('Smart Light') 38 .addSensors(['Light Sensor']) 39 .addActuators(['LED']) 40 .build(); 41 42console.log(device.name); // Output: Smart Light
The SmartHomeDeviceBuilder
class provides methods to set device properties and construct the final SmartHomeDevice
.
Now, we implement the Composite pattern to manage individual and composite objects uniformly.
JavaScript1class DeviceComponent { 2 constructor(name) { 3 this.name = name; 4 } 5 6 operation() { 7 throw new Error('Method "operation()" must be implemented.'); 8 } 9} 10 11class LeafDevice extends DeviceComponent { 12 operation() { 13 console.log(`Leaf ${this.name} operation`); 14 } 15} 16 17class CompositeDevice extends DeviceComponent { 18 constructor(name) { 19 super(name); 20 this.children = []; 21 } 22 23 add(component) { 24 this.children.push(component); 25 } 26 27 operation() { 28 console.log(`Composite ${this.name} operation`); 29 this.children.forEach(child => child.operation()); 30 } 31} 32 33// Usage 34const leaf1 = new LeafDevice('Light'); 35const leaf2 = new LeafDevice('Fan'); 36const composite = new CompositeDevice('Room'); 37composite.add(leaf1); 38composite.add(leaf2); 39composite.operation(); 40// Output: 41// Composite Room operation 42// Leaf Light operation 43// Leaf Fan operation
The CompositeDevice
class allows us to treat individual DeviceComponent
objects uniformly as part of a composite structure.
Finally, we implement the Abstract Factory pattern to create families of related objects without specifying their concrete classes.
JavaScript1class SmartDeviceFactory { 2 createSensor() { 3 throw new Error('Method "createSensor()" must be implemented.'); 4 } 5 6 createActuator() { 7 throw new Error('Method "createActuator()" must be implemented.'); 8 } 9} 10 11class LightFactory extends SmartDeviceFactory { 12 createSensor() { 13 return new LightSensor(); 14 } 15 16 createActuator() { 17 return new LEDActuator(); 18 } 19} 20 21class FanFactory extends SmartDeviceFactory { 22 createSensor() { 23 return new TemperatureSensor(); 24 } 25 26 createActuator() { 27 return new MotorActuator(); 28 } 29} 30 31class LightSensor { 32 constructor() { 33 console.log('Light Sensor created'); 34 } 35} 36 37class LEDActuator { 38 constructor() { 39 console.log('LED Actuator created'); 40 } 41} 42 43class TemperatureSensor { 44 constructor() { 45 console.log('Temperature Sensor created'); 46 } 47} 48 49class MotorActuator { 50 constructor() { 51 console.log('Motor Actuator created'); 52 } 53} 54 55// Usage for LightFactory 56const lightFactory = new LightFactory(); 57const lightSensor = lightFactory.createSensor(); // Output: Light Sensor created 58const lightActuator = lightFactory.createActuator(); // Output: LED Actuator created 59 60// Usage for FanFactory 61const fanFactory = new FanFactory(); 62const fanSensor = fanFactory.createSensor(); // Output: Temperature Sensor created 63const fanActuator = fanFactory.createActuator(); // Output: Motor Actuator created
The LightFactory
and FanFactory
classes implement methods to create specific sensor and actuator objects.
Implementing Singleton
, Builder
, Composite
, and Abstract Factory
patterns in our smart home system allows us to create, organize, and manage devices in a structured and reusable manner. The Singleton
pattern ensures a single instance of a class, the Builder
pattern provides a flexible solution for object creation, the Composite
pattern handles both individual and composite objects uniformly, and the Abstract Factory
pattern creates families of related objects. This approach makes our smart home system more modular, flexible, and easier to maintain and extend.