본문 바로가기
설계/객체지향설계

[객체지향설계] SOLID 설계 원칙 (2) - OCP (개방 폐쇄 원칙)

by J4J 2024. 3. 4.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 solid 설계 원칙 두 번째인 ocp (개방 폐쇄 원칙)에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

이전 글

 

[객체지향설계] SOLID 설계 원칙 (1) - SRP (단일 책임 원칙)

 

 

반응형

 

 

OCP (개방 폐쇄 원칙) 란?

 

solid 설계 원칙에서 ocp가 의미하는 것은 소프트웨어 확장에는 열려있어야 하지만 수정에는 닫혀있어야 하는 것을 말합니다.

 

여기서 확장에는 열려있다는 것과 수정에는 닫혀있다는 것의 의미를 파악해봐야 합니다.

 

 

 

먼저 확장에 열려있다는 것은 새로운 요구사항이 생겼을 때 요구사항에 맞는 새로운 기능을 추가할 수 있어야 한다는 것을 의미합니다.

 

예를 들어 캐릭터를 성장하는 게임에서 직업이 전사 / 마법사 / 도적이 있는데 새로운 직업인 궁수를 새롭게 만들고자 할 때 추가하는 것에 있어 문제가 없어야 한다는 것을 의미합니다.

 

직업 요구사항
전사 기존 직업
마법사 기존 직업
도적 기존 직업
궁수 문제 없이 신규 직업 추가 가능

 

 

 

 

다음으로 수정에 닫혀있다는 것은 새로운 요구사항이 생겼을 때 모듈 자체가 가지고 있는 기존 기능에 대해서는 변경 사항이 발생되면 안 되는 것을 의미합니다.

 

위의 예시와 동일하게 전사 / 마법사 / 도적이라는 직업이 공격하는 기능에 대한 정의가 되어 있을 때 새로운 직업인 궁수를 새롭게 추가했을 경우 공격하는 기능에 대한 모듈과 기존 직업 관련 모듈들은 변경 사항이 발생되면 안 되는 것을 의미합니다.

 

모듈 변경사항
직업 공격 기능 모듈 발생하지 않음
전사 직업 모듈 발생하지 않음
마법사 직업 모듈 발생하지 않음
도적 직업 모듈 발생하지 않음
궁수 직업 모듈 궁수 직업에 대한 새로운 기능만 추가

 

 

 

 

OCP (개방 폐쇄 원칙)의 특징

 

그러면 ocp의 형태로 설계가 이루어질 때 얻을 수 있는 이점이 무엇인지에 대해 궁금하게 됩니다.

 

ocp를 이용한 설계를 했을때 이점은 다음과 같이 얘기해 볼 수 있습니다.

 

  • 추가 요구사항이 발생했을 때 기존 구현되어 있는 기능을 고려할 필요가 없음
  • 기존 구현 기능을 수정하지 않기 때문에 예상치 못한 side effect가 발생하지 않음
  • 코드 수정에 대한 복잡도가 줄어듬
  • 유연한 개발이 가능해짐

 

 

 

 

위의 예시에 대해 다시 한번 얘기해 보겠습니다.

 

궁수 직업이 새롭게 추가된다고 할 때 만약 직업의 공격 기능에 대한 정의나 또는 기존 직업들과 관련된 기능이 변경된다면 검토해야 될 사항이 많아집니다.

 

즉, 시간이 지날수록 지속적으로 직업이 더 생겨날 경우 코드에 대한 복잡도는 방대하게 증가할 것이고 그에 따른 side effect를 무시할 수 없게 됩니다.

 

 

 

하지만 만약 ocp가 올바르게 적용되어 있는 경우라면 새로운 직업을 추가하는 것을 보다 쉽게 구현할 수 있습니다.

 

기존 기능에 대한 고민은 할 필요가 없고 오직 새로운 직업에 대한 기능만 집중할 수 있기 때문에 유연한 개발이 가능하게 됩니다.

 

