[go: up one dir, main page]

0% found this document useful (0 votes)
6 views14 pages

Design Patterns

The document discusses real-world applications of design patterns, specifically the Builder, Factory, and Bridge patterns, in web and mobile development. It provides examples of each pattern's implementation to solve common problems, highlighting benefits such as improved maintainability, readability, and extensibility. Additionally, it includes a quiz to test knowledge on these design patterns.

Uploaded by

Kiên Vũ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views14 pages

Design Patterns

The document discusses real-world applications of design patterns, specifically the Builder, Factory, and Bridge patterns, in web and mobile development. It provides examples of each pattern's implementation to solve common problems, highlighting benefits such as improved maintainability, readability, and extensibility. Additionally, it includes a quiz to test knowledge on these design patterns.

Uploaded by

Kiên Vũ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 14

Real-World Applications of Design Patterns in Web & Mobile

Development

1. Builder Pattern

1.1. Web Development Example


• Context (Real-World Problem):
In a large-scale e-commerce web application, the system must send various transactional
emails such as order confirmations, password resets, and promotional notifications. Each
email may include optional sections like a personalized greeting, order summary,
promotional banner, and footer. Building these emails via string concatenation or using
multiple static templates is hard to maintain when requirements change.

• Naive Solution:
```javascript
let html = "<html><body>";
html += `<h1>Hello ${userName}</h1>`;
if (order) {
html += `<p>Order #${order.id}</p><ul>`;
order.items.forEach(item => {
html += `<li>${item.name} x${item.quantity}</li>`;
});
html += `</ul>`;
}
if (promo) {
html += `<div class='promo'>${promo.banner}</div>`;
}
html += `</body></html>`;
sendEmail(emailAddress, html);
```
Drawbacks:
- Complex conditional logic mixes content and structure.
- Hard to add new sections without editing existing code.
- Prone to HTML syntax errors.

• Builder Pattern Solution (Implementation):


```javascript
class EmailBuilder {
constructor() {
this.subject = '';
this.recipientName = '';
this.sections = [];
}
setSubject(subject) {
this.subject = subject;
return this;
}
setRecipientName(name) {
this.recipientName = name;
return this;
}
addGreeting() {
this.sections.push(`<h1>Hello ${this.recipientName}</h1>`);
return this;
}
addOrderSummary(order) {
let html = `<p>Order #${order.id}</p><ul>`;
order.items.forEach(item => {
html += `<li>${item.name} x${item.quantity}</li>`;
});
html += `</ul>`;
this.sections.push(html);
return this;
}
addPromotionalBanner(promo) {
this.sections.push(`<div class='promo'>${promo.banner}</div>`);
return this;
}
addFooter(links) {
const footerHtml = links.map(l => `<a href='${l.url}'>${l.text}</a>`).join(' | ');
this.sections.push(`<footer>${footerHtml}</footer>`);
return this;
}
build() {
let html = `<html><head><title>${this.subject}</title></head><body>`;
this.sections.forEach(sec => { html += sec; });
html += `</body></html>`;
return html;
}
}
// Usage example:
const emailHtml = new EmailBuilder()
.setSubject('Order Confirmation')
.setRecipientName('John Doe')
.addGreeting()
.addOrderSummary(orderData)
.addPromotionalBanner(promoData)
.addFooter(footerLinks)
.build();
sendEmail(userEmail, emailHtml);
```

• Analysis:
1. **Readability:** The email construction sequence is clear and linear.
2. **Maintainability:** Adding new email sections requires only adding new builder
methods.
3. **Error Reduction:** The builder ensures well-formed HTML.

1.2. Mobile Development Example


• Context (Real-World Problem):
In a Flutter mobile app, sending push notifications with optional fields such as title, body,
image URL, custom data, Android channel, and iOS badge is required. Constructing the JSON
payload with nested conditions is error-prone and hard to extend.

• Naive Solution:
```dart
Map<String, dynamic> payload = {};
payload['notification'] = {'title': 'Sale Today!', 'body': 'Up to 50% off'};
if (imageUrl != null) payload['notification']['image'] = imageUrl;
if (deepLink != null) payload['data'] = {'click_action': deepLink};
payload['android'] = {'channel_id': 'promo_channel'};
payload['apns'] = {'payload': {'aps': {'badge': 1}}};
sendPushNotification(payload);
```
Drawbacks:
- Interleaved conditional logic makes payload structure unclear.
- Hard to add new fields like action buttons.

• Builder Pattern Solution (Implementation):


```dart
class PushNotificationBuilder {
String _title = '';
String _body = '';
String? _imageUrl;
Map<String, dynamic> _data = {};
String? _androidChannel;
int? _iOSBadge;

PushNotificationBuilder setTitle(String title) {


_title = title; return this;
}
PushNotificationBuilder setBody(String body) {
_body = body; return this;
}
PushNotificationBuilder setImageUrl(String url) {
_imageUrl = url; return this;
}
PushNotificationBuilder addData(String key, dynamic value) {
_data[key] = value; return this;
}
PushNotificationBuilder setAndroidChannel(String channel) {
_androidChannel = channel; return this;
}
PushNotificationBuilder setiOSBadge(int badge) {
_iOSBadge = badge; return this;
}
Map<String, dynamic> build() {
final notificationPart = {'title': _title, 'body': _body};
if (_imageUrl != null) notificationPart['image'] = _imageUrl;

final payload = {'notification': notificationPart};


if (_data.isNotEmpty) payload['data'] = _data;
if (_androidChannel != null) payload['android'] = {'channel_id': _androidChannel};
if (_iOSBadge != null) payload['apns'] = {'payload': {'aps': {'badge': _iOSBadge}}};
return payload;
}
}
// Usage example:
final payload = PushNotificationBuilder()
.setTitle('Flash Sale!')
.setBody('50% off until midnight')
.setImageUrl('https://example.com/banner.png')
.addData('deep_link', 'app://sale')
.setAndroidChannel('sales_channel')
.setiOSBadge(1)
.build();
sendPushNotification(payload);
```
• Analysis:
1. **Readability:** Builder methods clearly express each part of the payload.
2. **Maintainability:** Adding new fields means only adding new builder methods.
3. **Error Reduction:** Payload assembly is controlled, reducing missing or misplaced keys.
2. Factory Pattern

2.1. Web Development Example


• Context (Real-World Problem):
A Node.js backend needs to support multiple database types (MySQL, PostgreSQL,
MongoDB). Writing connection logic across many modules makes the code hard to maintain
when new databases are introduced.

• Naive Solution:
```javascript
let dbConnection;
if (dbType === 'mysql') {
dbConnection = new MySQLConnection(mysqlConfig);
} else if (dbType === 'postgres') {
dbConnection = new PostgresConnection(pgConfig);
} else if (dbType === 'mongodb') {
dbConnection = new MongoConnection(mongoConfig);
}
dbConnection.connect();
```

• Factory Pattern Solution (Implementation):


```javascript
class DbConnectionFactory {
static createConnection(dbType, config) {
switch (dbType) {
case 'mysql':
return new MySQLConnection(config);
case 'postgres':
return new PostgresConnection(config);
case 'mongodb':
return new MongoConnection(config);
default:
throw new Error('Unsupported database type');
}
}
}
// Usage example:
const connection = DbConnectionFactory.createConnection(process.env.DB_TYPE, config);
connection.connect();
```
• Analysis:
1. **Decoupling:** Connection creation is centralized, reducing duplication.
2. **Extensibility:** Add a new database type by extending the factory without changing
client modules.
3. **Testability:** Mock or override the factory to inject fake connections in tests.

2.2. Mobile Development Example


• Context (Real-World Problem):
A Flutter mobile app supports multiple payment methods (Stripe, PayPal, local bank). If
checkout logic contains numerous conditional checks, the code becomes hard to read and
test.

• Naive Solution:
```dart
void processPayment(String method, double amount) {
if (method == 'stripe') {
StripeService(stripeKey).charge(amount);
} else if (method == 'paypal') {
PayPalService(paypalClientId).pay(amount);
} else if (method == 'bank') {
LocalBankService(bankToken).transfer(amount);
}
}
```

• Factory Pattern Solution (Implementation):


```dart
abstract class PaymentService {
Future<void> pay(double amount);
}
class StripeService implements PaymentService {
final String apiKey;
StripeService(this.apiKey);
@override
Future<void> pay(double amount) async {
// Call Stripe API
}
}
class PayPalService implements PaymentService {
final String clientId;
PayPalService(this.clientId);
@override
Future<void> pay(double amount) async {
// Call PayPal API
}
}
class LocalBankService implements PaymentService {
final String token;
LocalBankService(this.token);
@override
Future<void> pay(double amount) async {
// Call local bank SDK
}
}
class PaymentServiceFactory {
static PaymentService create(String method) {
switch (method) {
case 'stripe': return StripeService(stripeKey);
case 'paypal': return PayPalService(paypalId);
case 'bank': return LocalBankService(bankToken);
default: throw ArgumentError('Unsupported payment method');
}
}
}
// Usage example:
final service = PaymentServiceFactory.create(userMethod);
await service.pay(amount);
```

• Analysis:
1. **Separation of Concerns:** Checkout code depends on the `PaymentService` interface,
not concrete classes.
2. **Testability:** Mock the factory to inject fake services for unit tests.
3. **Extensibility:** Adding new payment methods requires adding a new class and
extending the factory.
3. Bridge Pattern

3.1. Web Development Example


• Context (Real-World Problem):
A web dashboard needs to support two charting libraries, D3.js and Chart.js. Chart classes
(BarChart, LineChart) focus on data, but rendering logic differs by library. Embedding
conditional checks in chart classes leads to tangled code.

• Naive Solution:
```javascript
class BarChart {
constructor(data, useD3) {
this.data = data;
this.useD3 = useD3;
}
render(selector) {
if (this.useD3) {
// D3.js rendering
} else {
// Chart.js rendering
}
}
}
```

• Bridge Pattern Solution (Implementation):


```javascript
// Implementor interface
class ChartRenderer {
drawBar(selector, data) { throw new Error('Not implemented'); }
drawLine(selector, data) { throw new Error('Not implemented'); }
}
// Concrete Implementor for D3.js
class D3Renderer extends ChartRenderer {
drawBar(selector, data) {
// Use D3 to render a bar chart
}
drawLine(selector, data) {
// Use D3 to render a line chart
}
}
// Concrete Implementor for Chart.js
class ChartJsRenderer extends ChartRenderer {
drawBar(selector, data) {
const ctx = document.querySelector(selector).getContext('2d');
new Chart(ctx, { type: 'bar', data });
}
drawLine(selector, data) {
const ctx = document.querySelector(selector).getContext('2d');
new Chart(ctx, { type: 'line', data });
}
}
// Abstraction
class BarChart {
constructor(data, renderer) {
this.data = data;
this.renderer = renderer;
}
render(selector) {
this.renderer.drawBar(selector, this.data);
}
}
// Usage example:
const renderer = new D3Renderer();
const barChart = new BarChart(data, renderer);
barChart.render('#chartContainer');
// To switch libraries: use new ChartJsRenderer()
```

• Analysis:
1. **Decoupling:** BarChart does not know library-specific code; delegates to
ChartRenderer.
2. **Flexibility:** Swap rendering engines by passing a different implementor.
3. **Extensibility:** Add new chart types or new renderers independently.

3.2. Mobile Development Example


• Context (Real-World Problem):
A Xamarin.Forms mobile app needs to show a file picker dialog on both Android and iOS.
Each platform has its own native file picker API. Using conditional compilation (#if) in the
same class becomes hard to maintain.

• Naive Solution:
```csharp
public void OpenFilePicker() {
#if __ANDROID__
var intent = new Intent(Intent.ActionGetContent);
intent.SetType("*/*");
MainActivity.Instance.StartActivityForResult(intent, PICK_FILE);
#elif __IOS__
var picker = new UIDocumentPickerViewController(new string[] { "public.data" },
UIDocumentPickerMode.Import);
picker.DidPickDocument += (sender, e) => { selectedPath = e.Url.Path; };

UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(p
icker, true, null);
#endif
}
```

• Bridge Pattern Solution (Implementation):


```csharp
public interface IFilePicker {
void OpenPicker();
string GetSelectedPath();
}
public class AndroidFilePickerImpl : IFilePicker {
public void OpenPicker() {
var intent = new Intent(Intent.ActionGetContent);
intent.SetType("*/*");
MainActivity.Instance.StartActivityForResult(intent, MainActivity.PICK_FILE);
}
public string GetSelectedPath() {
return MainActivity.Instance.SelectedFilePath;
}
}
public class iOSFilePickerImpl : IFilePicker {
private string selectedPath;
public void OpenPicker() {
var picker = new UIDocumentPickerViewController(new string[] { "public.data" },
UIDocumentPickerMode.Import);
picker.DidPickDocument += (sender, e) => { selectedPath = e.Url.Path; };

UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(p
icker, true, null);
}
public string GetSelectedPath() {
return selectedPath;
}
}
public class FilePickerBridge {
private IFilePicker _pickerImpl;
public FilePickerBridge(IFilePicker impl) { _pickerImpl = impl; }
public void Open() { _pickerImpl.OpenPicker(); }
public string GetPath() { return _pickerImpl.GetSelectedPath(); }
}
// Usage example:
IFilePicker impl;
#if __ANDROID__
impl = new AndroidFilePickerImpl();
#elif __IOS__
impl = new iOSFilePickerImpl();
#endif
var filePicker = new FilePickerBridge(impl);
filePicker.Open();
string path = filePicker.GetPath();
```

• Analysis:
1. **Decoupling:** FilePickerBridge delegates to platform-specific implementations.
2. **Flexibility:** Add new platform by creating new implementor without modifying
bridge.
3. **Maintainability:** Platform-specific code is isolated in implementor classes.
4. Quiz: Knowledge Check on Builder, Factory, Bridge Patterns
1. What is the main purpose of the Builder Pattern?

A. To enforce a single global instance of a class.

B. To allow step-by-step construction of complex objects and separate the construction


logic.

C. To group multiple related factory classes together.

D. To provide a static method for object creation.

2. How does the Factory Method differ from a Simple Factory?

A. Factory Method returns a concrete class directly, while Simple Factory returns an
interface.

B. Factory Method is defined in a subclass and can be overridden, while Simple Factory is
usually a static function.

C. They are identical in functionality.

D. Factory Method always creates singleton objects.

3. In the Bridge Pattern, what role does the Abstraction play?

A. It implements platform-specific details.

B. It defines a high-level interface and holds a reference to the Implementor.

C. It replaces the Implementor entirely.

D. It contains only static methods.

4. When should you use the Builder Pattern in web development?

A. When creating many simple objects without variations.

B. When objects have many optional parameters and you want to avoid telescoping
constructors.

C. When you need to share a single instance across modules.

D. When data is immutable.

5. What benefit does the Factory Pattern provide in mobile development?

A. Improves runtime performance on low-end devices.


B. Simplifies unit testing by allowing easy mock injection and supports adding new
implementations.

C. Reduces the number of classes written.

D. Automatically generates UI components.

6. What is the primary advantage of the Bridge Pattern?

A. It reduces the number of files in the project.

B. It decouples the Abstraction from the Implementor, allowing independent variation.

C. It forces all classes to use the same interface.

D. It enables multiple inheritance for the Abstraction.

You might also like