본문 바로가기
YAGOM CAREER STARTER

[TIL] 20230102: MVC, Initialization, 쥬스 메이커, 연결 리스트

by Rhode 2023. 1. 3.

본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.


MVC & SW Architecture/Design Pattern

SW Architecture와 SW Design Pattern의 개념은 어디서부터 출발했을까?

객체 지향적인 성격에서 출발

건물 짓는 방식을 SW에서 채택

 

두 개념은 왜 필요할까?

통일성: 모든 이해관계자들이 이해하고 소통하는 데에 있어서 기본 제공

확장성: 모델들을 잘 정의해 놓을 시 재사용이나 확장에의 용이

 

두 개념의 차이점?

아키텍쳐 디자인
디자인보다 근본적이고 구조적인 과정 코드 레벨의 공통성
large scale medium scale
What we are building How we are building
전체적인 시스템에 관한 것 개별 모듈이나 컴포넌트에 관한 것
structure implement

 

애플이 이야기하는 MVC 구조는 (무엇)을 구분짓는 용도이며 (왜) 그것을 구분 지을 필요가 있을까?

  • Model, View, Controller를 구분짓기 위한 구조이다.
  • Model -> Data: a wrapper of data
  • View -> User Interface: a representation of a user interface
  • Controller -> Core Logic: an intermediary between the Model and the View
  • 역할 분담시 용이
  • 코드가 지저분하지 않음

 

MVC 패턴의 장점

다른 패턴에 비해 코드량이 적다

애플에서 기본적으로 지원하는 패턴이므로 쉽게 접근할 수 있다

많은 개발자들에게 친숙한 패턴이기 때문에 개발자들이 쉽게 유지보수할 수 있다

개발속도가 빠르기때문에 아키텍처가 중요하지 않을때 사용하거나 규모가 작은 프로젝트에서 사용하기 좋다

 

좋은 아키텍쳐의 세가지 요소

1. Balanced distribution of responsibilities among entities with strict roles.
 - 각 객체들의 역할이 분명한가?
 - SOLID 원칙에 포함된 Single Responsibility Principle(단일 책임 원칙)과 일맥상통한다.
 - 한 객체는 하나의 역할만 수행하도록 해서, 프로그램의 복잡도를 낮춰야한다는 의미이다.
2. Testability usually comes from the first feature.
 - 테스트가 가능한가?
 - 런타임에서 문제를 발견할 수 있으려면 테스트 단계를 수행하는 것이 좋기 때문이다.
3. Ease of use and a low maintenance cost.
 - 사용이 편리하고, 유지보수 비용이 적은가?
 - 여기서 사용이 편리한 코드란 길이가 짧은 코드, 설명이 필요하지 않은 코드를 말한다.

 

초기화 Initialization

초기화는 클래스, 구조체, 열거형의 인스턴스를 사용할 수 있게 준비하는 작업이다. 이 작업은 인스턴스의 각각의 저장 프로퍼티에 초기값을 설정하고, 인스턴스가 사용될 준비가 되기 전에 필요한 다른 설정이나 초기화를 수행하는 것을 포함한다. 특정 유형의 새 인스턴스를 생산하기 위해 호출할 수 있는 특수 메서드와 같은 이니셜라이저를 정의함으로써 초기화를 진행할 수 있다. Object-C 초기화와 달리 swift의 이니셜라이저는 값을 반환하지 않는다. 이니셜라이저의 주된 역할은 새 인스턴스가 처음 사용되기 전에 올바르게 초기화되도록 하는 것이다. 

 

저장 프로퍼티에 초기값 설정하기 Setting Initial Values for Stored Properties

클래스와 구조체는 그 안에 인스턴스가 생성되었을 때, 그것들의 저장 프로퍼티에 올바른 초기값을 설정해야한다. 이니셜라이저를 통해서 초기값을 설정할 수도 있고, 프로퍼티를 정의할 때 디폴트 프로퍼티 값을 할당해서 설정할 수도 있다. (이니셜라이저를 통해서 혹은 디폴트 프로퍼티 값을 할당해서 설정할 때, 어떠한 프로퍼티 옵저버도 호출하지 않고 직접적으로 값이 설정되어야한다.)

 

이니셜라이저 Initializers

이니셜라이저는 특정 타입의 새로운 인스턴스를 만들기 위해 호출된다. 가장 간단한 형태로, 이니셜라이저는 매개변수가 없고 init 키워드를 사용한 인스턴스 메서드와 같은 형태를 띄고 있다.

init() {
	//이 안에서 초기화 수행
}
struct Dog {
    var name: String
    init() {
        name = "hodoo"
    }
}
var dog = Dog()
print("The name of my dog is \(dog.name)")  //The name of my dog is hodoo
 
 

디폴트 프로퍼티 값 Default Property Values

프로퍼티가 정의될 때 초기 값을 할당하는 방법으로 디폴트 프로퍼티 값을 명시해줄 수 있다.

