티스토리 뷰

1. SRP - 단일책임원칙

클래스나 메서드는 하나의 역할만을 하며 한가지 책임만을 진다.

A 클래스가 존재하고 해당 A클래스는 B 클래스와 C 클래스에서 사용된다.


예)

class B{

  main(){

    A a = new A();


    a.getName();

  }

}


class C{

  main(){

    A a = new A();


    a.getId();

  }

}


A 클래스는 B와 C에서 사용되지만 각각 다른 메서드를 호출하며 '다른 역할'을 하고있다.

이경우 A클래스는 B에서 사용되는 역할때문에 수정될 수도 있고 C에서 사용되는 역할때문에 수정될 수도 있다.

이런경우에는 A클래스를 나눠 한가지 역할만을 행하게 해야한다.

메서드도 소스를 보다보면 if문같은 분기문으로 나뉘어서 완전히 다른 로직으로 진행되는 경우를 흔히 볼 수 있다.

이런 경우도 메서드를 나누는게 올바른 설계다.


2. OCP - 개방폐쇄원칙

클래스는 확장에는 열려있지만 변경에는 닫혀있다.

마우스를 생산하는 업체는 다양하다. 하지만 우리는 로지텍 마우스를 쓰든 MS마우스를 쓰든 새로운 사용법을 익힐 필요는 없다. 생산업체는 다르지만 동일한 인터페이스를 제공하기때문이다.

인터페이스라는 단어에서 느꼈듯이 OCP는 인터페이스를 적극적으로 사용하자는 원칙이다(아래에서 보면 알겠지만 사실 객체지향 설계자체가 대부분 인터페이스를 사용하자는 것이다.). List 구현 클래스로 보자면


ArrayList list = new ArrayList();

list.add(1);

list.add(2);


LinkedList list =  new LinkedList();

list.new(1);

list.new(2);


이런식으로 ArrayList 와 LinkedList 가 서로 값을 추가하는 방식이 다르면 사용 List를 교체할시 해당 클래스 내부에 고쳐야할 코드가 한두줄이 아닐수도있다.

하지만 List 인터페이스를 제공하고 ArrayList 와 LinkedList가 List를 구현함으로서 동일한 메서드를 제공하기때문에 List를 교체할 일이 생기더라도 객체생성부분만 고쳐주면 나머지는 수정할 필요가 없다.

이처럼 사용하는 클래스는 변화에 닫혀있고 사용되는 클래스는 확장에 열려있는것이 OCP.


3. LSP - 리스코프 치환 원칙

하위클래스는 언제나 상위클래스로 참조가 가능하다.

이는 앞서 포스팅한 객체지향의 특성중 상속과 관련이 있다.

상속을 계층도로 배우게되면 상속이 되면 될수록 하위 클래스의 메서드는 늘어만 가고, 그렇게되면 상위클래스에서 하위클래스를 참조하는데 있어 매우 많은 불편함이 따르게 된다.


예)

class ArrayList implements List{

  @Override

  ~~~~~


  public void addItem(){}

  public void addList(){}

}


List list = new ArrayList();


이런식으로 List에서 제공하는 추상메서드를 당연히 오버라이딩하게되고 그에 더하여 신규 메서드들을 추가하게되면 상위클래스(인터페이스)인 List로 참조하게될시 신규메서드를 사용하는데 있어 제약을 받게된다. 이런경우는 LSP를 위반한 것이다.

물론 인터페이스의 추상메서드를 구현하고 필요한 메서드를 추가하는것은 컴파일 에러가 발생하는것도 아니고 제약이 따르는것도 아니기때문에 절대 하면 안된다거나 하는것은 아니다. 절대 하면 안되는거라면 애초에 컴파일 단에서 인터페이스 구현 클래스는 해당 인터페이스가 제공하는 메서드만 구현할수 있도록 했을 것이다.

하지만 그렇다고 무분별하게 신규메서드들을 추가하게되면 사실 인터페이스를 사용하는 이유가 없어지기때문에 신중히 설계해 언제든 상위클래스가 하위객체를 참조할 수 있도록 만드는 것이 바람직하다.


4. ISP - 인터페이스 분리 원칙

사용클래스는 사용하는 클래스를 최소한의 인터페이스로 참조해야한다.

개인적으로 5개 원칙중 가장 난해한 원칙이다. 이해한게 맞는건지도 확신이 안들고 만약 틀린내용이 있다면 지적해주기 바란다.

SRP와 비슷한 내용인데 SRP가 다중 역할을 하는 클래스의 분리를 얘기한다면 ISP는 인터페이스의 분리를 얘기한다.


예)

interface AB{

  void getName();

}


interface AC{

  void getId();

}


class A implements AB, AC{}


class B{

  main(){

    AB a = new A();


    a.getName();

  }

}


