본문 바로가기
DEV/JAVA

03. JAVA와 SOLID

by wooki4307 2020. 2. 26.

SOLID, JAVA공부를 한 사람이라면, 객체지향 공부를 해본 사람이라면, 어떤 회사의 입사 테스트 시험을 본 사람이라면 한번쯤 봤을 문구의 약자이다. S.O.L.I.D.의 약자는 다음과 같다.

  • SRP
  • OCP
  • LSP
  • ISP
  • DIP

해당 약자에 대해 하나씩 살펴 보도록 하자.


1. SRP (Single Responsibility Principle): 단일 책임 원칙

어떠한 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

- 역할(책임)을 분리하라는 것

class Car {
    int tire;
    String window;          // 창문 개폐 방식
    boolean key;            // 열쇠 사용 여부(요즘 차는 없더라구요)
    boolean remoteController;

    void openType(){
        if(this.key==false){
            this.window="auto";
        }else{
            this.window="hand";
        }
    }
}

위의 샘플 클래스 파일은 SRP를 만족했는가? 그 답은 'No'이다. 우선 문제점이 두가지가 있다.

  1. key의 유무에 따라 창문 개폐 방식을 구현하려고 한 점
  2. 리모컨이 없는 차도 remoteController값이 필요하다는 점.

위의 클래스 파일을 SRP를 적용하면 다음과 같이 적용이 가능할 것 같다.

abstract class Car {
    int tire;
    String gear;
    String wiper;
    static String window;

    abstract void openType();
}

class OldCar extends Car{
    static {
        Car.window="Self";
    }
    boolean key;

    @Override
    void openType() {
        System.out.println("철컥...끼익...");
    }
}

class NewCar extends Car{
    static {
        Car.window="Button";
    }
    boolean remoteController;
    String seatHeat;

    @Override
    void openType() {
        System.out.println("딸깍...삐빅...");
    }
}

필자는 최대한 이전에 배운 사항을 적용해보려고 한 내용이니 잘 못된 부분이 있으면 지적 바랍니다. 해당 클래스를 좀더 세분화 하여 Car라는 공통된 추상화 모델링을 한 후에 공통된 메소드를 추상 메소드로 분류하여 구현하였다. 또한 각각 추가되고, 필요한 속성은 따로 클래스에 속성을 부여하였다. 

이러한 방법으로 모델링하여 신차인 경우 NewCar클래스만 수정 가능하도록 하는 것이 SRP, 단일 책임 원칙이다.

 

2. OCP (Open Closed Principle): 개방 폐쇄 원칙

자신의 확장에는 열려 있고, 주변의 변화에는 닫혀 있어야 한다.

만약 위의 클래스가 탈것에 대한 소스를 짠다고 가정하자. 그럼 SRP에서 제시한 예시로는 한계가 있을 것이다. 자동차와 오토바이를 파는데 Car 클래스에 추가할 수는 없지 않은가? 위의 예제로는 OCP원칙에 위배된다. 왜냐면 확장도 불가능한 구조이고, 클래스 추가에 대해 영향을 받아 폐쇄 원칙에도 위반이 되기 때문이다. 위의 예시의 SRP원칙에 OCP원칙까지 더하려면 다음과 같이 클래스 구조를 짤 수 있을 것이다.

class Vehicle{
    String type;
}
class Bike extends Vehicle{
    
}
abstract class Car extends Vehicle{
    int tire;
    String gear;
    String wiper;
    String window;

    abstract void openType();
}

위의 그림을 간단히 도식화 하면 다음과 같이 구성된다.

OCP 적용의 예

Car에 대해 수정을 하면 Bike클래스에 영향이 가지 않는다. 또 Vehicle은 다른 종류의 종류가 추가가 가능, 즉 확장이 가능하다. 이러한 부분이 OCP적용이 가능한 것이라고 명시한다.

해당 적용이 정확히 구현되어 있는 것은 다음의 예시에서 확인이 가능하다.

  • JDBC( DB변경이 설정에 대해 영향을 받지 않는다. )
  • JAVA( 사용 OS환경에 영향을 받지 않는다. )

