Post

객체지향의 특징, 설계 원칙

개론

어딘가에 갔을 때 그곳의 문화, 풍습에 맞게 행동하라는 말을 비유적으로 할 때 ‘로마에 가면 로마법을 따르라’ 라는 글귀를 언급하곤 한다.

그렇다면 우리는 객체지향의 세계에 입문했다면, 무조건 객체지향의 원칙들을 지켜야 하는 것일까? 물론 반드시 지킬 필요는 없다. 범법행위도 아니라서 잡혀갈 일도 없다. 그런데 왜 우리는 객체지향의 원칙들을 공부하는 것일까?

‘원칙’이라는 명칭이 붙은 이론이 있다면, 이미 널리 통용되고 검증되어 있을 가능성이 매우 높다. 활자의 발명으로 인해 선조들의 시행착오를 전수받을수 있었듯이, 우리의 선배 개발자들도 이러한 이론들을 후배들에게 남기면서 더 좋은 설계, 개발 방법에 대한 지름길을 제시하는 것이라 생각한다.

때로는 탄생 배경의 발자취를 그대로 따라가보는 것이 더 효과적인 방법이 될 수 있지만, 모든 이론을 그렇게 학습하다가는 우리가 후대에게 물려줄 것들은 그저 선대의 유산뿐일수 있다.

객체지향의 4가지 특징

추상화

다운로드

어떠한 자료나 시스템 등으로부터 핵심적이거나 공통적인 개념, 기능을 간추려 내는 것을 말한다.

일상 속에서 대표적인 추상화의 대표적인 예시로는 지하철 노선도가 있다. 지하철 노선도와 실제 지도상의 역 배치는 매우 다른 형태이다. 하지만 우리가 노선도를 통해 얻고자 하는 정보는 어느 역에서 환승을 해야 하는지, 몇 정거장을 더 가야하는지 등의 정보이지 정확한 서울 지도상의 지하철 배치를 보고자 하는 것이 아니다.

추상화는 이처럼 불필요한 부분들을 간소화하고, 중요한 속성들에 집중하는 과정이다.

캡슐화

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
class 감기약 implements  {


    // 노출되는 부분
    public void 사용하기(){
        물과함께삼키기();
    }


    // 내부 구현, 외부에서는 접근할 수 없다
    private void 물과함께삼키기(){

    }

}


class 바르는약 implements  {


    // 노출되는 부분
    public void 사용하기(){
        상처부위에_바르기();
    }


    // 내부 구현, 외부에서는 접근할 수 없다
    private void 상처부위에_바르기(){

    }

}

캡슐화란 클래스 내부의 속성과 기능들을 은닉해 외부로부터 보호하는 것을 말한다.

우리가 병원에서 처방받은 캡슐 알약을 생각해보면 이해가 쉽다. 우리는 처방받은 약을 ‘먹는다’는 행위만 할 뿐이지, 알약 내부에 어떤 성분의 가루들이 있는지, 어떤 조합으로 만들어져 나의 병을 완화시키는 것인지는 알 수 없다.

이러한 캡슐화을 활용, 내부 구현을 숨김에 따라 객체를 이용하는 사람은 기능 이용에만 집중할 수 있고 기능 구현자는 변경에 조금 더 유연하게 대처할 수 있다.

다형성

다형성이란 객체의 속성, 기능이 상황에 따라 다양한 형태를 가질 수 있는 성질을 의미한다. 위의 코드를 다시 한번 살펴보자.

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
33
34
interface  {
    void 사용하기();
} 

class 감기약 implements  {

    @Override
    public void 사용하기(){
        // ...
    }

}


class 바르는약 implements  {

    @Override
    public void 사용하기(){
        // ..
    }

}


class Main {
    public static void main(String[] args){
         감기약 = new 감기약();
        감기약.사용하기();

         바르는약 = new 바르는약();
        바르는약.사용하기();
    }
}

인터페이스에 정의된 메소드를 오버라이딩 하면서 각각의 클래스의 맥락에 맞게 행위를 정의했다. 먹는 약과 바르는 약은 ‘약’이라는 상처를 낫게하는 공통적인 특성이 있지만 사용하는 방법은 다르기 때문에, 같은 이름의 메소드이지만 상황에 따라 다른 방식으로 역할을 수행할 수 있게 해야 한다.

결국 다형성이란 ‘한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것’을 의미하며 상위 타입의 참조변수로 하위 객체를 참조할 수 있도록 하는 것이다.

상속

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
33
34
35
36
37
38
class  {
    
    public void 사용하기(){
        System.out.println("약을 먹었다!");
    }
} 

class 알약 extends  {

}

class 시럽약 extends  {

}


class 바르는약 extends  {

    @Override
    public void 사용하기(){
        System.out.println("약을 상처부위에 발랐다!");
    }

}

class Main {
    public static void main(String[] args){
        알약 감기약 = new 알약();
        감기약.사용하기();

        바르는약 후시딘 = new 바르는약();
        후시딘.사용하기();
    }

    // 출력
    // 약을 먹었다!
    // 약을 상처부위에 발랐다!
}