class C{

  main(){

    AC a = new A();


    a.getId();

  }

}


SRP에서 봤던 클래스를 인터페이스를 분리하고 분리한 인터페이스들을 한번에 구현하게끔 바꿨다.

A는 여전히 두가지 역할을 하고있지만 A를 사용하는 B, C클래스에서는 분리된 서로 다른 타입으로 A객체를 참조하고있기때문에 B에서는 getId()를, C에서는 getName()을 사용할 수 없다.

SRP와 동일한 목표를 추구하면서 서로 다른 방식을 제안하고있기때문에 다중 역할을 하는 클래스를 보게되면 어떤식으로 나눠야 할지 고민이 될 수 있다. ISP보다는 SRP가 더 직관적이기때문에 SRP를 먼저 고려하는게 좋아보인다.


5. DIP - 의존성역전 원칙

DI. 어디서 많이 들어본 단어다. Spring을 공부해본적이 있다면 그리 낯선 단어는 아닐것이다. 물론 DIP의 I와 스프링 DI에서의 I는 다른 단어지만 DIP(의존성역전원칙)를 준수하기위해 스프링에서 DI(의존성주입)를 구현한 것이므로 크게 다르지않은 단어다.

객체지향을 공부하고 스프링을 공부하다보면 의존이란 단어가 꾸준히 등장하는데 의존이란 결국 사용하는 클래스와 사용되는 클래스의 관계이며 '사용하는 클래스가 사용되는 클래스에 의존하고있다' 라고 이해하면 된다. 처음에는 이해하기 어려울수도 있는데 간단히 생각해서 사용되는 클래스가 수정되면 사용하는 클래스도 필연적으로 수정을 해야하기때문에 그걸 의존하고있다라고 표현하는 것이다.


예)

class A{

  main(){

    B b = new B();

    b.getName();

  }

}


class B{

  public void getName(){

    return "LichKing";

  }

}


사용하는 클래스는 A. 사용되는 클래스는 B이다. B에서 getName() 메서드의 명칭이 바뀌게되면 해당 메서드를 호출하는 A는 당연히 강제적으로 바꿔야된다. 실행하고 싶으면.

명칭까진 안바뀌더라도 B의 getName()의 로직이 수정되게되면 A클래스에서도 해당 수정부분을 확인을 해야한다. 의존하고있기때문이다.


객체지향설계, 올바른 설계란 결국 유지보수하기 편한 코드를 작성하자는 것이고, 궁극적으로 소스를 수정할 일이 생기면 최대한 적은 부분만을 수정하자는 것이다.

사소한 부분일지라도 여기저기 수정하게되면 개발자도 사람이라 어디서 예상치못한 일이 발생할지 모르기때문이다.


의존성역전이란 결국 이 의존성을 최대한 느슨하게 해보자는것이고 그것에 대한 해결책으로 인터페이스를 사용하는것이다.

하나의 인터페이스로 여러 구현체를 만들어 사용하고 LSP를 준수한다면 유지보수중에 구현체를 교체해도 교체부분 말고는 수정할 부분이 적을것이다.


한 가지 덧붙인다면 스프링의 DI는 이런 교체부분조차 한군데서 수정하기 위해 객체 조립기를 갖다놓고 사용하는 클래스에서 new 연산자로 객체를 생성하는게 아니라 조립기에서 생성한 객체를 return 받는 형식으로 '주입'(I, Injection)을 하게한다.


마치며

아직 많이 부족하고 부족한걸 해소하고자 공부중인 2년차 개발자입니다. 책과 고수분들의 블로그를 통해 배움을 얻고있고 그것에 대해 저만의 정리과정을 통해 블로그에 포스팅을 하고있습니다.

이번 포스팅에 굳이 마치며 라는 부분을 집어넣는것은 사실 작성 하면서도 나 스스로가 잘 이해한건지 혹시 잘못된 정보를 전달하진 않을지에 대한 두려움과 이번만큼은 제가 정리한것에 대해 자신감이 떨어지기때문입니다. 혹여나 틀린부분이있으면 지적해주시면 감사하겠습니다.

'기타 프로그래밍' 카테고리의 다른 글

TDD  (1) 2016.01.20
테스트의 종류  (0) 2015.12.03
객체지향설계 5대 원칙 SOLID  (1) 2015.09.20
자바스크립트 변수에 php값 넣기  (0) 2015.09.15
객체지향 4대 특성  (0) 2015.09.06
log4j log level  (0) 2014.10.13
공유하기 링크
TAG
댓글
  • 프로필사진 homekeeper89 예시가 직관적이면서 와닿는 형태라 5대 원칙 설명 중에 제일 이해하기 쉬웠네요. 감사합니다. 2020.09.10 01:07
댓글쓰기 폼