프로퍼티가 항상 같은 초기값을 가진다면, 이니셜라이저를 사용하는 것보다 디폴트 값을 주는게 낫다. 둘 중 어떤 방식을 사용하든 결과는 같다. 그러나, 디폴트 값을 주는 것이 훨씬 짧고, 훨씬 명료하다. 그리고 디폴트 이니셜라이저와 디폴트 상속의 이점을 사용하기도 더 쉬워진다. 
struct Dog {
    var name = "hodoo"
}
var dog = Dog()
print("The name of my dog is \(dog.name)")  //The name of my dog is hodoo

 

초기화 커스텀하기 Customizing Initialization

input 매개변수와 옵셔널 프로퍼티 타입을 통해, 혹은 초기화에 상수를 할당하는 것을 통해 초기화 작업을 커스터마이즈할 수 있다.

 

초기화 매개변수 Initialization Parameters

초기화 작업을 커스터마이즈하는 타입과 이름을 정의하기 위해서 초기화 매개변수를 이니셜라이저의 정의의 일부분으로 제공할 수 있다. 초기화 매개변수는 함수와 메서드 매개변수와 같은 수용력과 문법을 가진다.

struct Dog {
    var introduceMyDog: String
    init(fromEnglish englishName: String) {
        introduceMyDog = "The name of my dog is \(englishName)"
    }
    init(fromKorean koreanName: String) {
        introduceMyDog = "우리 강아지 이름은 \(koreanName)입니다"
    }
}

let englishIntroduction = Dog(fromEnglish: "hodoo")
let koreanIntroduction = Dog(fromKorean: "호두")

print(englishIntroduction)  //Dog(introduceMyDog: "The name of my dog is hodoo")
print(koreanIntroduction)  //Dog(introduceMyDog: "우리 강아지 이름은 호두입니다")

 

매개변수 이름과 인자 이름 Parameter Names and Arguement Labels

인자 이름은 이니셜라이저를 호출할 때 괄호(())안에 쓰여있는 것을 말한다. 

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

위의 코드에서의 red, grren, blue, white는 매개변수에 해당하고

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

이니셜라이저를 호출할 때의 괄호 안의 red, green, blue, white는 인자 이름에 해당한다. 인자 이름은 항상 필수적으로 작성되어야하고, 생략되면 컴파일 오류가 생긴다. 

 

인자 이름이 없는 이니셜라이저 매개변수 Initializer Parameters Without Argument Lables

이니셜라이저 매개변수에 대해 인자 이름을 사용하지 않으려면, 해당 매개변수에 대한 인자 이름 대신 밑줄(_)을 작성하면 된다.

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

 

To be continued...

 

쥬스메이커

재고 부족시 주스를 만들지 못하게 하는 방법

struct JuiceMaker {
    let juiceName = FruitStore()
    
    func makeJuice(selectedJuice: String) {
        switch selectedJuice {
        case "딸기쥬스":
            for _ in 0..<16 {
                juiceName.minus(selectedFruit: "딸기")
            }
        case "바나나쥬스":
            for _ in 0..<2 {
                juiceName.minus(selectedFruit: "바나나")
            }
        case "키위쥬스":
            for _ in 0..<3 {
                juiceName.minus(selectedFruit: "키위")
            }
        case "파인애플쥬스":
            for _ in 0..<2 {
                juiceName.minus(selectedFruit: "파인애플")
            }
        case "딸바쥬스":
            for _ in 0..<10 {
                juiceName.minus(selectedFruit: "딸기")
            }
            juiceName.minus(selectedFruit: "바나나")
        case "망고쥬스":
            for _ in 0..<3 {
                juiceName.minus(selectedFruit: "망고")
            }
        case "망고키위쥬스":
            for _ in 0..<2 {
                juiceName.minus(selectedFruit: "망고")
            }
            juiceName.minus(selectedFruit: "키위")
        default:
            print("잘못된 선택입니다.")
        }
    }
}

현재의 코드는 이러하다. 현재의 코드에서 if juiceName.fruits["딸기"] < 16 을 통해 재고가 부족하지 않은 상황을 가정해준다면, 코드 들여쓰기는 2번 초과하지 않습니다.를 어기게 된다. 그렇다고해서 guard문을 사용하면 에러처리를 또 해줘야한다. 에러 처리를 할 수 있는 방법은 두 가지가 있다.

  • Result를 이용하는 방법
  • throw, do-catch를 이용하는 방법

전자의 경우, Result안에 함수를 넣을 수 있을지 여부를 모르겠고, 후자의 경우 호출해줄 때마다 try로 에러를 잡아줘야한다. 잠을 자고 고민을 더 해봐야겠다..

 

연결 리스트 Linked List

연결 리스트란?

데이터와 링크로 구성된 노드를 이용하여 메모리에 저장된 순서와 상관없이 연결된 데이터 구조를 말한다. 연결 리스트에는 크게 세가지의 종류가 있다.

  • 단일 연결 리스트(Singly linked list) : 자료 공간 1개와 다음 노드를 가리키는 포인터로 구성된 노드를 가지는 연결 리스트
  • 이중 연결 리스트(Doubly linked list) : 앞의 노드와 뒤의 노드를 가리키는 포인터가 2개 있는 연결 리스트
  • 순환 연결 목록(Circular linked list) : 마지막 노드와 처음 노드가 연결되어 원형 구조를 이루고 있는 연결 리스트

