Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

sanichdaniel의 iOS 개발 블로그

객체지향 디자인 패턴 본문

카테고리 없음

객체지향 디자인 패턴

sanich8355 2020. 10. 8. 17:46

목차

Strategy

Command

Adapter

Proxy

Facade

Template Method

Decorator

Factory

Abstract Factory

Mediator

Compostie

Strategy

동작(Strategy)을 클래스로 캡슐화하고 런타임에 갈아끼울수 있도록 하는것

동작이 사용하는 코드로부터 캡슐화 되어있다.

새로운 동작을 쉽게 추가할수 있다. 

 

Context는 새로 추가되는 동작에 대해 몰라도 된다.

아래 예시에서 Restaurant는 Cook 구현체에 대해 닫혀있다.

import Foundation

protocol Cook {
    func cookFood()
}

class KoreanCook: Cook {
    func cookFood() {
        print("김치찌개")
    }
}

class JapaneseCook: Cook {
    func cookFood() {
        print("스시")
    }
}

class Restraunt {
    var strategy: Cook?
    
    func setCookStrategy(cook: Cook) {
        self.strategy = cook
    }
    
    func executeStrategy() {
        strategy?.cookFood()
    }
}

enum Region {
    case Korea
    case Japan
}
var region = Region.Korea

let restraunt = Restraunt()

switch region {
case .Korea:
    restraunt.setCookStrategy(cook: KoreanCook())
case .Japan:
    restraunt.setCookStrategy(cook: JapaneseCook())
}

restraunt.executeStrategy()

Command

Strategy 패턴과 유사한데, Strategy 패턴은 갈아끼우나

Command 패턴은 실행될 기능을 객체로 캡슐화함으로서,

기능의 실행을 요구하는 호출자와 실제 기능을 수행하는 수신자 사이의 의존성을 제거한다.

 

커맨드 패턴 활용으론 어레이에 요청들을 넣어두고 돌아가면서 실행시키는 방식이 있다

또는 히스토리를 트랙킹할때도 쓰인다. 

 

출처

refactoring.guru/design-patterns/command


Adapter

요리사들이 일하는 식당에서 초록색 요리사들과 빨간색 베이커 사람이 있습니다.

매니저는 누구한테는 요리해라, 누구한테는 빵을 구워달라할 정신이 없습니다.

이때 파티셰에게 어답터를 달아주면, 베이커에게 요리를 부탁하면 제과를 하게 되는것.

 

한 클래스를 특정 프로토콜을 따르도록 하고 싶을때, 어답터 클래스를 만들어서 따르도록 해준다.

프로토콜때문에 호환이 안되던 클래스를 연결해서 쓸 수 있다.

protocol Cook {
    func cook()
}

class CookAdapter: Cook {
    let bake: Bake
    
    init(bake: Bake) {
        self.bake = bake
    }
    
    func cook() {
        bake.bake()
    }
}

protocol Bake {
    func bake()
}

class Bakery: Bake  {
    func bake() {
        print("빵 구웠습니다")
    }
}

let cookAdapter = CookAdapter(bake: Bakery())
cookAdapter.cook()
// "빵 구웠습니다"

 

출처

www.youtube.com/watch?v=lJES5TQTTWE

Proxy

실제 객체를 생성하기에는 메모리를 많이 차지하거나, 인터넷에서 받아와야해서 시간을 차지해서 부담이 되는 경우, 그 클래스의 Proxy, 대리자 클라스를 따로두어 가벼운 일은 Proxy 객체가 처리하도록 하고, 무거운 작업을 해야하는 경우에 실제 객체를 생성하는것

 

유튜브처럼 영상들 제목이 띄워지고 마우스를 올리면 프리뷰가 재생되도록 하고싶다.

제목을 나타내는건 가볍지만 프리뷰 보여주는건 무겁다. 

제목을 보여줄때는 프록시를 통해 보여주고

실제로 프리뷰 보여달라는 요청이 들어왔을때 실제 객체를 생성하는것

실제로 필요할때만 객체를 생성하기에 효율적이다!

protocol Thumbnail {
    func showTitle()
    func showPreview()
}

class RealThumbnail: Thumbnail {
    func showTitle() {
        print("제목을 보여줘")
    }
    
    func showPreview() {
        print("프리뷰를 보여줘")
    }
}

class ProxyThumbnail: Thumbnail {
    var realThumbnail: RealThumbnail? = nil
    
    func showTitle() {
        print("제목을 보여줘")
    }
    
    func showPreview() {
        if realThumbnail == nil {
            realThumbnail = RealThumbnail()
        }
        realThumbnail?.showPreview()
    }
}