3. LSP (Liskov Substitution Principle): 리스코프 치환 원칙

서브 타입은 언제나 자신의 기반타입으로 교체될 수 있어야 한다.

쉽게 말해 상속관계에 대한 설명이다. 일전에 상속에 대해 예를 들면서 다음과 같은 소스를 예로 들었다.

붕어빵틀 붕어빵=new 붕어빵틀();	// 1번
아버지 영희=new 딸();		// 2번
Vehicle car = new Car();	// 3번

위의 예시중 어떤 것이 맞는 표현일까? 상속은 'is a kind of'의 관계를 가진다고 했다. 

1번: 붕어빵 인스턴스는 붕어빵 틀의 한 분류이다.

2번: 영희라는 딸 인스턴스는 아버지의 한 분류이다.

3번: car라는 Car 인스턴스는 Vehicle의 한 분류이다.

LSP원칙의 서브타입은 자신의 클래스를 말하고 기반 타입은 상위 클래스를 뜻한다. 3번은 원래 다음과 같이 소스를 짜는게 일반적이다.

Car car = new Car();

하지만 Car를 기반타입인 Vehicle로 변경하여도 가능한 구조이다. 이처럼 리스코프 치환 원칙은 상속의 관계를 가진 경우 즉, 분류도의 관계를 가진 경우를 말한다.

 

4. ISP (Interface Segregation Principle): 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.

이제는 단순히 말로는 이해가 안될 지경이다. 계속사용하는 예제를 예로 들어 보자. 만약 탈것에 대한 회사를 유지하다 Bike사업이 망하게 되었다. Bike클래스를 제거한다면? 영향, 즉, 의존관계를 맺고 있는가? 정답은 No이다. SRP의 경우 클래스를 분할하여 지정해주는 원칙이라면 ISP의 경우는 인터페이스를 분할하여 사용하도록 하는 원칙이다. 아까의 예시로 도식화하면 다음과 같이 나타낼 수 있다.

ISP 원칙 적용 예

해당 원칙은 SRP 원칙 적용에 대한 서로 같지만 다른 해결 방법인 것이다. 하지만 이런식으로 많이 적용되어 있는 경우 상위 클래스의 속성이 적어진다는 말이기 때문에 불필요한 Casting이 많이 일어난다는 단점이 있다.

5. DIP (Dependency Inversion Principle): 의존 역전 원칙

추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.

Car클래스의 tire속성을 예로 들어보면 타이어에 종류가 많이 있다. 스노우 체인을 달은 타이어, 일반 타이어, 레이싱용 타이어 등이 있는데 만약 tire속성이 많이 변경이 된다면 tire속성에 대한 의존도는 심해지게 된다. 만약 이 tire가 하나의 클래스라고 가정을 해본다면 계절에 따라 변경해주어야 하는 하나의 클래스가 된다는 것이다. 만약 이 의존도를 낮추려면 어떻게 해야할까?

바로 타이어 인터페이스를 구성하여 추상적인 것에 타이어 속성의 하위 클래스가 의존하게 하면 된다. 이렇게 하나의 타이어 종류에 대한 클래스가 추상적인 타이어 전체 인터페이스에 의존하는 것이 의존 역전 원칙이다.

그 예는 다음과 같이 표현이 된다.

DIP적용 전

해당 그림처럼 자주 변경되는 주 클래스에 의존되기 보다. 다음과 같이 구체적인 것(하위 클래스)이 추상화된 것(인터페이스)에 의존하게 변경하라는 것이다.

DIP 적용 이후

해당 그림처럼 구체적인 SnowTire와 Normal Tire가 추사화된 것인 Tire인터페이스에 의존하는 것이 DIP원칙 중 하나이다.

 

참고자료: 스프링 입문을 위한 자바 객체 지향의 원리와 이해

'DEV > JAVA' 카테고리의 다른 글

String, StringBuffer, StringBuilder에 대한 설명  (0) 2021.07.17
String에 대하여...  (0) 2021.07.17
02. JAVA의 키워드와 연산자  (0) 2020.02.25
01. JAVA? 객체지향?  (0) 2020.02.24