티스토리 뷰

반응형

- SOLID 란?

로버트 마틴이 2000년대 초에 명명한 객체지향 프로그래밍의 다섯 가지 기본 원칙을 마이클 페더스가 원칙의 앞글자를 따서 다시 SOLID라는 이름으로 소개한 것

- 원칙이란 ?

- 5가지 원칙

단일 책임의 원칙(Single Responsibility Principle, SRP)

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C. 마틴

  • 모든 클래스는 각각 하나의 기능만 가진다는 의미
  • 해당 클래스가 제공하는 모든 서비스는 단 하나의 책임만 수행하는데 집중되어야 한다 -> 책임을 완전히 캡슐화해야 한다.

내가 처음 안드로이드 개발을 할 때 Activity 안에서 api를 호출하고 응답 데이터를 가공하고 조건에 맞게 화면에 그려주었다. 이때 Activity가 가지고 있는 책임은

1. Api 호출
2. 데이터 가공
3. 조건 계산 
4. 화면에 그려주기

이렇게 4가지라고 볼 수 있다. 요즘 많이 사용되는 MVVM 패턴을 보면 Activity에 몰려있는 책임을 나눠가지는 구조이다.

M(Model): 데이터 가공
V(View): 화면 그리기
VM(ViewModel): 비즈니스 로직

이로써 Activity(View)는 화면을 그리는 책임만 갖게 된다.

책임에 대한 이해가 없다면 모양만 MVVM인 형태가 만들어질 수 있다.  처음 MVVM을 적용했을 때 ViewModel에서 받아온 서버 응답 데이터를 Activity에서 가공 후 각 뷰에 그렸는데 이는 View가 비즈니스 로직까지 처리했던 구조라서 혼이 났던 기억이 있다.

개방 폐쇄의 원칙(Open Close Principle, OCP)

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다. - 로버트 C. 마틴

  • 소프트웨어의 모든 구성요소(클래스, 모듈, 함수)는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 의미
  • 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며 쉽게 확장이 가능하여 재사용할 수 있어야 한다

개방 폐쇄의 원칙은 의존성 주입을 생각하면 된다. 나는 Repository를 생성할 때 interface와 구현체를 만든다. 그리고 ViewModel의 인자 값으로 interface를 받는다. 그러면 ViewModel은 주입받는 구현체가 바뀌어도 신경을 쓸 필요가 없다(확장에는 열려있고 변경에는 닫혀있다)

interface FoodRepository {
    fun eat()
}

class PizzaRepositoryImpl : FoodRepository {
    override fun eat() {
        print("피자 냠냠")
    }
}

class ChickenRepositoryImpl : FoodRepository {
    override fun eat() {
        print("치킨 냠냠")
    }
}

// FoodViewModel은 주입받는 형태이기 때문에 어떤 Repository가 오든 변경하지 않아도 됨
// DI를 사용하면 모듈에서 주입하는 모델을 변경해주면 쉽게 Repository를 변경 가능
class FoodViewModel constructor(
    foodRepository: FoodRepository
) {
    init {
        foodRepository.eat()
    }
}

리스 코브 치환의 원칙(LisKov Substitution Principle, LSP)

서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다. - 로버트 C. 마틴

  • 부모 클래스를 가리키는 포인터에 해당 클래스를 상속하는 자식 클래스를 할당하더라도 모든 기능이 정상적으로 작동해야 하며 자식 클래스의 상세 내부를 부모 클래스는 알 필요가 없다.

리스 코브 치환 원칙을 검색하면 가장 많이 나오는 예제인 정사각형/직사각형의 예제가 제일 이해하기 쉬웠다.

// 직사각형 클래스
abstract class Rectangle {
    open var width: Int = 0
    open var height: Int = 0
}

/** 
 *  직사각형을 상속받은 정사각형 클래스
 *  가로와 세로는 항상 같다
 */
class Square : Rectangle() {
    override var width: Int
        get() = super.width
        set(width) {
            super.width = width
            super.height = width
        }
    override var height: Int
        get() = super.height
        set(height) {
            super.width = height
            super.height = height
        }
}

/** 
 * 부모클래스인 직사각형 대신 자식클래스인 정사각형을 넣게되면 
 * Height과 Width 둘다 1씩 증가하는 오류가 생긴다
 */
fun increaseHeight(rectangle: Rectangle) {
    if (rectangle.height <= rectangle.width) {
        rectangle.height = rectangle.width + 1
    }
}

쉽게 말해서 상속관계를 잘 생각자라는 말인듯하다. 리스 코브 치환의 원칙이 지켜지지 않는다는 말은 자식 클래스로 할당했을 때 기능이 정삭적으로 작동하지 않는다는 말이다. 작동하지 않으면 다른 코드를 수정해야 하고 이는 변경에는 닫혀있어야 하는 개방 폐쇄의 원칙의 위배와도 같다.

인터페이스 분리의 원칙(ISP, Interface Segregation Principle)

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다. - 로버트 C. 마틴
  • 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다
  • 하나의 큰 인터페이스를 상속받기보다는 구체적이고 작은 단위들로 분리시켜 꼭 필요한 인터페이스만 상속해야 한다.

인터페이스 분리의 원칙은 단일 책임의 원칙과 매우 유사하다고 생각한다.
안드로이드의 View class 내부의 clickListener interface들(OnClickListener, OnLongClickListener)이 인터페이스 분리의 원칙을 잘 지킨 예라고 생각한다.

// View class 에 속한 Interface 스니펫
public interface OnDragListener {
    boolean onDrag(View var1, DragEvent var2);
}

public interface OnCreateContextMenuListener {
    void onCreateContextMenu(ContextMenu var1, View var2, ContextMenuInfo var3);
}

public interface OnContextClickListener {
    boolean onContextClick(View var1);
}

public interface OnClickListener {
    void onClick(View var1);
}

의존성 역전의 원칙(DIP, Dependency Inversion Principle)

고차원 모듈은 저 차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.
자주 변경되는 구체(Concrete) 클래스에 의존하지 마라 - 로버트 C. 마틴 -
  • 의존관계를 맺을 때 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺어야 한다.
  • 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존해야 한다

 

의존성 역전의 원칙에서 말하는 고차원 모듈과 저차원 모듈은 무엇일까?

고차원 모듈 = 인터페이스 같은 구현되지 않은 추상적 개념
저차원 모듈 = 구현된 객체

즉 인터페이스, 객체는 구현된 객체에 의존해선 안된다 모두 추상화된(인터페이스)것에 의존해야 한다는 것이다.

개방 폐쇄의 원칙을 지키면 자연스럽게 지켜지는 원칙이라고 생각한다. 개방 폐쇄의 원칙을 좀 더 자세히 설명한 버전인듯한 느낌이다.

 

느낀 점

원칙이란 게 참 어렵다. 머리로는 알지만 항상 일관되게 꾸준히 지켜나가는 것이 매우 힘든 일인 듯하다. SOLID 원칙을 지키는것이 객체지향 프로그래밍의 특징에 맞게 올바르게 쓸 수 있게끔 방향을 잡아준다고 생각한다. 힘들겠지만 지키려고 노력해야겠다.

 

결론

인터페이스를 잘쓰자

 

참고 링크 1
참고 링크 2

반응형
댓글