소프트웨어를 처음 공부하기 시작하고 나서 가장 어려웠고 지금도 어려운 부분이 SOLID이다. SOLID를 공부해야겠다 생각하고 처음 접하게 되는 것이 SOLID의 S인 Single Responsibility Principle, 단일 책임 원칙이다. 이리 저리 구글링을 해보며 단일 책임 원칙에 대해 공부하고 이해했다 싶더라도, 막상 적용해보려니 어려움을 많이 느꼈다. 그렇게 시행착오를 겪으며 나만의 결론에 어느정도 도달하게 되었다. 물론 아직 부족하지만, 내 생각들을 정리하며 복습해보려 한다!🚀
책임이란 무엇일까?
SOLID라는 다섯가지 원칙은 객체지향의 절대적인 원칙이 아니다. Clean Code의 저자인 로버트 마틴이 객체지향의 원칙으로서 권장하고 이제는 주류로 자리잡은 원칙이 SOLID이다.
로버트 마틴의 말에 따르면,
A class should have only one reason to change.
클래스는 오직 하나의 변경의 이유를 가져야한다. 즉, 클래스를 수정할 일이 생길 땐 그 이유가 오직 하나여야 하고, 책임은 변경의 이유라고 정의할 수 있다.
직사각형의 책임 - 초기 설계
간단한 직사각형 객체의 예제를 써보겠다.
public class Rectangle {
private final double width;
private final double length;
public Rectangle(double width, double length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn...");
}
public double calculateArea() {
return width * length;
}
}
Rectangle
은 간단하게 가로 세로 길이를 가지고 있는 직사각형 객체이다. 직사각형을 그릴 수 있는 draw()
라는 함수와 넓이를 구할 수 있는calculateArea()
라는 함수를 가지고 있다.
이 직사각형 클래스의 책임은 뭐라고 정의할 수 있을까?
나는 직사각형이라고 생각한다. 직사각형을 그리는 책임도, 넓이를 계산하는 책임도 그저 직사각형이라는 넓은 책임으로 볼 수 있다고 생각한다! 적어도 여기까지는 그렇다.
애플리케이션 확장
그렇다면 여기에 다른 두 객체를 추가해보자.
public class RectangleCalculator {
private final List<Rectangle> rectangles;
public RectangleCalculator(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public double calculateAreaSum() {
double areaSum = rectangles.stream()
.map(Rectangle::calculateArea)
.reduce(Double::sum)
.get();
return areaSum;
}
}
public class GUI {
private final List<Rectangle> rectangles;
public GUI(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public void drawRectangles() {
rectangles.forEach(Rectangle::draw);
}
}
RectangleCalculator
: 직사각형들의 넓이의 합을 구하는calculateSum()
이라는 함수를 가지는 객체GUI
: 직사각형들을 그리는 객체
두 객체는 각각 다른 책임을 지니고 있다. RectangleCalculator
는 여러 직사각형들의 계산을 책임지고, GUI
는 여러 직사각형들을 그리는 책임을 가지고 있다. 직사각형들의 다른 계산을 추가하고 싶다면 RectangleCalculator
로 가서 추가하고, 직사각형들의 그래픽에 수정이나 추가를 하고싶다면 GUI
로 가서 수정하면 된다. 나름 직관적인 것처럼 보인다!
애플리케이션 확장으로 인한 책임의 확장
직사각형의 가로 세로 길이를 double 타입으로 정했는데, int로도 충분하다는 생각이 들어 바꾸게 되었다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn...");
}
public int calculateArea() {
return width * length;
}
}
public class RectangleCalculator {
private final List<Rectangle> rectangles;
public RectangleCalculator(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public int calculateAreaSum() {
int areaSum = rectangles.stream()
.map(Rectangle::calculateArea)
.reduce(Integer::sum)
.get();
return areaSum;
}
}
RectangleCalculator
는 여러 직사각형들에 대한 계산기이니 개별적인 직사각형의 로직을 담당하는 Rectangle
클래스를 수정해주었다.
이번엔 직사각형을 그리는 형식이 맘에 안들어 변경하려고 한다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn with dimensions of " + width + "x" + length);
}
public int calculateArea() {
return width * length;
}
}
역시 Rectangle
클래스를 수정해주었다. 직사각형 넓이의 계산과 직사각형 그림 형식, 두가지 이유로 직사각형 클래스를 변경했다. 즉, 현재 직사각형 클래스는 두가지 변경의 이유, 혹은 두가지 책임을 가지고 있다고 할 수 있다.
여기서 의문을 가질 수 있다. 이게 그렇게 문제인가?
책임의 분리 이유 - 책임의 결합
직사각형을 그리는 것도, 직사각형의 넓이를 계산하는 것도 모두 직사각형이 스스로 가지고 있는 정보로 할 수 있는 행위들이다. 직사각형 넓이 계산와 직사각형 그리기, 이 두가지 행위를 굳이 두가지 책임으로 나누는 것이 억지라고 생각이 들었었다. 하지만 객체지향 프로그래밍은 중점이 추후의 확장과 유지보수다.
현재의 직사각형 클래스는 확장하기 어려운 클래스이다. 만약 직사각형의 대각선 길이, center of pressure, center of gravity 등 여러가지 연산을 추가하고 싶어진다면, 다양한 방식의 그리는 법을 추가하고 싶어진다면 직사각형 클래스는 더욱 뚱뚱해질 것이다. 애플리케이션을 구동하다가 연산이 실제와 달라서 수정해야 할 일이 생긴다면, 수정 포인트를 직관적으로 찾기 힘들어질 것이다. 만약 한 사람이 아닌 다수가 애플리케이션을 작성했다면, 의도치 못한 버그마저 생겨날 가능성이 높다.