상속이란 상위 클래스의 기능들을 이어받아 재활용하거나 새로 정의할 수 있게 하는 행위이다.

비슷한 속성의 클래스들끼리 반복적으로 사용되는 기능, 속성들을 정의해두고 재사용하면서 코드의 반복을 줄일 수 있다.

객체지향의 5가지 설계 원칙(SOLID)

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

하나의 클래스는 하나의 기능만 수행해야 한다는, 한 가지 업무에만 책임을 가져야 한다는 원칙이다.

물론 이 기능, 책임이란게 상황마다 사람 생각마다 달라질 수 있기 때문에 절대적인 기준은 존재하지 않지만 하나의 클래스가 경계없이 여러 기능을 수행한다면 프로그램의 유지보수성이 현저히 낮아진다.

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

클래스는 기능 확장에 열려있어야 하고, 수정에는 닫혀있어야 한다는 원칙이다.

새로운 기능을 추가해야 할 때 손쉽게 확장할 수 있어야 하지만 이에 따른 추가적인 수정이 최소화 되어야 한다는 뜻이다. 코드로 예시를 살펴보자.

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
class Car {
	String type;
    
    Car(String type) {
    	this.type = type;
    }
}

class DriveCar {
    void drive(Car car) {
        if(car.type.equals("스포츠카")) {
            System.out.println("부우우우웅");
        } else if(car.type.equals("경운기")) {
            System.out.println("덜덜덜덜");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        DriveCar driveCar = new DriveCar();
        
        Car sports = new Car("스포츠카");
        Car duldul = new Car("경운기");

        driveCar.drive(sports); // 부우우우웅
        driveCar.drive(duldul); // 덜덜덜덜
    }
}

위의 코드는 변경과 수정에 굉장히 취약한 구조를 가지고 있다.

차의 종류가 추가될때마다 기존의 if 분기에 코드를 계속 추가, 수정 작업을 거쳐야 한다. 만약 차의 종류가 100가지만 넘어가도 if절의 코드는 가독성이 현저히 안 좋아질 것이며 유지보수 또한 몹시 어려워진다.

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
33
34
35
interface Car {
	void drive();
}

class SportsCar implements Car{
    void drive() {
      System.out.println("부우우우웅");
    }
}

class FarmerCar implements Car{
    void drive() {
      System.out.println("덜덜덜덜");
    }
}

class DriveCar {
    void drive(Car car) {
        car.drive();
    }
}

public class Main {
    public static void main(String[] args) {
        DriveCar driveCar = new DriveCar();
        
        Car sports = new SportsCar();
        Car duldul = new FarmerCar();

        driveCar.drive(sports); // 부우우우웅
        driveCar.drive(duldul); // 덜덜덜덜
    }
}

위와 같이 구성하게 된다면 기능을 추가해야 할 때 새로운 클래스에 대해서만 집중하면 되고 기존 코드의 수정에 대해 크게 걱정하지 않아도 된다.

리스코프 치환 원칙 - LSP (Liskov Substitution Principle)

화면 캡처 2023-10-01 151646

자료형 S가 자료형 T의 서브타입라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙이다.

쉽게 풀어쓰자면 상위 클래스 타입으로 객체를 선언, 하위 클래스로 인스턴스 생성 후 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 한다는 것이다.

1
2
3
4
5
6
void main() {
    Collection data = new ArrayList();
    data = new HashSet(); 
    
    data.add(1); 
}

위의 코드에서는 Collection 이라는 최상위 클래스 타입으로 변수 선언 후 구현체는 교체되었지만 모두 Collection의 서브 클래스들이기 때문에 의도한대로 동작하는 것을 확인할 수 있다.

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

인터페이스를 사용처에 맞게 적절한 단위로 분리해야한다는 원칙이다. 단일 책임 원칙(SRP)의 주체가 클래스였다면 ISP는 그 주체가 인터페이스로 변경되었다고 생각하면 된다.

인터페이스를 목적에 맞게 잘 설계, 분리하여 용도에 적합한 인터페이스를 제공하여 차후에 수정사항 발생 시 인터페이스 자체가 변경되는 행위를 방지해야 한다.

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

클래스를 참조하는 상황에서 구현체를 직접 참조하지 말고 상위 요소(추상 클래스, 인터페이스)를 참조하라는 원칙이다.

의존 관계 형성 시 구현 클래스는 변화가 잦을 수 있기 때문에, 비교적 변화가 발생하지 않는 상위의 인터페이스나 추상 클래스 등에 의존하는 것을 권장하는 원칙이다.

참고

  • 위키백과, SOLID (객체 지향 설계), https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%84%A4%EA%B3%84)
  • 심플에디, 쉽게 설명하는 객체 지향 프로그래밍의 본질, https://www.youtube.com/watch?v=zgeCwYWzK-k
  • 코드스테이츠, 객체 지향 프로그래밍의 4가지 특징ㅣ추상화, 상속, 다형성, 캡슐화, https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95
  • inpa, 객체 지향 설계의 5가지 원칙 - S.O.L.I.D, https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID
This post is licensed under CC BY 4.0 by the author.

© . Some rights reserved.

Using the Jekyll theme Chirpy