--> 10 [객체 지향 언어의 이해] 자바 객체 설계 원칙 - SOLID

10 [객체 지향 언어의 이해] 자바 객체 설계 원칙 - SOLID

SRP - 단일 책임 원칙


 모든 객체는 하나의 책임만을 가지면 객체가 제공하는 서비스는 하나의 책임을 수행하는데 집중 되어야 한다.

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

 

단일 책임 원칙이 잘 자켜지지 않은 경우는 분기 처리를 위한 if문으로 코드를 보도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
    
    public String job;
    public Person(String job)
    {
        this.job = job;
    }
    
    public void Work()
    {
        if(job.equals("Programmer"))
            System.out.println("코딩하다");
        else if(job.equals("teacher"))
            System.out.println("수업을 하다.");    
    }
}
 

 

다음 Person 클래스는 Work() 메서드를 제공하는데 그 사람의 직업에 따라 분기를 하고 있습니다.

Person 클래스는 딱 봐도 두 개의 직업에 대한 '책임'을 지고 있는 모습을 볼 수 있습니다.

 

이 코드를 SRP 원리를 적용하여 리펙토링을 해보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Person {
    abstract public void Work();
}
 
 
public class Programmer extends Person {
    public void Work()
    {
        System.out.println("개발을 하다");
    }
}
 
public class Teacher extends Person{
    
    public void Work()
    {
        System.out.println("학생을 가르치다");
    }
}

 

Person 클래스를 추상 클래스로 선언하고 하나의 추상 메서드 구현을 강요합니다.

Programmer와 Teacher 클래스로 나누고 Person을 상속받아 Work() 메서드를 구현하게 됩니다.

Person 클래스는 이제 여러개의 책임에서 벗어난 형태가 되었습니다. Person 클래스는 다만 여러 직업을 가지는

사람의 최상위 클래스이며 Work() 메서드 구현을 강요만 하면 됩니다.

 

 

OCP - 개방 폐쇄 원칙


소프트웨어 엔티티는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 합니다.

 

사용자가 A파서를 통하여 HTML 파싱을 하는 기능을 수행한다고 가정합니다. A파서를 열심히 사용하던 중 B파서를 사용해야 할 일이 있어서 B파서를 사용 할려는데 A파서와는 사용방식이 다릅니다.

  

그림과 같이 사용자는 파서의 종류에 따라 변경 되어야 합니다.

 

개방 폐쇄 원칙을 적용하여 파서에 대한 공통 인터페이스를 정의한 후 A파서에서 사용하도록 바꿉니다.

 

여기서 사용자 입장에서는 파서의 변경에 대해서는 닫혀 있고 파서 입장에서는 변경(확장)에 대해서는 열려 있도록 설계를 해야합니다.

 

 

LSP - 리스코프 치환 원칙


서브 타입은 언제나 자신의 기반 타입으로 교체 할 수 있어야 합니다. 즉 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Person {
    abstract public void Work();
}
 
 
public class Programmer extends Person {
    public void Work()
    {
        System.out.println("개발을 하다");
    }
}
 
public class Teacher extends Person{
    
    public void Work()
    {
        System.out.println("학생을 가르치다");
    }
}

 

Person 클래스의 하위 클래스로 Programmer와 Teacher가 옵니다.

Programmer가 사람 역할을 수행한다와 Teacher가 사람 역할을 수행한다. 문맥상으로 어색하지 않습니다.

 

1
2
3
4
5
6
7
8
9
10
11
public class Main {
    
    public static void main(String argsp[])
    {
        Person person1 = new Programmer();
        person1.Work();
        
        Person person2 = new Teacher();
        person2.Work();
    }
}

 

실제로 상위 클래스의 객체 참조 변수에 하위 클래스의 인스턴스를 대입해도 아무런 문제가 발생하지 않습니다.

 

 

ISP - 인터페이스 분리 법칙


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

 

위에서 보았던 Person 클래스를 Programmer와 Teacher역할을 수행하는 클래스로 분리 하도록 설계를 하였습니다. 위 방식 말고 ISP 원칙을 적용하는 방법으로 Person을 클래스로 분리하는게 아니라 인터페이스를 통해 역할을 구분하는 방식입니다.

 

인터페이스 분리 원칙을 이야기 할때 함께 등장하는 원칙 중 하나로 인터페이스 최소주의 원칙입니다.

 

즉 상위 클래스는 풍부할수록 좋고, 인터페이스는 작을 수록 좋다는 의미입니다.

 

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
32
public class Programmer extends Person {
    public void Work()
    {
        System.out.println("개발을 하다");
    }
    public void Eating()
    {
        System.out.println("먹다");
    }
    public void Sleeping()
    {
        System.out.println("자다");
    }
}
 
public class Teacher extends Person{
    
    public void Work()
    {
        System.out.println("학생을 가르치다");
    }
    public void Eating()
    {
        System.out.println("먹다");
    }
    
    public void Sleeping()
    {
        System.out.println("자다");
    }
}
 

 

소스를 살펴보면 Programmer와 Teacher에는 '먹다'와 '자다' 메서드가 둘다 구현되어 있습니다. '먹다'와 '자다'는 사람으로서 공통적으로 가지는 기능으로 상위 클래스인 Person 클래스에서 구현을 하고 하위 클래스에서는 재사용 개념으로 쓰게 하면 더 좋은 설계가 된다는 것입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Person {
    
    abstract public void Work();
    
    public void Eating()
    {
        System.out.println("먹다");
    }
    
    public void Sleeping()
    {
        System.out.println("자다");
    }
}
 

 

다음과 같이 상위 클래스에서 공통 부분에 대해서 구현을 하고 (풍성할수록 좋다), 하위 클래스가 따로 구현 할 수 밖에 없는 메서드에 대해서만 구현을 강요하도록 (인터페이스가 작을수록 좋다) 합니다.

 

 

DIP - 의존 역전 법칙


고차원 모듈은 저차원 모듈에 의존해서는 안되고 추상화된 것은 구체적인 것에 의존하면 안됩니다.

즉 자주 변경이 되는 클래스에 대해서 의존하지 말라는 것입니다.

 

억지스럽지만 예를 들어보겠습니다.

 

 

어떤 사람이 여름옷을 입고 있다고 가정해보겠습니다. 사람이 여름옷에 의존한다고 볼수 있습니다.

문제는 계절이 지나면 그 계절에 맞는 옷을 입어야 한다는 것이죠. 즉 여름옷은 자주 변경이 될 수 있는 클래스라는 점입니다.

 

 

사람이 구체적인 '여름 옷'에 의존적인 것보다 추상화된 '옷'이라는 인터페이스에 의존하도록 '여름옷'이든 '겨울옷'이든 교체가 이루어져도 사용자에겐 영향을 받지 않도록 설계를 합니다.

 

 

 

댓글(1)

  • 지나가던 행인
    2020.12.03 19:04

    글 좋네요 잘보고 갑니다!

Designed by JB FACTORY