-
Swift4: Protocol Oriented Programming - 7.스위프트에서 디자인 패턴 적용 - (1) 생성 패턴POP 2021. 2. 19. 03:10
7장 스위프트에서 디자인 패턴 적용
디자인 패턴
공통의 소프트웨어 개발 문제를 확인하고 이를 다루기 위한 전략을 제공. 여러 개발적 문제를 해결하는 방법들이 입증되어 있기 때문에 개발 프로세스 속도를 높일 수 있다. 또한, 유지하기 쉬운 일관된 코드를 얻을 수 있다. 코드와 구현한 디자인 패턴을 문서로 만들게 될 경우, 다른 개발자가 해당 코드가 어떠한 일을 하는지 이해하는데 도움을 줄 수도 있다.
- 코드의 재사용
- 유연성
문제를 수정하려고 하기 전에 수정하고자 하는 문제를 먼저 확인하는 것이 좋은 방법. 해결하고자 하는 문제에 대한 디자인 패턴이 없을 때는 디자인 패턴에 관한 지식과 근본적인 철학을 이용할 수 있을 것이다.
- 범주
- 생성 패턴: 객체의 생성을 지원
- 구조 패턴: 타입과 객체 컴포지션과 관련
- 행위 패턴: 타입 간의 소통과 관련
생성 패턴
객체를 어떻게 생성하는지 다루는 디자인 패턴. 어떤 구체적 타입이 생성돼야 하는지에 대한 정보 캡슐화하고, 이러한 타입의 인스턴스가 어떻게 생성되는지 숨기는 것이 기본 개념이다. 생성 로직을 한 지점에 캡슐화돼 있기 때문에 로직 변경에 용이하다.
-
추상 팩토리 패턴
구체적 타입을 명시하지 않고, 관련된 객체를 생성하기 위한 인터페이스를 제공
-
빌더 패턴
복잡한 객체의 생성과 표현을 서로 분리해 유사한 타입을 생성하기 위해 동일한 프로세스가 사용될 수 있도록 한다.
-
팩토리 메소드 패턴
객체를 어떻게 생성하는지에 대한 근본적 로직을 노출하지 않고 객체를 생성
-
프로토타입 패턴
이미 존재하는 객체를 복사
-
싱글턴 패턴
애플리케이션 주기 동안 오직 하나인 클래스 인스턴스 허용
싱글턴 패턴
싱글턴 패턴은 오용하기 쉬우며, 애플리케이션 내에 어느 지점에서나 객체를 변경시키는 정적 상태를 제공하기 때문에 논란이 많다. 숨겨진 의존성과 강한 결합을 불러 일으키기도 한다.
싱글턴은 애플리케이션 주기에 클래스 인스턴스를 오직 하나만 허용한다. 싱글턴 패턴을 사용하면 모든 페이지가 인스턴스를 유지하는 것을 강제하지 않으면서, 언제가 인스턴스에 접근하는 것이 가능하다. 다른 페이지로 넘어가는 경우에도 재연결 필요 없이 연결을 유지한다.
-
문제
애플리케이션 주기 동안 유일한 인스턴스가 있어야 함. 내부나 외부 리소스의 집중적 관리가 필요한 경우나 접근 할 수 있는 단일 정적 포인트가 필요한 경우.
타입의 내부가 변경되지 않고, 어떠한 기능을 수행하는 메소드만 가지고 있는 경우 때때로 사용하기 좋다.
-
해결
클래스 상수를 이용. 클래스 상수에 처음 접근할 때, 클래스의 단일 인스턴스가 생성되고, 프라이빗 생성자로 추가적 인스턴스화를 막는다. 애플리케이션 내내 단일 인스턴스에 접근하는데에 클래스 상수를 이용한다.
싱글턴 패턴은 참조 타입에서만 구현할 수 있기 때문에 클래스를 사용한다.
class MySingleton { static let sharedInstance = MySingleton() // 정적 상수 var number = 0 private init() {} // 추가 인스턴스화 방지 }
싱글턴 패턴은 애플리케이션 라이프 사이클 내내 인스턴스가 오직 하나만 존재해야하는 구체적 요구 사항이 있기 전까지는 사용하지 말아야 한다. 단순 편의를 위해 사용하고 있다면 잘못 사용하고 있을 확률이 높다.
빌더 디자인 패턴
복잡한 객체의 생성을 도우면서, 어떻게 이러한 객체들을 생성하는지에 대한 프로세스를 강제한다. 복잡한 타입으로부터 생성 로직을 분리, 다른 타입을 추가한다. 타입의 서로 다른 결과물을 생성하는데 동일한 생성 프로세스를 사용하게 해준다.
-
문제
타입의 인스턴스가 설정 가능한 여러 값을 요구하는 문제. 클래스의 인스턴스를 생성할 때 설정 옵션을 추가할 수도 있지만, 타입의 인스턴스를 생성할 때마다 모든 설정 가능한 옵션을 설정하는데 많은 양의 코드가 필요하다.
-
해결
builder로 알려진 중개자를 이용.
- 원래의 복잡한 객체를 설정하는 정보를 가진 여러가지의 빌더 타입을 갖는 방법
- 모든 설정 가능한 옵션을 기본 값으로 설정하는 단일 빌더 타입을 이용하는 방법. 필요할 시에 옵션 값 변경
- 여러 빌더 타입을 이용하는 경우
protocol BurgerBuilder { var name: String {get} var patties: Int {get} var bacon: Bool {get} var cheese: Bool {get} var pickles: Bool {get} var ketchup: Bool {get} var mustard: Bool {get} var lettuce: Bool {get} var tomato: Bool {get} } struct HamburgerBuilder: BurgerBuilder { let name = "Burger" let patties = 1 let bacon = false let cheese = false let pickles = true let ketchup = true let mustard = true let lettuce = false let tomato = false } struct Burger { init(builder: BurgerBuilder) { self.name = builder.name self.patties = builder.patties self.bacon = builder.bacon self.cheese = builder.cheese self.pickles = builder.pickles self.ketchup = builder.ketchup self.mustard = builder.mustard self.lettuce = builder.lettuce self.tomato = builder.tomato } }
위와 같은 빌더 패턴을 이용하면 빌더 클래스 내부에서 값을 설정해 주기 때문에 각 버거 타입의 프로퍼티 값을 잘못 설정하는 오류를 방지해준다.
- 단일 빌더 타입을 이용하는 방법
모든 설정 가능한 값을 기본 값으로 설정한 단일 빌더 타입을 갖는다. 각 값들은 필요에 따라 변경될 수 있다. 이 방법은 기존 코드와 통합하기 쉬워 오래된 코드를 업데이트할 때 사용하기 좋다.
struct SingleBurgerBuilder { var name = "Burger" var patties = 1 var bacon = false var cheese = false var pickles = true var ketchup = true var mustard = true var lettuce = false var tomato = false mutating func setPatties(choice: Int) { self.patties = choice } mutating func setBacon(choice: Bool) { self.bacon = choice } mutating func setCheese(choice: Bool) { self.cheese = choice } mutating func setPickles(choice: Bool) { self.pickles = choice } mutating func setKetchup(choice: Bool) { self.ketchup = choice } mutating func setMustard(choice: Bool) { self.mustard = choice } mutating func setLettuce(choice: Bool) { self.lettuce = choice } mutating func setTomato(choice: Bool) { self.tomato = choice } func buildBurgerOld(name: String) -> BurgerOld { return BurgerOld(name: name, patties: patties, bacon: bacon, cheese: cheese, pickles: pickles, ketchup: ketchup, mustard: mustard, lettuce: lettuce, tomato: tomato) } }
팩토리 메소드 패턴
생성할 정확한 타입을 명하지 않으면서 객체의 인스턴스를 생성하는 메소드를 사용. 생성할 타입을 런타임에 선택하게 해 준다.
-
문제
하나의 프로토콜을 따르는 여러 타입이 있고, 인스턴스화하기 위해 적합한 타입을 런타임에 결정해야 하는 문제
-
해결
팩토리 메소드 패턴은 하나의 메소드에서 인스턴스화 오형을 선택하는 로직을 캡슐화한다. 이 메소드는 프로토콜(혹은 클래스)를 코드로만 노출한다. 메소드를 호출하고, 특정 타입을 어떻게 선택해야하는지는 드러내지 않는다.
func getValidator(alphaCharacters: Bool, numericCharacters: Bool) -> TextValidation? { if alphaCharacters && numericCharacters { return AlphaNumericValidation.sharedInstance } else if alphaCharacters && !numericCharacters { return AlphaValidation.sharedInstance } else if !alphaCharacters && numericCharacters { return NumericValidation.sharedInstance } else { return nil } }
위 메소드는 불리언 타입으로 두 개의 인자를 받고 해당 인자 값의 결과에 따라 필요한 유효성 타입을 반환한다. 이 팩토리 메소드 패턴을 사용하여 문장 유효성 타입이 어떻게 선택되는지에 대한 모든 로직이 하나의 함수로 캡슐화 되었다. 로직에 변경이 있더라도 위 함수 내부의 내용만 바꾸면 되고, 사용되는 부분에서의 변경은 필요하지 않다.
코드
참고 문헌
https://book.naver.com/bookdb/book_detail.nhn?bid=14107671
'POP' 카테고리의 다른 글
Swift4: Protocol Oriented Programming - 7.스위프트에서 디자인 패턴 적용 - (3) 행위 패턴 (0) 2021.02.24 Swift4: Protocol Oriented Programming - 7.스위프트에서 디자인 패턴 적용 - (2) 구조 패턴 (0) 2021.02.24 Swift4: Protocol Oriented Programming - 6.프로토콜지향 프로그래밍 (0) 2021.02.17 Swift4: Protocol Oriented Programming - 5.객체지향 프로그래밍 (0) 2021.02.16 Swift4: Protocol Oriented Programming - 4.제네릭 (0) 2021.02.15