let proxyThumbnail = ProxyThumbnail()
proxyThumbnail.showPreview()

Adapter 패턴은 다른 인터페이스로 래핑을 하나, Proxy는 같은 인터페이스를 사용한다. 

 

출처

refactoring.guru/design-patterns/proxy#:~:text=Proxy%20is%20a%20structural%20design,through%20to%20the%20original%20object. 

Facade

건물의 정면 이라는 뜻으로, 복잡한 코드를 간단한 인터페이스로 제공해주는걸 말합니다.

단점으로 Facade 객체가 모든 코드에 의존하는 God Object가 될 수 있다

 

출처

refactoring.guru/design-patterns/facade

Template-method

어떤 일을 수행할때 공통된 절차가 있을때 효율적이다.

templeteMethod() 에서 알고리즘 호출 순서, 조건을 명시한다. 

protocol SetupUI {
    func templateMethod()
    
    func addSubviews()
    func autoLayout()
    func draw()
    
    func hook1() { }
}

extension SetupUI {
    func templateMethod() {
        addSubviews()
        autoLayout()
        hook1()
        draw()
    }
}

class MyViewController: UIViewController, SetupUI {
    func addSubviews() {
        print("add subview")
    }
    
    func autoLayout() {
        print("auto Layout")
    }
    
    
    func draw() {
        print("draw")
    }
    
    func hook1() { }
}

UIViewController의 생명주기 함수들도 template method와 유사하다고 할 수 있다

 

장점

- 클라이언트에게 알고리즘 특정 부분을 변경하게 하고 싶을때 유용하다

- 반복되는 코드를 프로토콜 extension 으로 몰아넣을수 있다

 

출처

refactoring.guru/design-patterns/template-method

 

Decorator

객체에 새로운 동작을 추가하고 싶을때, Wrapper 객체 안에 넣어두어 추가하는 방식.

runtime에 동적으로 동작을 확장할수 있다.

만약 객체에 여러 피쳐가 추가되어야하고, 그 피쳐들의 모든 조합들을 대응해야할 경우 유용하다. 

 

예시 1 

문명의 육각형 땅을 만들어야하는데, 땅에 기름, 금, 은이 있을수 있다.

모든 경우를 다 상속하기보다는, 상항에 따라 Decorate 하기

var crazyMix = GoldDec(SilverDec(OilDec(Square())))

 

예시2 

Notifier을 만들어야한다.

Notifier은 SNS, Facebook, Slack 모든 조합의 Noti를 다 받아야한다.

이때 아래처럼 모든 경우를 상속받는다면 자식클래스 개수가 폭발할것이다. 

여러 Decorator 클래스를 만들고, 오른쪽 사진처럼 상황별로 Decorator을 계속 입힌다.

protocol Notifier {
    func send() -> String
}

class ConcreteNotifier: Notifier {
    func send() -> String {
        return ""
    }
}

class Decorator: Notifier {
    var notifier: Notifier
    
    init(notifier: Notifier) {
        self.notifier = notifier
    }
    
    func send() -> String {
        return notifier.send()
    }
}

class SlackDecorator: Decorator {
    override func send() -> String {
        return "Slack " + super.send()
    }
}

class FaceBookDecorator: Decorator {
    override func send() -> String {
        return "FaceBook " + super.send()
    }
}

class SNSDecorator: Decorator {
    override func send() -> String {
        return "SNS " + super.send()
    }
}

// 런타임에 동적으로 decorator 동작 추가 가능
let decorator = SNSDecorator(notifier: SlackDecorator(notifier: FaceBookDecorator(notifier: ConcreteNotifier())))
print(decorator.send())

 

장점

- 자식 클래스 없이 

- 책임을 런타임에 추가하거나 지울수 있다 

- 여러 조합을 대응해야하는경우 유용하다

 

단점

- wrappper들의 stack에서 특정 wrapper을 지우기 어렵다

 

Composite Decorator 차이

Decorator은 Composite과 유사하나 오직 하나의 자식 component만 가진다

Decorator은 새로운 책임을 추가하지만, Composite은 자식들의 결과를 더할뿐이다.

 

출처

refactoring.guru/design-patterns/decorator/swift/example

Factory Method

객체를 생성하는 프로토콜을 정의한다. 프로토콜을 채택한곳에서 어떤 클래스의 객체를 만들지 결정한다. 

protocol Product { }
class Toy: Product { }

protocol FactoryType {
    func factoryMethod() -> Product
}

class Factory: FactoryType {
    func factoryMethod() -> Product {
        return Toy()
    }
}

