JAVA/ETC

[객체 지향 설계 5원칙 - SOLID] 2.OCP

자이구 2023. 12. 8. 11:54

Open Closed Principle : 개방 폐쇄 원칙

 

개방 폐쇄 원칙은 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있고, 변경에는 닫혀 있어야 한다.

즉, 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다. 

 

여기서 확장이란 새로운 기능이 추가, 모듈의 확장성을 의미한다 

변경에 닫혀 있다는 것은 객체를 직접적으로 수정하는건 제한해야 한다는 것을 의미한다. 

 

 

어느 날 한 운전자가 차량을 마티즈에서 쏘나타로 교체를 했다고 가정해 보자.

수동 동작에서 자동 동작으로 기능이 변경됨에 따라 운전자는 차량에 따른 운전하는 습관을 바꿔야만 하는 것일까?

차량으로 바꿨다고 해서 운전자가 운전에 영향을 받아야만 하는가?

 

어렵게 생각할 필요없이, OCP 원칙은 우리가 객체 지향 프로그래밍을 추상화를 의미하는 것으로 보면 된다. 

 

상위 클래스 또는 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다 해도 운전자는 운전 습관에 영향을 받지 않는다.

다양한 자동차가 생긴다고 하는 것은 자동차 입장에서 자신의 확장에 개방돼 있는 것이고, 

운전자 입장에서는 주변의 변화에 폐쇄돼 있는 것이다.

 

즉, OCP는 다형성과 확장을 가능케 하는 객체지향의 장점을 극대화하는 설계 원칙으로써, 

클래스를 추가해야한다면 기존 코드를 크게 수정할 필요없이, 적절하게 상속 관계에 맞춰 추가만 한다면

유연하게 확장을 할 수 있었던 것이다. 


OCP 원칙을 따른 JDBC

 

OCP 원칙의 가장 좋은 예시가 바로 데이터베이스 인터페이스, JDBC이다. 

 

JDBC를 사용하는 클라이언트(자바 어플리케이션)은 데이터베이스가 바뀌더라도 Connection을 설정하는 부분 외에는 

수정할 필요가 없다. Connection 설정 부분을 별도의 설정 파일로 분리해두면 클라이언트 코드는 다 한 줄도 변경할 필요가 없다. 

 

즉 클라이언트는 데이터베이스라고 하는 주변의 변화에 닫혀 있는 것이다. 

데이터베이스는 손쉽게 교체한다는 것은 데이터베이스가 자신의 확장에는 열려 있다는 말이 된다. 

 


OCP 원칙에 따른 Java

자바에도 개방 폐쇄 원칙이 적용돼 있다. 

 

자바 개발자는 작성하고 있는 코드가 윈도우에서 구동될지, 리눅스에서 구동될지 

OS 상에서 구동될지에 대해서는 걱정하지 않는다. 각 OS별 JVM과 목적파일(.class)이 있기에 개발자는 다양한 구동 환경에 대해서는 걱정하지 않고 본인이 작업하고 있는 개발PC에 설치된 JVM에서 구동되는 코드만 작성하면 된다. 

 

소스코드는 운영체제의 변화에 닫혀있고, 각 OS별 JVM은 확장에 열려있는 구조가 되는 것이다. 

개발자의 소스코드와 운영체제별 JVM 사이에는 목적 파일이라고 하는 완충 장치가 있는 것이다. 


OCP 원칙 위반 예제와 수정하기

class Animal {
	String type;
    
    Animal(String type) {
    	this.type = type;
    }
}