GUI
와 RectangleCalculator
가 동시에 직사각형을 의존하고 있다. 직사각형의 수학 계산에 대한 책임과 직사각형의 그림에 대한 책임이 직사각형 클래스에 전부 포함되면서 책임의 결합이 강하게 생긴 것이다. 결국, 넓이 연산을 수정했더니 그림이 이상하게 나올 수도 있다!
책임의 분리
직사각형의 확장된 책임을 분리해보려 한다. 수학 계산과 그림 중 어떤 책임을 분리해야 바람직할까? 둘 다 직사각형으로부터 분리하는 것이 좋을까?
정답은 없다. 상황마다, 그리고 가진 인적자원과 시간 등 리소스마다 다르다. 물론 최대한 많이 분리해서 개별적인 직사각형의 수학 계산을 하는 클래스, 개별적인 직사각형 그림에 대한 클래스를 모두 만드는 것이 책임이 가장 잘 분리될 것이다. 하지만 현재 수학 계산은 둘레, 대각선 길이, center of pressure, center of gravity 등등 변경이 예상되기 쉽고, 그림 형식에 대한 변경이 예상하기 어렵다고 가정한다면 개별적인 직사각형 그림에 대한 클래스를 정의하고 관리하는 것이 불필요한 복잡성(Needless Complexity)이지 않을까?
그렇다고 가정하고 수학 계산을 담당하는 클래스를 추가해 책임을 분리해보겠다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn with dimensions of " + width + "x" + length);
}
public int getWidth() {
return width;
}
public int getLength() {
return length;
}
}
public class GeometricRectangle {
private final Rectangle rectangle;
public GeometricRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public int calculateArea() {
return rectangle.getWidth() * rectangle.getLength();
}
}
public class RectangleCalculator {
private final List<GeometricRectangle> rectangles;
public RectangleCalculator(List<GeometricRectangle> rectangles) {
this.rectangles = rectangles;
}
public int calculateAreaSum() {
int areaSum = rectangles.stream()
.map(GeometricRectangle::calculateArea)
.reduce(Integer::sum)
.get();
return areaSum;
}
}
GeometricRectangle
: 직사각형의 수학 계산을 담당하는 클래스
GeometricRectangle
클래스를 추가해주었다. 대각선 길이와 둘레도 계산하고 싶어져서 추가해주겠다.
public class GeometricRectangle {
private final Rectangle rectangle;
public GeometricRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public int calculateArea() {
return rectangle.getWidth() * rectangle.getLength();
}
public int calculatePerimeter() {
return 2 * (rectangle.getWidth() + rectangle.getLength());
}
public double calculateDiagonal() {
double widthSquare = Math.pow(rectangle.getWidth(), 2);
double lengthSquare = Math.pow(rectangle.getLength(), 2);
return Math.sqrt(widthSquare + lengthSquare);
}
}
이와 같이 Rectangle
클래스가 아닌 GeomtricRectangle
에 대각선 길이와 둘레를 구하는 함수를 추가해주었다.

