2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[Swift-Study] iOS 앱 개발 심화 1주차 - 디자인 패턴

2024. 5. 1. 01:29ㆍIOS/Swift-Study
SMALL

디자인 패턴이란?

디자인 패턴은 소프트웨어 개발에서 자주 발생하는 문제에 대한 해결책을 재사용 가능한 형태로 정리한 것

개발자들 간의 공통 언어를 제공하여 효율적인 의사소통을 도움

아키텍쳐와의 차이

규모와 적용 범위

아키텍처

시스템 전체의 구조와 레이아웃을 다루며,

대규모의 시스템에서 적용 아키텍처는 시스템의 주요 구성 요소와 이들 간의 관계,

데이터 흐름, 성능 최적화, 보안 정책 등 정의

디자인 패턴

보통 클래스나 객체의 작은 규모의 디자인 문제를 해결하며,

개별 컴포넌트나 모듈 내의 상세한 구조와 상호 작용을 다룸

목적

아키텍처

시스템의 기본 구조를 정의하여 시스템의 전체적인 모습을 관리,

시스템의 성능, 확장성, 유지보수성을 보장

디자인 패턴

클래스나 객체 간의 상호 작용을 구조화하여

재사용 가능한 솔루션을 제공

정의(적용) 시점

아키텍처 시스템을 설계할 때 고려,

초기 개발 단계에서 정의

디자인 패턴

클래스나 객체의 작은 규모의 구조를 설계할 때 고려,

주로 구현 단계에서 적용

Delegate Pattern

한 객체가 다른 객체의 대리자(delegate)가 되는 디자인 패턴

다른 객체의 이벤트나 데이터를 처리하는 방식으로 구현,

객체 간의 결합도를 낮추고,

유연하고 확장 가능한 코드 작성 가능

예시

MyClass 객체를 사용

MyDelegate 프로토콜을 채택한 delegate 객체를 가짐

 

 

delegate 객체의 didSomething 매서드를 호출

객체의 이벤트나 데이터 처리

 

MyDelegateClass는 MyDelegate를 구현한 클래스

didSomething 메서드에서 작업 수행

 

myObject는 delegateObject를 대리자로 설정

doSomething 매서드 호출

 

MyClass는 MyDelegateClass와 독립적으로 작성되었기에
확장성 높은 코드 작성 가능

 

해당 Delegate 프로토콜을 클래스 전용 프로토콜(class-only protocol)로 만들기 위해
AnyObject를 상속받음

MyDelegate 상속받는 구현체가 값 타입이 아닌,

참조타입인 클래스만 해당 프로토콜을 채택할 수 있음을 명시적으로 나타냄

// 프로토콜 선언
protocol MyDelegate: AnyObject {
    func didSomething()
}

// 객체 선언
class MyClass {
    weak var delegate: MyDelegate?
    
    func doSomething() {
        // 작업 수행
        delegate?.didSomething()
    }
}

// 대리자 객체 선언
class MyDelegateClass: MyDelegate {
    func didSomething() {
        // 작업 수행
    }
}

// 사용 예시
let myObject = MyClass()
let delegateObject = MyDelegateClass()

myObject.delegate = delegateObject
myObject.doSomething()

특징

다중 상속을 지원하지 않는 Swift에서 Protocol을 사용하여 다중 상속과 유사한 구현 가능

delegate는 자체적으로 이벤트를 발생시키지 않음

다른 객체에서 호출할 때 매서드를 제공, 해당 객체에서 발생한 이벤트나 데이터 처리

UITableView, UICollectionView와 상호작용하기 위해,

delegate라는 개념을 사용하는, UICollectionViewDelegate, UITableViewDelegate가 있음


Observer Pattern

하나의 객체가 변경되었을 때,

해당 객체에 의존하는 다른 객체들에게 자동으로

알림을 보내어 변경 사항을 적용하는 디자인 패턴

예시

Observer구현

Subject 클래스는 관찰 대상이 되는 객체

Observer 프로토콜은 변경 사항을 적용할 객체들이 구현해야하는 메서드들을 가짐

addObserver, removeObserver, notifyObservers 함수를 사용하여

의존 관계를 설정하고 변경 사항을 적용.

protocol Observer: AnyObject {
    func update(subject: Subject)
}

class Subject {
    var observers = [Observer]()
    
