실제 코드로 분석하는 OOP 원칙과 도메인 모델링
객체지향 프로그래밍을 공부하다 보면
‘추상화’, ‘캡슐화’, ‘상속’, ‘다형성’, 그리고 ‘SOLID 원칙’과 같은 개념을 이해하는 것이 쉽지 않다.
이론으로만 접하면 감이 잘 오지 않지만, 실제 코드 속에서 이러한 원리가 어떻게 적용되는지 살펴보면 훨씬 명확하게 이해할 수 있다.
이 글에서는 SNS(Post, Comment, User) 도메인을 직접 설계하면서 적용된 객체지향 개념들을 정리한다.
각 도메인 클래스에 어떤 의도가 담겨 있고, 어떤 객체지향 원칙을 따르고 있는지 실제 코드와 함께 분석해보자.
1. 도메인을 객체로 표현하는 과정
객체지향 설계의 첫 단계는 현실 세계의 개념을 객체로 추상화하는 것이다.
SNS 서비스에는 다음과 같은 개념이 존재한다.
- 사용자(User)
- 글(Post)
- 댓글(Comment)
- 텍스트 내용(Content)
- 좋아요 수(like counter)
- 작성/수정 날짜(datetime)
이 개념들을 그대로 객체로 변환한 것이 이 프로젝트의 시작점이다.
2. 캡슐화(Encapsulation) — 데이터는 감추고 행동만 드러낸다
객체지향의 핵심은 캡슐화다.
즉, 데이터는 외부에서 직접 접근할 수 없도록 숨기고, 행동(메서드)을 통해서만 객체를 조작하게 만든다.
다음 코드를 보자.
public class PositiveIntegerCounter {
private int count;
public void increment() {
this.count++;
}
public void decrement() {
if (this.count <= 0) {
return;
}
this.count--;
}
}
이 클래스는 좋아요 수, 팔로워 수 등에서 공통으로 사용하는 카운터 도메인이다.
핵심은 다음과 같다.
count필드는 private이므로 외부에서 직접 조작할 수 없다.- 카운터의 변경은 반드시
increment()와decrement()를 통해서만 이루어진다. - 음수 상태가 되지 않도록 객체가 스스로 보호한다.
이처럼 객체가 스스로 자신의 유효 상태를 관리하는 것이 캡슐화다.
3. 추상화(Abstraction) — 공통 개념을 상위 클래스로 묶는다
SNS 글(Post)과 댓글(Comment)은 모두 텍스트 기반 콘텐츠이며,
작성/수정 시간 기록이 필요하다는 공통점이 있다.
반면 길이 제한은 서로 다르다.
이 특징을 반영하기 위해 Content라는 추상 클래스를 만들었다.
public abstract class Content {
String contentText;
final DatetimeInfo datetimeInfo;
protected Content(String contentText) {
checkText(contentText);
this.contentText = contentText;
this.datetimeInfo = new DatetimeInfo();
}
public void updateContentText(String contentText) {
checkText(contentText);
this.contentText = contentText;
this.datetimeInfo.updateEditDateTime();
}
protected abstract void checkText(String contentText);
}
그리고 이를 확장하는 두 개의 구현체가 존재한다.
PostContent
public class PostContent extends Content {
private static final int MIN_POST_LENGTH = 5;
private static final int MAX_POST_LENGTH = 500;
@Override
protected void checkText(String contentText) { ... }
}
CommentContent
public class CommentContent extends Content {
private static final int MIN_COMMENT_LENGTH = 1;
private static final int MAX_COMMENT_LENGTH = 280;
@Override
protected void checkText(String contentText) { ... }
}
핵심 개념은 다음과 같다.
- 공통 속성과 기능은 Content에 정의한다.
- 길이 규칙처럼 서로 다른 부분은 자식 클래스에서 구현한다.
- 이는 전형적인 템플릿 메서드 패턴(Template Method Pattern) 구조다.
4. 불변성(Immutable) 유지 — 바뀌면 안 되는 값은 final로 보호한다
도메인 객체에서 어떤 값은 절대 바뀌면 안 된다.
예를 들어 Post의 작성자(author)나 id 값은 변경되면 도메인 자체가 깨진다.
private final Long id;
private final User author;
private final PostContent content;
이처럼 불변성을 보장하면 다음과 같은 장점이 있다.
- 도메인의 일관성이 유지된다.
- 스레드 안전성이 높아진다.
- 의도치 않은 부작용을 방지할 수 있다.
5. 도메인 규칙을 객체 자신이 책임지도록 만든다
Post.like() 메서드를 보자.
public void like(User user) {
if (this.author.equals(user)) {
throw new IllegalArgumentException("User cannot like their own post");
}
likeCounter.increment();
}
이 규칙은 PostService가 아닌 Post 객체 내부에서 처리된다.
왜 그럴까?
도메인 규칙은 가장 관련 있는 객체가 지켜야 한다.
좋아요 규칙은 Post 도메인의 규칙이므로 Post가 직접 책임지는 것이 옳다.
이는 DDD(Rich Domain Model) 설계 원칙과도 일치한다.
6. 디미터의 법칙(Law of Demeter) — 객체 내부 구조를 외부에 노출하지 않는다
다음은 User.follow()의 일부 코드다.
public void follow(User targetUser) {
if (targetUser.equals(this)) {
throw new IllegalArgumentException("User cannot follow itself");
}
followingCounter.increment();
targetUser.increaseFollowerCount();
}
여기서 중요한 점은 다음과 같다.
// 이렇게 하면 안 된다
targetUser.followerCounter.increment();
필드를 외부에서 직접 조작하면 캡슐화가 깨진다.
따라서 User는 followerCount를 변경하는 로직을 자신의 내부에서만 수행한다.
private void increaseFollowerCount() {
followerCounter.increment();
}
이것이 바로 디미터의 법칙,
즉 **“다른 객체의 내부 구조에 의존하지 말라”**는 원칙이다.
7. 상속(Inheritance)과 다형성(Polymorphism)의 자연스러운 적용
Content 추상 클래스는 상속과 다형성을 보여주는 좋은 예다.
- PostContent와 CommentContent는 각각 다르게 검증 로직을 갖는다.
- Content를 참조하는 입장에서는 두 타입을 모두 동일하게 다룰 수 있다.
- 공통 기능은 상위 클래스에 모이고, 확장 포인트는 하위 클래스에서 구현된다.
이 구조 덕분에 기능 확장이 매우 쉽다.
예를 들어 DMContent, ArticleContent 등이 추가되어도 Content 구조가 충분히 수용할 수 있다.
8. 객체 간 관계는 현실 세계의 구조를 반영한다
도메인 간의 관계도 객체지향적으로 잘 설계되어 있다.
- User는 여러 개의 Post를 작성한다.
- Post는 여러 개의 Comment를 가진다.
- User는 다른 User를 follow할 수 있다.
이 구조는 현실 모델과 동일하며,
이를 객체 간 연관 관계로 자연스럽게 옮겨온 것이다.
결론: 이 도메인 구조는 객체지향 원칙이 명확하게 반영된 모델이다
이 프로젝트는 다음과 같은 객체지향 특징을 모두 갖추고 있다.
- 캡슐화
- 추상화
- 상속과 다형성
- 불변성 유지
- 디미터의 법칙 준수
- 도메인 규칙을 객체 내부에서 처리
- 응집도 높은 구조
- 중복 제거를 위한 공통 도메인(PositiveIntegerCounter)
- DDD 스타일의 풍부한 도메인 모델(Rich Domain Model)
답글 남기기