상세 컨텐츠

본문 제목

소프트웨어 설계 5원칙인 SOLID를 알아보자.

IT이야기/소프트웨어 일반

by Rahs 2017. 1. 19. 20:05

본문

 본 블로그는 실질/객관을 지향합니다만 저는 부족함이 많은 사람이라 혹여 잘못된 정보가 있을 수 있습니다.

 혹여나 그런 부분을 발견하시면 그냥 가지 마시고 악플이라도 달아주세요! 고치겠습니다!


 오늘은 SOLID라는 소프트웨어 디자인 원칙에 대해 정리를 조금 해보겠습니다.

 

 객체지향 프로그래밍을 공부하다가 만나게 된 주제인데, 꼭 객체지향 소프트웨어 설계에 한정된 것은 아닙니다.

 소프트웨어 공학 또는 유관 전공을 공부하신 분들께서는 이미 익숙할대로 익숙한 주제이겠지만,

 실무자들이 많이 활동하는 포럼 등을 찾아보면, 원칙은 원칙인데 여러 가지 상황 변수들로 인해 실무에서 이것들이

 모두 갖춰지는 경우는 굉장히 드물다고 합니다.


 BUT 알고 있으면 나쁠 것은 없다고 생각해서 내용을 좀 살펴보고, 또 정리를 해보게 되었습니다.

 글 하나로 정리될만한 내용은 아니지만.. 기억을 되살리는 편린 역할을 할 정도로 쓰는 것이 목표이니만큼, 

 차례대로 살펴보도록 하겠습니다.




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


 객체는 반드시 "한 개의 책임"을 가져야 한다는 원칙입니다.

 이는 결국 객체의 응집도를 극대화하고, 객체 간 결합도를 최소화해야 한다는 고전 설계원칙(응집도△, 결합도▽)의 연장선 상에 놓인 원칙이라 생각됩니다. 한 가지 객체가 여러 책임을 가지고 수행할 경우, OOP 본연의 의미가 퇴색될 뿐더러, 필연적으로 결합도가 높아진다고 봅니다.

 그러면 재사용성이 악화되고, 산탄총 수술(Shotgun Surgery)을 해야 하는 일이 생길 수도 있고..


#2 Open-Closed Principle (OCP, 개방 폐쇄 원칙)


 설계된 객체는 각각이 확장은 가능하되(open), 변경할 필요는 없어야 한다(closed)는 의미를 가진, 이름 그대로의 원칙입니다.

 한번 설계가 되고 단위 테스트가 완료된 객체는, 향후 외부에 변경 사항이 발생하더라도 객체 자체는 변경되지 않아야 한다는 거죠.

 가장 쉽게 이해할 수 있는 예는 GOF의 디자인 패턴 중 스트래티지(Strategy) 패턴이라 생각됩니다.

 이를 방법론으로 접근하자면 객체가 외부 클래스를 참조할 필요가 있을 때, 클래스 자체를 참조하는 것이 아니라, 인터페이스를 참조하도록 하는 것입니다. 예를 들면 이런 겁니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Random;
 
public class DiceGame {
    public static void main(String[] args) {
        new Dice().roll();
    }
}
 
class Dice {
    public void roll(){
        System.out.println(new Random().nextInt(6+ + "이 나왔습니다.");
    }
}
cs


 6면체 주사위와 12면체 주사위를 상황에 따라 번갈아 가면서 던져야 하는 상황이라면 어떻게 수정을 해야 할까요.

 지금 설계로는 Dice 클래스에 어떤 식으로든 변경을 가해야만 하는 상황이라는 겁니다.


 반면 아래와 같이 수정했을 때의 장점을 생각해보신다면 OCP를 쉽게 받아들이실 수 있을 거라 생각합니다.

 이제 20면체 주사위가 추가되어도 일단 이미 설계된 인터페이스/클래스들은 영향을 받지 않을 수 있다는 것입니다.


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
import java.util.Random;
 
public class DiceGame {
    public static void main(String[] args) {
        new Dice6().roll();
        new Dice12().roll();
    }
}
 
interface Dice {
    public void roll();
}
 
class Dice6 implements Dice {
    @Override
    public void roll() {
        System.out.println(new Random().nextInt(6+ + "이 나왔습니다.");
    }
}
 
class Dice12 implements Dice {
    @Override
    public void roll() {
        System.out.println(new Random().nextInt(12+ + "이 나왔습니다.");
    }
}
cs



#3 Liskov Substitution Principle (LSP, 리스코프 치환 원칙)