    func addObserver(_ observer: Observer) {
        observers.append(observer)
    }
    
    func removeObserver(_ observer: Observer) {
        if let index = observers.firstIndex(where: { $0 === observer }) {
            observers.remove(at: index)
        }
    }
    
    func notifyObservers() {
        for observer in observers {
            observer.update(subject: self)
        }
    }
}

class ConcreteObserver: Observer {
    func update(subject: Subject) {
        // 변경 사항을 적용하는 코드 작성
    }
}

let subject = Subject()
let observer = ConcreteObserver()
subject.addObserver(observer)

// subject가 변경되었을 때, observer에게 알림을 보냄
subject.notifyObservers()

Notification Center 사용

NotificationCenter 객체를 사용하여 MyNotification 이름의 Notification을 등록하고 발송

addObserver 를 사용하여 Notification을 등록

post 매소드를 사용하여 Notification 발송

handleNotification 에서 Notification 처리

// Notification을 등록하는 코드
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: Notification.Name("MyNotification"), object: nil)

// Notification을 발송하는 코드
NotificationCenter.default.post(name: Notification.Name("MyNotification"), object: nil)

// Notification을 처리하는 코드
@objc func handleNotification(_ notification: Notification) {
    // Notification을 처리하는 로직을 작성
}

특징

객체 간의 결합도를 낮춰 유연성을 높임

변경사항을 적용할 객체들을 동적으로 관리할 수 있음

변경사항을 적용할 객체 간의 직접 호출을 피함

객체의 변경 사항에 대한 응답을 캡슐화하여 코드의 유지보수를 쉽게 함

주의점

통제 불가능

Observer 패턴을 사용시 한번 보내진 메시지는 모든 관찰자에게 전달, 이를 제어하거나 중단할 방법이 없음

디버깅 어려움

Observer 패턴은 메시지의 소스와 대상을 느슨하게 연결하여,

누가 이벤트를 보냈는지 또는 누가 이벤트를 수신했는지 파악하기 어려움.

순서 보장 불가

Observer 패턴을 통해 보내진 메시지는,

어떤 순서로 처리될지 보장하지 않음

메모리 누수

Observer 패턴을 사용할 때 객체가 제거될 때

관찰자 목록에서 해당 객체를 제거하는 것이 중요

밀접한 결합 Observer 패턴이나 broadcast 방법을 사용하면

이벤트 소스와 이벤트 핸들러 간에 밀접한 결합이 발생할 수 있음


Singleton Pattern

하나의 객체 인스턴스만 생성하고,

이를 전역에 제공하는 디자인 패턴

앱 전역에 공유해야하는 상태나 기능을 효율적으로 관리 가능

예시

shared 정적 상수를 통해 전역에 단 하나인 인스턴스 생성

생성자는 private로 선언하여 외부에서 인스턴스 생성 방지

doSomething() 함수를 앱 전역에서 사용 가능

// Singleton 클래스 선언
class MySingleton {
    static let shared = MySingleton()
    
    private init() {}
    
    func doSomething() {
        // 작업 수행
    }
}

// 사용 예시
MySingleton.shared.doSomething()

특징

전역에서 단 하나의 인스턴스만 생성되어, 객체 간의 의존성을 줄임

앱 전역에 공유해야하는 상태나 기능(Database, Userdata 등)을 효율적으로 관리


주의점

전역 상태

'싱글톤'은 애플리케이션의 전역 상태를 관리하므로 프로그램의 예측이 어려움

싱글톤이 유지해야 하는 상태는 가능한 최소한으로 제한해서 관리

테스트하기 어려움

'싱글톤'이 전역 상태를 가지고 있으므로, 테스트하기 어려움 

테스트 케이스 간에 상태가 공유되므로,

하나의 테스트 케이스가 다른 테스트 케이스에 영향을 줄 수 있음

이를 피하기 위해, 테스트할 수 있는 방법을 고려하여 싱글톤을 설계해야 함

결합도 증가

싱글톤은 코드 간의 높은 결합도를 초래할 수 있음

클래스가 직접 싱글톤 인스턴스에 의존하게 되면,

그 클래스는 싱글톤과 떼어낼 수 없는 결합을 형성하게 됨

이를 완화하기 위해, 의존성 주입과 같은 기법을 사용하여

싱글톤에 대한 의존성을 줄일 수 있음

728x90