개별적인 직사각형의 수학 계산과 그림에 대한 책임을 분리해주며 클래스마다 단일 책임을 가지게 하고, 책임 간의 결합을 해소했다. 이제 수학 계산을 수정할 일이 생기더라도 GeometricRectangle
에서 수정을 하면 되고, 직사각형을 그리는데 예상하지 못한 버그가 생길 가능성이 줄었다고 볼 수 있다!
풀리지 않는 의문
간단하게 예제를 작성해보며 객체는 왜 단일 책임을 가져야 하는지 이해하기는 어렵지 않았다. 하지만 프로젝트를 진행하거나 애플리케이션을 만들때 나름대로 클래스 마다의 책임을 설계하다보면 풀리지 않는 의문이 있었다.
책임의 범주는 어디까지인가? 어디까지 책임을 나눠야하고, 책임으로 묶일 수 있는 허용치는 어느 정도일까?
SOLID는 객체지향의 절대적인 특성이 아니다. 로버트 마틴이 중요시하고 내새웠던 추상적인 개념이다. 그렇기에 책임을 설계하는 것은 너무나도 어렵게 느껴진다. 다음 포스팅에선 이 의문에 대한 의견을 적어보려한다.
https://jtechtalk.tistory.com/9
Reference
R. C. Martin, Agile Software Development: Principles, Patterns, and Practices. Harlow, Essex: Pearson Education Limited, 2014.
'객체지향 프로그래밍' 카테고리의 다른 글
객체지향의 특성 - 추상화 (0) | 2024.05.22 |
---|---|
객체지향의 특성 - 상속 (0) | 2024.05.21 |
객체지향의 특성 - 캡슐화 (정보은닉) (0) | 2024.05.21 |
객체지향 프로그래밍(OOP) 이야기 (1) | 2024.05.20 |
단일 책임 원칙 이야기 (2) - 책임의 범주 (1) | 2024.01.21 |
소프트웨어를 처음 공부하기 시작하고 나서 가장 어려웠고 지금도 어려운 부분이 SOLID이다. SOLID를 공부해야겠다 생각하고 처음 접하게 되는 것이 SOLID의 S인 Single Responsibility Principle, 단일 책임 원칙이다. 이리 저리 구글링을 해보며 단일 책임 원칙에 대해 공부하고 이해했다 싶더라도, 막상 적용해보려니 어려움을 많이 느꼈다. 그렇게 시행착오를 겪으며 나만의 결론에 어느정도 도달하게 되었다. 물론 아직 부족하지만, 내 생각들을 정리하며 복습해보려 한다!🚀
책임이란 무엇일까?
SOLID라는 다섯가지 원칙은 객체지향의 절대적인 원칙이 아니다. Clean Code의 저자인 로버트 마틴이 객체지향의 원칙으로서 권장하고 이제는 주류로 자리잡은 원칙이 SOLID이다.
로버트 마틴의 말에 따르면,
A class should have only one reason to change.
클래스는 오직 하나의 변경의 이유를 가져야한다. 즉, 클래스를 수정할 일이 생길 땐 그 이유가 오직 하나여야 하고, 책임은 변경의 이유라고 정의할 수 있다.
직사각형의 책임 - 초기 설계
간단한 직사각형 객체의 예제를 써보겠다.
public class Rectangle {
private final double width;
private final double length;
public Rectangle(double width, double length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn...");
}
public double calculateArea() {
return width * length;
}
}
Rectangle
은 간단하게 가로 세로 길이를 가지고 있는 직사각형 객체이다. 직사각형을 그릴 수 있는 draw()
라는 함수와 넓이를 구할 수 있는calculateArea()
라는 함수를 가지고 있다.
이 직사각형 클래스의 책임은 뭐라고 정의할 수 있을까?
나는 직사각형이라고 생각한다. 직사각형을 그리는 책임도, 넓이를 계산하는 책임도 그저 직사각형이라는 넓은 책임으로 볼 수 있다고 생각한다! 적어도 여기까지는 그렇다.
애플리케이션 확장
그렇다면 여기에 다른 두 객체를 추가해보자.
public class RectangleCalculator {
private final List<Rectangle> rectangles;
public RectangleCalculator(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public double calculateAreaSum() {
double areaSum = rectangles.stream()
.map(Rectangle::calculateArea)
.reduce(Double::sum)
.get();
return areaSum;
}
}
public class GUI {
private final List<Rectangle> rectangles;
public GUI(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public void drawRectangles() {
rectangles.forEach(Rectangle::draw);
}
}
RectangleCalculator
: 직사각형들의 넓이의 합을 구하는calculateSum()
이라는 함수를 가지는 객체GUI
: 직사각형들을 그리는 객체
두 객체는 각각 다른 책임을 지니고 있다. RectangleCalculator
는 여러 직사각형들의 계산을 책임지고, GUI
는 여러 직사각형들을 그리는 책임을 가지고 있다. 직사각형들의 다른 계산을 추가하고 싶다면 RectangleCalculator
로 가서 추가하고, 직사각형들의 그래픽에 수정이나 추가를 하고싶다면 GUI
로 가서 수정하면 된다. 나름 직관적인 것처럼 보인다!
애플리케이션 확장으로 인한 책임의 확장
직사각형의 가로 세로 길이를 double 타입으로 정했는데, int로도 충분하다는 생각이 들어 바꾸게 되었다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn...");
}
public int calculateArea() {
return width * length;
}
}
public class RectangleCalculator {
private final List<Rectangle> rectangles;
public RectangleCalculator(List<Rectangle> rectangles) {
this.rectangles = rectangles;
}
public int calculateAreaSum() {
int areaSum = rectangles.stream()
.map(Rectangle::calculateArea)
.reduce(Integer::sum)
.get();
return areaSum;
}
}
RectangleCalculator
는 여러 직사각형들에 대한 계산기이니 개별적인 직사각형의 로직을 담당하는 Rectangle
클래스를 수정해주었다.
이번엔 직사각형을 그리는 형식이 맘에 안들어 변경하려고 한다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn with dimensions of " + width + "x" + length);
}
public int calculateArea() {
return width * length;
}
}
역시 Rectangle
클래스를 수정해주었다. 직사각형 넓이의 계산과 직사각형 그림 형식, 두가지 이유로 직사각형 클래스를 변경했다. 즉, 현재 직사각형 클래스는 두가지 변경의 이유, 혹은 두가지 책임을 가지고 있다고 할 수 있다.
여기서 의문을 가질 수 있다. 이게 그렇게 문제인가?
책임의 분리 이유 - 책임의 결합
직사각형을 그리는 것도, 직사각형의 넓이를 계산하는 것도 모두 직사각형이 스스로 가지고 있는 정보로 할 수 있는 행위들이다. 직사각형 넓이 계산와 직사각형 그리기, 이 두가지 행위를 굳이 두가지 책임으로 나누는 것이 억지라고 생각이 들었었다. 하지만 객체지향 프로그래밍은 중점이 추후의 확장과 유지보수다.
현재의 직사각형 클래스는 확장하기 어려운 클래스이다. 만약 직사각형의 대각선 길이, center of pressure, center of gravity 등 여러가지 연산을 추가하고 싶어진다면, 다양한 방식의 그리는 법을 추가하고 싶어진다면 직사각형 클래스는 더욱 뚱뚱해질 것이다. 애플리케이션을 구동하다가 연산이 실제와 달라서 수정해야 할 일이 생긴다면, 수정 포인트를 직관적으로 찾기 힘들어질 것이다. 만약 한 사람이 아닌 다수가 애플리케이션을 작성했다면, 의도치 못한 버그마저 생겨날 가능성이 높다.