즉, 시간이 지날 수록 지속적으로 직업이 더 생겨나더라도 기존 직업과는 전혀 관련이 없기 때문에 효율적인 유지 보수가 가능하고 자연스럽게 재 사용성도 높아지게 될 겁니다.

 

 

 

 

OCP (개방 폐쇄 원칙) 적용

 

ocp를 적용하기 위해서는 oop의 특징 중 하나인 추상화의 개념을 활용해야 합니다.

 

즉, 추상화를 할 수 있도록 도와주는 인터페이스를 이용하는 것입니다.

 

 

 

ocp를 이용하여 설계를 하지 않을 경우 위의 예시와 관련된 소스 코드는 자연스럽게 if-else나 switch-case 구문을 많이 활용하게 될 겁니다.

 

하지만 추상화를 기반으로 한 ocp 설계를 한다면 if-else나 switch-case 구문을 사용하는 일이 줄어들 겁니다.

 

 

 

 

대신 그에 따른 대가가 오게 됩니다.

 

계속 얘기하는 것처럼 추상화를 기반으로 하기 때문에 인터페이스와 이를 상속받아 구현된 클래스들이 많이 생성됩니다.

 

그러므로 과도한 ocp의 사용은 오히려 코드 관리의 복잡성을 제공할 수 있기에 개발되는 서비스의 구조에 맞게 올바르게 적용할 필요가 있습니다.

 

 

 

 

예제 코드 (1) - Bad Case

 

위에서 말한 예시를 코드화시켜보겠습니다.

 

먼저 bad case에 대해 작성하면 다음과 같이 구성할 수 있습니다.

 

// main
package com.jforj.solidocp.badcase;

public class Main {
    public static void main(String[] args) {
        User user = new User();

        // 전사
        String warrior = "warrior";
        user.attack(warrior);

        // 마법사
        String wizard = "wizard";
        user.attack(wizard);

        // 도적
        String thief = "thief";
        user.attack(thief);
    }
}


// user
package com.jforj.solidocp.badcase;

public class User {

    public void attack(String job) {
        switch (job) {
            case "warrior": {
                System.out.println("칼을 이용하여 공격합니다.");
                break;
            }

            case "wizard": {
                System.out.println("지팡이를 이용하여 공격합니다.");
                break;
            }

            case "thief": {
                System.out.println("단검을 이용하여 공격합니다.");
                break;
            }

            default: {
                System.out.println("공격을 하지 않습니다.");
                break;
            }
        }
    }
}

 

bad case 결과

 

 

 

 

여기서 궁수 직업을 추가한다고 가정하면 다음과 같이 코드가 변경될 수 있습니다.

 

// main
package com.jforj.solidocp.badcase;

public class Main {
    public static void main(String[] args) {
        User user = new User();

        // 전사
        String warrior = "warrior";
        user.attack(warrior);

        // 마법사
        String wizard = "wizard";
        user.attack(wizard);

        // 도적
        String thief = "thief";
        user.attack(thief);

        // 궁수
        String archer = "archer";
        user.attack(archer);
    }
}


// user
package com.jforj.solidocp.badcase;

public class User {

    public void attack(String job) {
        switch (job) {
            case "warrior": {
                System.out.println("칼을 이용하여 공격합니다.");
                break;
            }

            case "wizard": {
                System.out.println("지팡이를 이용하여 공격합니다.");
                break;
            }

            case "thief": {
                System.out.println("단검을 이용하여 공격합니다.");
                break;
            }

            case "archer": {
                System.out.println("활을 이용하여 공격합니다.");
                break;
            }

            default: {
                System.out.println("공격을 하지 않습니다.");
                break;
            }
        }
    }
}

 

bad case 결과

 

 

 

 

위의 코드와 같이 궁수 직업이 새롭게 추가되었지만 사용자가 공격하는 기능에 대한 정의가 변경된 것을 볼 수 있습니다.

 

하지만 ocp 개념에서는 공격하는 기능에 대한 정의를 수정하는 것이 닫혀 있어야 합니다.

 