일반적으로 연결 리스트라고 하면, 이 중에서 단일 연결 리스트를 의미한다.

위는 단일 연결 리스트의 구조를 나타낸 그림이다. 각각의 사각형은 Node를 의미한다. Node는 데이터(채색되어 있지 않은 부분)와 링크(채색되어 있는 부분)로 나뉘는데 데이터에는 저장하길 원하는 값을 넣고, 링크에는 다음 노드를 가리키도록 하여 연결 리스트를 구현할 수 있다.

중간 element를 삭제할 때에는 위 그림처럼 삭제하고자하는 element를 가리키는 Node를 그 다음 element를 가리키도록 하면 된다. 즉, B를 가리키는 Node가 C를 가리키도록 하면 된다.

중간 element를 삽입할 때에도 비슷하다. B를 가리키는 Node가 I를 가리키도록 하고, I의 노드는 B를 가리키면 된다.

시간 복잡도

삽입, 삭제: O(1) 다음 노드의 위치를 가지고 있기 때문에 중간 element를 삽입, 삭제해도 연산의 오버헤드가 발생하지 않아 O(1)의 시간복잡도로 처리할 수 있다. 하지만, 현실적으로 가장 기초적인 연결 리스트는 탐색 후 삽입, 삭제를 해야하므로 O(N+1)을 요구한다. head나 tail 앞이나 뒤에 새로운 노드를 삽입하면 탐색이 필요없기 때문에 O(1)에 삽입이 가능하다.

탐색: O(N) 인덱스를 통한 Random Access가 불가능하고, 원하는 데이터가 나올 때까지 탐색해야하기 때문에 O(N)의 시간 복잡도가 요구된다.

 

연결 리스트의 장단점(cf.배열)

장점

다음 노드의 위치를 가지고 있기 때문에 연속적으로 저장할 필요가 없다. 그래서 중간 element를 삽입, 삭제를 해도 재배치에서 연산의 오버헤드가 발생하지 않는다. 그 결과 삽입, 삭제에 O(1)의 시간 복잡도가 걸린다.(하지만, 현실적으로 가장 기초적인 연결 리스트는 탐색 후 삽입, 삭제를 해야하므로 O(N+1)을 요구한다.)

단점

배열과 달리 swift에서는 따로 제공하는 타입이 없어 직접 구현을 해야하는 번거로움이 있다. 또한 내 다음 데이터에 대한 연결 정보를 저장하는 별도의 공간이 필요해서, 노드 관리에 일정량의 메모리가 소진된다. 그리고 배열과 달리 인덱스를 통한 Random Access가 불가능하여 처음부터 원하는 데이터가 나올 때까지 탐색해야한다. 예를 들어 데이터가 100개인데, 100번째 데이터에 접근하려고 하면 100번을 순회해야한다는 것이다. 따라서 탐색에 O(N)의 시간 복잡도가 요구된다. 덧붙여 포인터 기반이기 때문에 디버깅이 어렵다.

배열과의 비교

  장점 단점
배열
▪️ 구현이 쉽다.
▪️ 인덱스 값을 알고 있는 경우 빠르게 데이터에 접근할 수 있다.
▪️ 중간 element 삽입, 삭제시 자료의 이동이 빈번하다.
▪️ 크기가 고정되어있다.
연결 리스트 ▪️ 다음 노드의 위치를 가지고 있기 때문에 연속적으로 저장할 필요가 없다.
▪️ 삽입, 삭제시 자료의 이동이 없다.
▪️ 중간 element를 삽입, 삭제를 해도 재배치에서 연산의 오버헤드가 발생하지 않는다.
▪️ 삽입, 삭제에 O(1) 시간 복잡도가 걸린다.
▪️ 구현을 직접 해야하는 번거로움이 있다.
▪️ 노드 관리에 일정량의 메모리가 소진된다.
▪️ 인덱스를 통한 Random Access가 불가능하다.
▪️ 처음부터 원하는 데이터가 나올 때까지 탐색해야하기 때문에 탐색에 O(N)의 시간 복잡도가 요구된다.
▪️ 포인터 기반이라 디버깅이 어렵다.

 

 

너무 무지성으로 공부하는 것 같아서 속상한 오늘이다.

 

 

 

참조

https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

 

Initialization — The Swift Programming Language (Swift 5.7)

Initialization Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization t

docs.swift.org

https://jiyeonlab.tistory.com/38

 

[iOS 아키텍처 패턴] MVC

iOS의 아키텍처 패턴에는 MVC, MVP, MVVM 등 여러 종류가 있다. 사실 그동안에는 MVC 패턴으로만 코드를 짰었다. Apple이 기본적으로 적용한게 MVC 패턴이기도 하고, 단순하기 때문이었다. 하지만, Apple의

jiyeonlab.tistory.com

https://www.geeksforgeeks.org/difference-between-software-design-and-software-architecture/

 

Difference between Software Design and Software Architecture - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org