GUI
와 RectangleCalculator
가 동시에 직사각형을 의존하고 있다. 직사각형의 수학 계산에 대한 책임과 직사각형의 그림에 대한 책임이 직사각형 클래스에 전부 포함되면서 책임의 결합이 강하게 생긴 것이다. 결국, 넓이 연산을 수정했더니 그림이 이상하게 나올 수도 있다!
책임의 분리
직사각형의 확장된 책임을 분리해보려 한다. 수학 계산과 그림 중 어떤 책임을 분리해야 바람직할까? 둘 다 직사각형으로부터 분리하는 것이 좋을까?
정답은 없다. 상황마다, 그리고 가진 인적자원과 시간 등 리소스마다 다르다. 물론 최대한 많이 분리해서 개별적인 직사각형의 수학 계산을 하는 클래스, 개별적인 직사각형 그림에 대한 클래스를 모두 만드는 것이 책임이 가장 잘 분리될 것이다. 하지만 현재 수학 계산은 둘레, 대각선 길이, center of pressure, center of gravity 등등 변경이 예상되기 쉽고, 그림 형식에 대한 변경이 예상하기 어렵다고 가정한다면 개별적인 직사각형 그림에 대한 클래스를 정의하고 관리하는 것이 불필요한 복잡성(Needless Complexity)이지 않을까?
그렇다고 가정하고 수학 계산을 담당하는 클래스를 추가해 책임을 분리해보겠다.
public class Rectangle {
private final int width;
private final int length;
public Rectangle(int width, int length) {
this.width = width;
this.length = length;
}
public void draw() {
System.out.println("Pretend a rectangle is being drawn with dimensions of " + width + "x" + length);
}
public int getWidth() {
return width;
}
public int getLength() {
return length;
}
}
public class GeometricRectangle {
private final Rectangle rectangle;
public GeometricRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public int calculateArea() {
return rectangle.getWidth() * rectangle.getLength();
}
}
public class RectangleCalculator {
private final List<GeometricRectangle> rectangles;
public RectangleCalculator(List<GeometricRectangle> rectangles) {
this.rectangles = rectangles;
}
public int calculateAreaSum() {
int areaSum = rectangles.stream()
.map(GeometricRectangle::calculateArea)
.reduce(Integer::sum)
.get();
return areaSum;
}
}
GeometricRectangle
: 직사각형의 수학 계산을 담당하는 클래스
GeometricRectangle
클래스를 추가해주었다. 대각선 길이와 둘레도 계산하고 싶어져서 추가해주겠다.
public class GeometricRectangle {
private final Rectangle rectangle;
public GeometricRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public int calculateArea() {
return rectangle.getWidth() * rectangle.getLength();
}
public int calculatePerimeter() {
return 2 * (rectangle.getWidth() + rectangle.getLength());
}
public double calculateDiagonal() {
double widthSquare = Math.pow(rectangle.getWidth(), 2);
double lengthSquare = Math.pow(rectangle.getLength(), 2);
return Math.sqrt(widthSquare + lengthSquare);
}
}
이와 같이 Rectangle
클래스가 아닌 GeomtricRectangle
에 대각선 길이와 둘레를 구하는 함수를 추가해주었다.

