개방 폐쇠 원칙(Open-Closed Principle)

옹심이·2025년 1월 6일
0
post-thumbnail

단일 책임 원칙이 변경에 관해 다뤘다면 개방 폐쇄 원칙은 확장에 대해 다룬다. 이 원칙은 “확장에는 열려있고 변경에는 닫혀 있어야 한다”라는 말로 표현되기도 한다.

이 원칙의 주 목적은 기존 코드를 수정하지 않으며 확장 가능한 시스템을 만드는 것이다. 시스템 운영에서 코드를 변경하는 것은 아주 위험하다. 큰 프로젝트에서는 변경하려는 코드를 사용하는 액터가 여럿인 경우 코드 변경이 아주 어렵다. 따라서 코드를 확장하고자 할 때 기존 코드를 건드리지 않는 것이 최고의 전략이다.

위반 예시와 잘 지켜진 예시 코드를 보며 이해해보자

// ----------------- Shape interface -----------------
public interface Shape { }

// ----------------- Rectangle class -----------------
public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }
}

// ----------------- Circle class -----------------
public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}

// ----------------- AreaCalculator (위반 예시) -----------------
public class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            if (shape instanceof Rectangle) {
                Rectangle rectangle = (Rectangle) shape;
                totalArea += rectangle.getWidth() * rectangle.getHeight();
            } else if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                totalArea += Math.PI * circle.getRadius() * circle.getRadius();
            }
            // 새로운 Shape 타입이 생길 때마다 코드를 수정해야 함
            // else if (shape instanceof Triangle) { ... }
        }
        return totalArea;
    }
}

개방 폐쇠 원칙을 위반한 예시이다. AreaCalculator 라는 넓이를 계산하는 클래스가 따로 존재하면 도형의 종류가 추가 될 때마다 AreaCalculator의 calculateTotalArea 메서드의 내부 코드가 수정되어야 한다. 이 경우에는 수정에 닫혀 있는 개방 폐쇠 원칙을 지키지 못했다고 볼 수 있다.

// ----------------- Shape interface -----------------
public interface Shape {
    double getArea(); // 각 도형 스스로 넓이를 계산하도록
}

// ----------------- Rectangle class -----------------
public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}

// ----------------- Circle class -----------------
public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

// ----------------- Triangle class (새로운 도형) -----------------
public class Triangle implements Shape {
    private double base;
    private double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double getArea() {
        return (base * height) / 2;
    }
}

// ----------------- AreaCalculator (OCP 준수 예시) -----------------
public class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.getArea();
        }
        return totalArea;
    }
}

이 코드에서는 인터페이스에서 역할을 정의해 각 도형이 넓이를 구하는 책임을 가지도록 만들었다. 따라서 이전 코드와 같이 도형의 종류가 추가될 때마다 if-else문을 이용한 직접적인 수정이 일어나지 않게되었다.

0개의 댓글