// 동물 타입을 받아 각 동물에 맞춰 울음소리를 내게 하는 클래스 모듈
class HelloAnimal {
    void hello(Animal animal) {
        if(animal.type.equals("Cat")) {
            System.out.println("냐옹");
        } else if(animal.type.equals("Dog")) {
            System.out.println("멍멍");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        HelloAnimal hello = new HelloAnimal();
        
        Animal cat = new Animal("Cat");
        Animal dog = new Animal("Dog");

        hello.hello(cat); // 냐옹
        hello.hello(dog); // 멍멍
    }
}

 

동작 자체는 문제가 없지만 문제는 기능 추가이다. 만약 동물을 추가하면 어떻게 될까?

HelloAnimal은 지금 현재 SRP도 위배되고 있는 사항이나 일단은 무시하고 OCP에 대해서 생각해보자

당연히 HelloAnimal 클래스를 수정해야한다. 각 객체의 필드 변수에 맞게 if문을 분기하여 구성해줘야 한다. 

 

동물이 추가될 때마다 계속 코드를 일일히 변경해줘야 하는 번거로운 작업이 생기게 된다. 

따라서 처음에 OCP 설계원칙에 따라 적절한 추상화 클래스를 구성하고 이를 상속하여 확장시키는 관계로 구성하면

변경에 닫히고 추가에는 열려있는 프로그램을 만들 수 있다. 

 

// 추상화
abstract class Animal {
    abstract void speak();
}

class Cat extends Animal { // 상속
    void speak() {
        System.out.println("냐옹");
    }
}

class Dog extends Animal { // 상속
    void speak() {
        System.out.println("멍멍");
    }
}

// 추상클래스를 상속만 하면 메소드 강제 구현 규칙으로 규격화만 하면 확장에 제한 없다 (opened)
class Sheep extends Animal {
    void speak() {
        System.out.println("매에에");
    }
}

// 기능 확장으로 인한 클래스가 추가되어도, 더이상 수정할 필요가 없어진다 (closed)
class HelloAnimal {
    void hello(Animal animal) {
        animal.speak();
    }
}

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

        Animal cat = new Cat();
        Animal dog = new Dog();
        
        Animal sheep = new Sheep();

        hello.hello(cat); // 냐옹
        hello.hello(dog); // 멍멍
        hello.hello(sheep); // 매에에
    }
}

 

OCP를 따르지 않는다고 해서 객체 지향 프로그램을 구현하는 것이 불가능한 것은 아니다.

OCP를 무시하고 프로그램을 작성하면 객체 지향의 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없다.

따라서 객체 지향 프로그래밍에서 OCP는 반드시 지켜야 할 원칙이다.


Default Method

java 8 이 등장하면서 인터페이스(Interface) 개념에 디폴트 메서드(default method)를 사용할 수 있게 되었다.

원래 기존의 인터페이스는 추상 메서드만 존재할 수 있었고 상속받는 구현체에서 직접 해당 추상 메서드를 구현했었다.

 

 

 

요구사항이 추가되면서 InterfaceA에 특정 추상 메서드 methodA를 추가해야된다고 생각해보자

InterfaceA를 상속받는 무수히 많은 클래스가 있을때, 일일이 모든 InterfaceA의 구현체 클래스에 추가되는 메서드 methodA를 구현해줘야 한다.

 

위와 같은 경우, 확장은 할 수 있지만(OPEN) 변경에 대한 폐쇄(CLOSE)를 위반한 케이스라고 볼 수 있다.

methodA가 InterfaceA에 추가된다는 이유로 methodA를 사용하지 않는 모든 구현체에 구현을 해야 하기 때문이다.

 

기존의 코드가 변경되지 않고 기능 확장을 해야 위 원칙을 지킬 수 있을 것이다.

default method는 인터페이스에 있는 구현 메서드를 의미한다.

다음과 같이 default method가 등장하면서 위에서 다뤘던 문제점을 해결할 수 있다.

 

문제점 : 인터페이스에 추상메서드를 추가하게 되면 모든 구현체에 구현을 해야한다.

해결 방안 : 인터페이스에 default method를 사용하면 추가 변경을 막을 수 있다.

 

이로써 OCP(Open-Close-Principle : 개방 폐쇄 원칙) 에서 확장에 개방(Open)되어 있고, 변경에 닫혀(Close)있는 코드를 설계할 수 있다.

 

📖Reference

  • 도서 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'
  • inpa.tistory.com '완벽하게 이해하는 OCP(개방 폐쇄 원칙)'
  • 'https://velog.io/@heoseungyeon/디폴트-메서드Default-Method'