클린 코드 11장 - 시스템

French Marigold·2024년 1월 24일
0

클린코드

목록 보기
11/13

시스템 제작과 시스템 사용을 분리하는 방법 (194p)

  • Main 분리

    • 시스템 생성과 관련된 코드는 모두 main 이나 main이 호출하는 모듈로 옮긴다. (AppDelegate 쪽으로 넘기라는 의미로 받아들이면 될 듯)
    • main에서 생성된 객체를 애플리케이션에게 넘긴다.
    • 애플리케이션은 main이나 객체가 생성되는 과정을 전혀 모른다.
  • 추상 팩토리

    • 추상 팩토리 패턴은 다음과 같이 5가지 종류로 분류된다.

      • AbstractFactory ⇒ 추상화 공장. 추상화 제품을 생산하는 공장이다.
      • AbstractProduct ⇒ 추상화 제품.
      • ConcreteFactory ⇒ 추상화 공장을 구체적으로 구현한 공장이다. 구체적 제품을 생산하는 공장이다.
      • ConcreteProduct ⇒ 추상화 제품을 구체적으로 구현한 제품이다.
      • Client ⇒ 사용자는 추상화된 팩토리로부터만 객체를 받기 때문에 추상 팩토리의 내부를 절대 알 수 없다.
    • 여러 객체를 특정 클래스에 의존하지 않고 만들 수 있게 해주기 위해 사용된다.

      // AbstractFactory는 'SnackFactory'이다. 
      // 추상화 공장으로 **이 공장은 추상화 제품을 찍어내는 역할**을 한다. 
      protocol SnackFactory {
          func createSnack() -> Snack
      }
      
      // AbstractProduct는 'Snack'이다. 
      // 추상화 제품으로, 어떤 제품이든 이름을 붙여 생산할 수 있다. 
      protocol Snack {
          var name: String { get }
      }
      
      // ConcreteFactory는 'ChocolateFactory'와 'CandyFactory'이다. 
      // 추상화 공장을 초콜릿 공장과 사탕 공장으로 구체화시켰다.
      // 구체적 공장은 구체적 제품을 생산하는 역할을 한다. 
      class ChocolateFactory: SnackFactory {
          func createSnack() -> Snack {
              return Chocolate()
          }
      }
      
      class CandyFactory: SnackFactory {
          func createSnack() -> Snack {
              return Candy()
          }
      }
      
      // ConcreteProduct는 'Chocolate'와 'Candy'이다. 
      // 추상화 제품을 초콜릿과 사탕으로 구체화시켰다.
      class Chocolate: Snack {
          var name: String {
              return "초콜릿"
          }
      }
      
      class Candy: Snack {
          var name: String {
              return "사탕"
          }
      }
      
      // Client는 실제로 이 팩토리를 사용하는 코드 부분입니다.
      let chocolateFactory = ChocolateFactory()
      let chocolate = chocolateFactory.createSnack()
      print(chocolate.name)  // 출력: 초콜릿
      
      let candyFactory = CandyFactory()
      let candy = candyFactory.createSnack()
      print(candy.name)  // 출력: 사탕
  • 의존성 주입
    class A부품 {
    		var name: String = "A 부품"
    }
    
    // C완성품 A부품에게 의존하는 상태이다.
    // 왜냐하면 A부품의 저장 속성이 변경되면 (name에서 nameString으로 변경되면)
    // C완성품의 메소드 내에 있는 저장속성도 변경해주어야 하기 때문이다. 
    // C완성품이 A부품에게 의존하고 있기 때문에 이런 현상이 발생하는 것이다. 
    class C완성품 {
    		var a: A부품 = A부품()
    
    		private func printName() {
    				print(a.name)
    		}
    }
    
    C완성품 => 의존 => A부품 ⭐️⭐️
    
    ================================================================================
    
    protocol 모듈화된부품 {
    		var name: String { get set }
    }
    
    class A부품: 모듈화된부품 {
    		var name: String = "A 부품"
    }
    
    class B부품: 모듈화된부품 {
    		var name: String = "B 부품"
    }
    
    class C완성품 {
    		var moduled: 모듈화된부품
    
    		init(moduled: 모듈화된부품) {
    				self.moduled = moduled
    		}
    
    		private func printName() {
    				print(moduled.name)
    		}
    }
    
    let moduledA = A부품()
    let moduledB = B부품()
    
    let cmoduledA = C완성품(moduled: moduledA)
    let cmoduledB = C완성품(moduled: moduledB)
    
    cmoduledA.printName() // "A 부품"
    cmoduledB.printName() // "B 부품"
    
    													  A부품
    C완성품 - 모듈화된부품 <= 의존 <= 의존관계의 역전이 일어남 ⭐️⭐️
    													  B부품

관심사 분리 (195p)

  • 관심사 분리란 각 모듈이 자신의 관심사에만 집중하고 다른 부분은 알 필요가 없다는 원칙을 의미한다.
  • 반대로 말하면 관심사가 제대로 분리되지 않은 코드는 하나의 클래스나 함수가 여러가지 역할을 수행하는 것을 의미한다.
  • 각 클래스가 자신의 관심사에만 가지고 구현하도록 하면, 코드의 가독성과 유지보수성이 향상된다.
// 관심사가 분리되지 않은 코드
// 한 클래스가 사용자의 이름을 "입력하는 역할"과 "출력하는 역할"을 동시에 하고 있다. 

class NameSettingController {
    func enterName(_ name: String) {
        // 사용자 이름 입력
        let userName = name

        // 사용자 이름 출력
        print("Hello, \(userName)!")
    }
}

=======================================================================================

// 관심사가 분리된 코드 

// 이름을 입력받는 역할 담당
class InputManager {
    func enterName(_ name: String) -> String {
        // 사용자 이름 입력
        let userName = name
        return userName
    }
}

// 이름을 출력하는 역할 담당
class OutputManager {
    func printName(_ name: String) {
        // 사용자 이름 출력
        print("Hello, \(name)!")
    }
}

// NameSettingController는 두 Manager를 이용하여 입력받고 출력하는 역할을 담당
// 이렇게 각 클래스가 자신의 관심사에만 집중하도록 하면, 코드의 가독성과 유지보수성이 향상된다. ⭐️⭐️
class NameSettingController {
    let inputManager = InputManager()
    let outputManager = OutputManager()

    func enterName() {
        let userName = inputManager.enterName()
        outputManager.printName(userName)
    }
}

BDUF (210p)

  • 개발 시작 전에 시스템 전체 설계를 끝내는 방식의 소프트웨어 개발 방법.
  • 하지만 이 방식은 구현 사항이 변경될 경우, 유연하게 대응하기 어렵다는 단점이 있다.
profile
꽃말 == 반드시 오고야 말 행복

0개의 댓글