티스토리 뷰

들어가며

 

객체 지향 설계에서 자주 언급되는 SOLID

프로그램의 유지보수성과 확장성을 높이기 위해 꼭 필요한 원칙인데요.

 

SOLID 원칙
- SRP(Single Responsibility Principle): 단일 책임 원칙
- OCP(Open Closed Priciple): 개방 폐쇄 원칙
- LSP(Listov Substitution Priciple): 리스코프 치환 원칙
- ISP(Interface Segregation Principle): 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle): 의존성 역전 원칙

 

 

그 중에서도 이번 글에서는 SOLID 원칙 중 마지막에 해당하는 의존성 역전 원칙(DIP)에 대해

그림과 Swift의 예시 코드로 설명해보겠습니다.

 

 


✅ DIP(의존 역전 원칙)의 필요성

 

 

DIP에 대해 알아보기 전에, 오늘의 주인공 김아요 군을 모셔보겠습니다.

 

김아요 군은 자전거로 배달을 하며 건실하게 살아가는 청년인데요.

 

건실한 청년 김아요 군

 

 

그림으로 그려보면 위와 같은 모습입니다.

 

배달 서비스가 자전거를 갖고 있다,

의존을 하고 있다고 하는데요,

 

 

class Bicycle {
    func deliver() {
        print("김아요 군이 자전거로 배달 중입니다..")
    }
}

class DeliveryService {
    let bicycle = Bicycle()
    
    func startDelivery() {
        bicycle.deliver()
    }
}

let service = DeliveryService()
service.startDelivery()

 

 

코드로 나타내면 위와 같은 모습입니다. 

 

DeliveryService 안bicycle 객체를 직접 생성하고 있는데요.

이를 DeliveryService가 bicycle 구현체에 강하게 의존하고 있다 합니다.

 

 

 

강한 의존이 왜 문제가 될까요?

 

다음 상황을 고려해봅시다.

 

 

자전거로 열심히 배달을 하던 김아요 군은...

길을 지나던 길에 멋진 오토바이를 보곤 나쁜 마음을 먹습니다.

 

 

나쁜 마음을 먹은 김아요 군..

 

 

훔친 오토바이가 생긴 김아요 군은

배달 수단을 자전거에서 오토바이로 바꾸게 되는데요.

 

기존에 있는 DeliveryService 내의 bicycle을 수정하려면

bicycle 객체를 삭제하고 motorcycle 객체로 변경을 해주어야 할 겁니다.

 

class DeliveryService {
    let motorcycle = Motorcycle()
    
    func startDelivery() {
        motorcycle.deliver()
    }
}

 

 

즉, 하위 클래스인 bycicle이 변경되어

상위 클래스인 DeliveryService 클래스를 직접 수정해주어야 한다는 것이죠.

 

이를 코드의 유연성이 부족하다고 합니다.

 

 

 

다음 상황입니다.

 

비록 훔친 오토바이이지만 김아요 군은 열심히 배달을 해서.. (생략)

배달 플랫폼을 만들어 큰 성공을 이루는데요.

 

배달 서비스가 확장되며,

자전거, 오토바이, 자동차 등 다양한 수단들이 추가되었습니다.

배달 플랫폼으로 대성공한 김아요 군

 

 

이 경우에도 마찬가지로,

각각의 교통수단을 DeliveryService 클래스 내에 직접 추가해주어야 합니다.

 

 

class DeliveryService {
    let bicycle = Bicycle()
    let motorcycle = Motorcycle()
    let car = Car()

    func startDelivery() {
        bicycle.deliver()
        motorcycle.deliver()
        car.deliver()
    }
}

 

 

이렇게 되면 배달 수단이 계속 추가될 때마다

DeliveryService 내의 코드가 복잡해지며 서비스 코드의 유지 보수를 어렵게 만듭니다.

 

확장성이 부족하다고 하는데요,

 

이 때, 각 배달 수단에 맞는 deliver() 로직을 직접 추가해야 하기에

개방-폐쇄 원칙(OCP)에도 위배되는 것이라 볼 수 있습니다.

 

 

프로젝트는 새로운 기능이 추가 될 때 기존 코드를 수정하지 않고 확장할 수 있어야 할 것입니다.

 

 


✅ DIP(의존 역전 원칙) 적용하기

 

그렇다면 위와 같은 문제를 어떻게 해결할 수 있을까요?

 

해결 방법으로 의존성 역전 원칙(DIP)를 적용할 수 있습니다.

 