 MIT 컴퓨터 공학과 리스코프 교수가 1987년에 제안한 원칙이라고 합니다. 상속관계에 있는 두 객체에 있어서, 부모 클래스의 인스턴스가 사용된 자리에 자식 클래스의 인스턴스를 집어 넣어도 코드의 맥락이 변하지 않아야 한다는 겁니다.

 이 부분은 굳이 아마 '당연한 소리 아닌가?'라고 하실텐데, 클래스 설계가 잘못된 경우, 그렇지 못한 경우도 있다고 합니다.

 LSP의 만족여부는 간단한 사고 실험으로 알아볼 수가 있는데, "자식 클래스 is a kind of 부모 클래스"의 참/거짓 여부를 알아보는 것입니다.


 부모 클래스가 동물이고, 자식 클래스가 돼지인 경우, "돼지 is a kind of 동물" 관계가 성립합니다.

 부모 클래스가 사람이고, 자식 클래스가 직장인인 경우, "직장인 is a kind of 사람" 관계는 성립하지 않습니다. 직장인은 사람의 역할(Role) 중 하나이지 사람의 종류(kind)가 아니기 때문입니다.


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


 간단하면서도 쉽지만은 않은, 인터페이스 분리 원칙입니다. 핵심은 객체 자신이 사용하지 않을 인터페이스는 구현하면 안 된다는 것입니다.

 휴대폰이라는 인터페이스가 있는데, 녹음기 객체를 생성하면서 녹음 기능을 사용하기 위해서 휴대폰이라는 인터페이스를 구현해서는 안 된다는 거죠.  가능한 경우라면 범용 인터페이스보다는 객체에 특화된 인터페이스를 분리해내어 구현해야 한다는 원칙이고, 그를 통해 얻어지는 이점은 역시 고전 설계원칙(응집도△, 결합도▽) 의 연장선 상에 있는 것으로 생각됩니다.


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


 개인적으로는 가장 이해하기 힘들었는데, 이는 '의존'의 개념에 대한 몰이해에 기인한 것으로 생각됩니다. 결국 의존이란 객체가 다른 객체의 기능이나 구조를 가져다 쓰면서 발생하는 것인데, '역전'이라는 말이 들어가서 혼란이 가중되는 것이 아닌가 하는 생각을 해봤습니다.

 도대체 뭐가 '역전'되었다는 것인지를 생각해보면 이는 프로그래밍 패러다임의 변화를 먼저 살펴볼 필요가 있었는데, 절차지향 프로그래밍이 훨씬 역사가 오래되었다는 것에 중점을 맞춰서 이를 '원조'로 본다면, 보다 이해가 편해질 수 있다고 생각하기 때문입니다.


 기존 절차지향 프로그래밍의 경우, 일반적으로 main 스레드와 같은 총괄 주체가 작업에 필요한 함수들을 호출합니다.

 즉, 보다 추상적인 객체가 보다 구체적인 객체를 호출함으로써 추상적인 객체가 구체적인 객체에 의존하게 되는 형태가 되는 것입니다.

 여기서 'Java랑은 반대네?'라는 의문이 생깁니다.

 Java에서는 구체적인 객체가 추상적인 객체를 상속함으로써 구체적인 객체가 추상적인 객체에 의존하게 되죠.

 그럼으로써 잦은 변화가 요구되는 구체적 객체의 변화에 전체적인 맥락이 영향을 덜 받을 수 있는,

 다시 말해, 자연스럽게 어느 정도 유연한 설계가 이루어지는 것이고 이것이 '바람직하다'는 것이 결국 DIP의 핵심이라 볼 수 있겠습니다.


 DIP의 경우 객체지향 언어를 다루시는 분이라면 다들 익히 알고 계시는 개념이라 생각됩니다만 이걸 말로 나타내자니 조금 표현이 난해한 원칙이 아니었나 싶습니다...




 초짜가 다룰 내용이 아니었는데 나름 공부를 위해서 한번 정리해본다고 정리해보게 되었습니다. 나중에 이 글이 개발자로서의 스스로에게 흑역사가 될 지 모른다는 생각이 강하게 듭니다만 글에는 '수정'기능이 있으니.. *_* 보다 깨달음이 늘어나면 추후 또 '수정'을 하면 된다고 자기 위안을 하며 정리를 마칩니다.


 스윗한 하루 되시기 바랍니다 :)

댓글 영역