왜냐하면 현재 코드는 단순하지만 내부 비즈니스 로직이 복잡하게 얽혀있다면 유연한 코드 변경이 불가능하기 때문에 새로운 기능 추가를 어렵게 만드는 결과를 만듭니다.

 

 

 

 

예제 코드 (2) - Best Case

 

이번엔 bad case에 ocp 설계를 적용했을 때 어떻게 달라지는지 확인해 보겠습니다.

 

먼저 궁수 직업이 추가되기 전 코드 작성은 다음과 같이 변경됩니다.

 

// main
package com.jforj.solidocp.bestcase;

public class Main {
    public static void main(String[] args) {
        User user = new User();

        // 전사
        Warrior warrior = new Warrior();
        user.attack(warrior);

        // 마법사
        Wizard wizard = new Wizard();
        user.attack(wizard);

        // 도적
        Thief thief = new Thief();
        user.attack(thief);
    }
}


// character
package com.jforj.solidocp.bestcase;

public interface Character {

    void attack();
}


// warrior
package com.jforj.solidocp.bestcase;

public class Warrior implements Character {

    @Override
    public void attack() {
        System.out.println("칼을 이용하여 공격합니다.");
    }
}


// wizard
package com.jforj.solidocp.bestcase;

public class Wizard implements Character {

    @Override
    public void attack() {
        System.out.println("지팡이를 이용하여 공격합니다.");
    }
}


// thief
package com.jforj.solidocp.bestcase;

public class Thief implements Character {

    @Override
    public void attack() {
        System.out.println("단검을 이용하여 공격합니다.");
    }
}


// user
package com.jforj.solidocp.bestcase;

public class User {

    public void attack(Character character) {
        character.attack();
    }
}

 

best case 결과

 

 

 

 

이번에도 여기서 궁수 직업이 추가되었다고 가정하면 다음과 같이 코드를 작성할 수 있습니다.

 

// main
package com.jforj.solidocp.bestcase;

public class Main {
    public static void main(String[] args) {
        User user = new User();

        // 전사
        Warrior warrior = new Warrior();
        user.attack(warrior);

        // 마법사
        Wizard wizard = new Wizard();
        user.attack(wizard);

        // 도적
        Thief thief = new Thief();
        user.attack(thief);

        // 궁수
        Archer archer = new Archer();
        user.attack(archer);
    }
}


// character
package com.jforj.solidocp.bestcase;

public interface Character {

    void attack();
}


// warrior
package com.jforj.solidocp.bestcase;

public class Warrior implements Character {

    @Override
    public void attack() {
        System.out.println("칼을 이용하여 공격합니다.");
    }
}


// wizard
package com.jforj.solidocp.bestcase;

public class Wizard implements Character {

    @Override
    public void attack() {
        System.out.println("지팡이를 이용하여 공격합니다.");
    }
}


// thief
package com.jforj.solidocp.bestcase;

public class Thief implements Character {

    @Override
    public void attack() {
        System.out.println("단검을 이용하여 공격합니다.");
    }
}


// archer
package com.jforj.solidocp.bestcase;

public class Archer implements Character {

    @Override
    public void attack() {
        System.out.println("활을 이용하여 공격합니다.");
    }
}


// user
package com.jforj.solidocp.bestcase;

public class User {

    public void attack(Character character) {
        character.attack();
    }
}

 

best case 결과

 

 

 

 

코드를 확인해 보면 추상화를 활용하기 위해 인터페이스를 사용했고 그에 맞는 구현체들이 다양하게 생성된 것을 볼 수 있습니다.

 

또한 궁수 직업이 새롭게 추가되었을 때 공격하는 기능에 대한 정의와 각 직업에 대한 기능의 변경점은 전혀 없는 것을 볼 수 있습니다.

 

이처럼 유연하게 새로운 기능을 추가할 수 있기에 상황에 맞게 효율적으로 ocp를 적용하는 것은 프로젝트 구조를 잡는데 많은 도움이 될 수 있습니다.

 

 

 

 

 

 

 

 

 

이상으로 solid 설계 원칙 두 번째인 ocp (개방 폐쇄 원칙)에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글