DIP는 다음과 같은 두 가지 원칙을 따르는데요,

 

1. 상위 모듈(DeliverService)는 하위 모듈 (Bicycle, Motorcycle, Car..)에 의존해서는 안 된다.
   둘 다 추상화 (인터페이스, 프로토콜)에 의존해야 한다.
2. 추상화는 세부 사항에 의존해서는 안 된다. 세부 사항이 추상화에 의존해야 한다.

 

 

여기서 추상화란 구체적인 세부 사항을 감추고, 동작 자체만 정의하는 것을 의미합니다.

 

예를 들어, 어떠한 함수 func A() { ... } 가 존재할 때

func A()만 정의하고 구체적인 세부 내용 {...} 은 드러내지 않는 것이죠.

 

 

다시 김아요의 배달 이야기로 돌아가서 ..

 

위의 상황에 DIP를 적용하면 아래와 같은 구조가 됩니다.

DIP 예시

 

배달 서비스(상위 모듈)자전거, 오토바이, 자동차(하위 모듈)이동수단이라는

공통된 인터페이스에 의존시키는 건데요,

 

Swift에서는 프로토콜을 이용해 DIP를 구현할 수 있습니다.

 

 

 

위의 예시처럼 DeliveryMethod라는 프로토콜을 정의하고

이를 채택한 Bicycle이라는 클래스를 만들면, 프로토콜에 정의된 함수가 없을 경우 에러문이 발생합니다.

 

 

 

 

에러에서 fix 버튼을 누르면 함수가 자동 생성되고, 함수의 구현부를 작성해주면 됩니다.

 

 

protocol DeliveryMethod {
    func deliver()
}

class Bicycle: DeliveryMethod {
    func deliver() {
        print("김아요 군이 자전거로 배달 중입니다..")
    }
}

class Motorcycle: DeliveryMethod {
    func deliver() {
        print("김아요 군이 오토바이로 배달 중입니다..")
    }
}

class Car: DeliveryMethod {
    func deliver() {
        print("김아요 군이 자동차로 배달 중입니다..")
    }
}

class DeliveryService {
    let method: DeliveryMethod
    
    init(method: DeliveryMethod) {
        self.method = method
    }
    
    func startDelivery() {
        method.deliver()
    }
}

 

 

위와 같이 DeliveryMethod 프로토콜을 따르는 Bicycle, Motorcycle, Car 클래스를 만들었습니다.

 

DeliveryService에도 DeliveryMethod 타입의 method를 사용해 추상화에 의존하게 합니다.

 

 

let bicycle = Bicycle()
let motorcycle = Motorcycle()
let car = Car()

let bikeDelivery = DeliveryService(method: bicycle)
bikeDelivery.startDelivery()  // "김아요 군이 자전거로 배달 중입니다.."

let motoDelivery = DeliveryService(method: motorcycle)
motoDelivery.startDelivery()  // "김아교 군이 오토바이로 배달 중입니다.."

let carDelivery = DeliveryService(method: car)
carDelivery.startDelivery()  // "김아요 군이 자동차로 배달 중입니다.."

 

 

이렇게 프로토콜에 의존하게 되면,

 

배달 수단이 바뀌어도 DeliveryService 내부의 코드를 수정할 필요가 없어져

유지보수와 확장성이 용이해집니다.

 

 


✅ DIP와 DI(의존성 주입)의 관계

 

DIP의 '상위 모듈이 하위 모듈에 의존하지 않도록 한다'는 원칙!

 

여기에서 함께 의존성 주입(DI)이 활용되는데요.

 

DeliveryService(method: bicycle)

 

 

위에서 DeliveryService 객체를 생성하며 method에 프로퍼티

외부에서 생성한 객체를 입력해주었습니다.

 

DI 예시

 

주사기로 주사를 놓듯

배달 서비스에 맞게 자전거, 오토바이, 자동차를 넣어주는 것을 의존성을 주입한다고 말합니다.

 

이렇게 하면 모든 클래스의 내부 구현체를 수정하지 않고도

유지 보수를 할 수 있겠죠?

 

 

때문에 DIP와 DI를 함께 사용해서 유지보수성을 높이고,

테스트를 용이하게 하는 장점이 있습니다.

 

 


마치며

 

이렇게 해서 SOLID의 원칙 중 DIP에 대해 알아보았는데요.

 

프로토콜과 의존성 주입을 통해 코드의 결합도를 낮추고,

더 나은 소프트웨어 아키텍처를 설계할 수 있겠습니다.

 

감사합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   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
글 보관함