Abstract Factory Method

아래 예시에서 Button, Label은 연관되는 Family Products 이다.

AbstractFactory에서는 연관된 family products를 생성하는 메서드를 정의한다

MacFactory는 ModernButton과 ModernLabel을 생성하고

WinFactory는 OldButton과 OldLabel을 생성한다. 

protocol Button { }
class ModernButton: Button { }
class OldButton: Button { }

protocol Label { }
class ModernLabel: Label { }
class OldLabel: Label { }

protocol AbstractFactory {
    func createButton() -> Button
    func createLabel() -> Label
}

class MacFactory: AbstractFactory {
    func createButton() -> Button {
        return ModernButton()
    }
    
    func createLabel() -> Label {
        return ModernLabel()
    }
}

class WinFactory: AbstractFactory {
    func createButton() -> Button {
        return OldButton()
    }
    
    func createLabel() -> Label {
        return OldLabel()
    }
}

let abstractFactory: AbstractFactory = MacFactory()
let button = abstractFactory.createButton()
let label = abstractFactory.createLabel()

var factory: AbstractFactory
if config.OS == "Windows" {
    factory = WinFactory()
} else if (config.OS == "Mac") {
    factory = MacFactory()
}

let app = Application(factory)

 

 

단점

여러 인터페이스와 클래스들을 추가하게 되며 복잡해진다. 

 

Factory Method vs Abstract Factory 

Factory Method:

오직 하나의 객체만 생성

클라이언트에게 객체를 생성하는 메서드를 노출한다

Abstract Factory:

연관, 관련된 객체들을 생성한다.

관련된 객체들을 생성하는 Factory method를 노출한다 

 

swift에서 프로토콜로 Factory 패턴을 구현했다면, FactoryMethod는 한개의 객체, Abstract Factory는 연관된 객체들 생성으로 이해하면 될것 같다.

// 오직 한개의 객체 생성
protocol FactoryMethod {
    func createA() -> A
}

// 연관된 객체들 생성
protocol AbstractFactory {
    func createA() -> A
    func createB() -> B
}	

 

출처

refactoring.guru/design-patterns/abstract-factory

stackoverflow.com/questions/5739611/what-are-the-differences-between-abstract-factory-and-factory-design-patterns

Mediator 

객체들끼리의 의존이 복잡할때 Mediator (중계자) 객체를 두어 
M : N 관계를 M : 1 로 변경해주기

Notification Center을 생각하자 

Composite

컴퓨터의 폴더 시스템을 떠올리면 된다.

폴더안에는 파일이 들어갈수 있지만 또다른 폴더가 들어갈수 있다. 

포함하는것들 포함되는것들을 같은 방식으로 다루고 싶을때 (Tree) Composite 패턴을 사용한다.

클라이언트는 전체와 부분을 구분하지 않고 동일한 인터페이스로 접근 가능 

 

ex) 만약 Product와 Box를 다룬다고 생각해보자. Box는 여러 Products를 가질수 있고, Box를 가질수 있다. 

만약 모든 Product의 가격을 알고 싶다면 어떻게 해야할까?

Product와 Box에 공통으로 적용되는 인터페이스를 만든다

Product는 그냥 가격을 리턴하고, Box는 자신이 가진것들을 돌아가면서 가격을 recursive하게 물어본다. 

 

  1. Component 인터페이스는 트리의 모든 원소에게 공통으로 적용되는 동작

  2. Leaf는 기본이 되는 원소로, 대게 실제 해야하는 동작을 수행한

  3. Container (aka composite) 는 자식을 가지는 원소이다. Container 또는  Leaf를 자식으로 가질수 있다. 자식들과 Component 인터페이스를 통해서만 소통한다. Container는 모든 요청을 자식들에게 대리하고, 클라이언트에게 결과값을 전달한다

iOS에서 Composite 패턴 예시

1) UIView

- View는 다른 View 안에 포함되어 View 계층을 만든다.

- UIView는 자식으로 UIView의 어레이인 subviews를 가진다

- UIView는 또한 UILabel, UIButton 같은 leaf, 또는 UIButton, UITextField 같은 composites를 가진다. (UIButton, UITextField는 내부에 또 UIView를 가질수 있기에)

- UIStackView, UITableView

 

2) UIViewController

- ContentVC: 보여지는 부분. (Leaf)

- ContainerVC: 다른 VC들을 포함한다. (Composite)

 

출처

medium.com/flawless-app-stories/app-architecture-and-object-composition-in-swift-c9101a9e37e3

refactoring.guru/design-patterns/composite