개별적인 직사각형의 수학 계산과 그림에 대한 책임을 분리해주며 클래스마다 단일 책임을 가지게 하고, 책임 간의 결합을 해소했다. 이제 수학 계산을 수정할 일이 생기더라도 GeometricRectangle
에서 수정을 하면 되고, 직사각형을 그리는데 예상하지 못한 버그가 생길 가능성이 줄었다고 볼 수 있다!
풀리지 않는 의문
간단하게 예제를 작성해보며 객체는 왜 단일 책임을 가져야 하는지 이해하기는 어렵지 않았다. 하지만 프로젝트를 진행하거나 애플리케이션을 만들때 나름대로 클래스 마다의 책임을 설계하다보면 풀리지 않는 의문이 있었다.
책임의 범주는 어디까지인가? 어디까지 책임을 나눠야하고, 책임으로 묶일 수 있는 허용치는 어느 정도일까?
SOLID는 객체지향의 절대적인 특성이 아니다. 로버트 마틴이 중요시하고 내새웠던 추상적인 개념이다. 그렇기에 책임을 설계하는 것은 너무나도 어렵게 느껴진다. 다음 포스팅에선 이 의문에 대한 의견을 적어보려한다.
https://jtechtalk.tistory.com/9
Reference
R. C. Martin, Agile Software Development: Principles, Patterns, and Practices. Harlow, Essex: Pearson Education Limited, 2014.
'객체지향 프로그래밍' 카테고리의 다른 글
객체지향의 특성 - 추상화 (0) | 2024.05.22 |
---|---|
객체지향의 특성 - 상속 (0) | 2024.05.21 |
객체지향의 특성 - 캡슐화 (정보은닉) (0) | 2024.05.21 |
객체지향 프로그래밍(OOP) 이야기 (1) | 2024.05.20 |
단일 책임 원칙 이야기 (2) - 책임의 범주 (1) | 2024.01.21 |