Architecture And Design Patterns Part 2 (IOS)
Structural Design Pattern:-
This design pattern deals with the composition of objects and classes to form larger structures. means how classes and objects are composed to form larger structures. They help to organize and structure classes and objects to achieve a specific goal or solve a particular problem. means if one part of a system changes, the entire system does not need to change
There are many types of structural design patterns like Adapter, Decorators, Facade, Bridge, Composite and others, I will explain some of them
- Adapter:- The Adapter pattern is a structural design pattern that allows two incompatible objects to work together. it’s like a bridge between two incompatible interfaces.
// Third-party library class
class OtherUser {
var fullName: String
var age: Int
init(fullName: String, age: Int) {
self.fullName = fullName
self.age = age
}
func getUserDetails() -> String {
return "\(fullName), \(age) years old"
}
}
// Your app's user model
class MyUser {
var firstName: String
var lastName: String
var age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
func getUserInfo() -> String {
return "\(firstName) \(lastName), \(age) years old"
}
}
// Adapter class
class UserAdapter {
private var otherUser: OtherUser
init(otherUser: OtherUser) {
self.otherUser = otherUser
}
func getUser() -> MyUser {
let nameComponents = otherUser.fullName.split(separator: " ")
let firstName = String(nameComponents.first ?? "")
let lastName = String(nameComponents.last ?? "")
return MyUser(firstName: firstName, lastName: lastName, age: otherUser.age)
}
}
let otherUser = OtherUser(fullName: "Raghvendra Singh", age: 30)
let adapter = UserAdapter(otherUser: otherUser)
let user = adapter.getUser()
print(user.getUserInfo())
The Output will be
Raghvendra Singh, 35 years old
In the above example, I have a third-party user “Otheruser” and my app user “MyUser” and I have created an adapter that will adapt the third-party “OtherUser” user and return my app user “MyUser”. The adapter acts like a bridge
2. Facade:- This design pattern provides a simplified interface to a complex system. it hides the complexity of the system and exposes only necessary functionalities.
import AVFoundation
class AudioPlayerSubsystem {
private var player: AVAudioPlayer?
func loadAudioFile(named fileName: String) {
if let url = Bundle.main.url(forResource: fileName, withExtension: "mp3") {
do {
player = try AVAudioPlayer(contentsOf: url)
player?.prepareToPlay()
} catch {
print("Error loading audio file: \(error.localizedDescription)")
}
}
}
func play() {
player?.play()
}
func pause() {
player?.pause()
}
func stop() {
player?.stop()
player?.currentTime = 0
}
}
class AudioPlayerFacade {
private let audioPlayerSubsystem = AudioPlayerSubsystem()
func playAudio(named fileName: String) {
audioPlayerSubsystem.loadAudioFile(named: fileName)
audioPlayerSubsystem.play()
}
func pauseAudio() {
audioPlayerSubsystem.pause()
}
func stopAudio() {
audioPlayerSubsystem.stop()
}
}
// Usage
let audioPlayerFacade = AudioPlayerFacade()
audioPlayerFacade.playAudio(named: "sample")
audioPlayerFacade.pauseAudio()
audioPlayerFacade.stopAudio()
In the above example, I have created AudioPlayerFacade to display a simplified system and it hides the complexity AudioPlayerSubsystem
3. Decorator:- This design Pattern allows behaviour to be added to individual objects, dynamically, without affecting the behaviour of other objects from the same class.
protocol TextComponent {
func getText() -> String
}
class PlainText: TextComponent {
private var text: String
init(text: String) {
self.text = text
}
func getText() -> String {
return text
}
}
class TextDecorator: TextComponent {
private var decoratedText: TextComponent
init(decoratedText: TextComponent) {
self.decoratedText = decoratedText
}
func getText() -> String {
return decoratedText.getText()
}
}
class SpellCheckDecorator: TextDecorator {
override func getText() -> String {
let text = super.getText()
return performSpellCheck(on: text)
}
private func performSpellCheck(on text: String) -> String {
// Implement spell checking logic
return text + " [Spell Checked]"
}
}
class GrammarCheckDecorator: TextDecorator {
override func getText() -> String {
let text = super.getText()
return performGrammarCheck(on: text)
}
private func performGrammarCheck(on text: String) -> String {
// Implement grammar checking logic
return text + " [Grammar Checked]"
}
}
// Usage
let plainText = PlainText(text: "This is a sample text")
let spellCheckedText = SpellCheckDecorator(decoratedText: plainText)
let grammarCheckedText = GrammarCheckDecorator(decoratedText: spellCheckedText)
print(grammarCheckedText.getText())
In the above example I have created TextDecorator class that will used by SpellCheckDecorator and GrammarCheckDecorator to perform specific task like grammar check and spelling check