지금까지 우리는 소프트웨어 개발의 다양한 단계와 방법론, 그리고 설계에 앞선 준비 과정들을 살펴보았습니다.
이제 드디어 소프트웨어 설계의 중요한 패러다임 중 하나인
객체 지향(Object-Oriented)
의 세계로 한 걸음 더 깊이 들어가 보려 합니다.
객체 지향 분석(OOA)과 설계(OOD)는 우리가 주변에서 보는 실제 세상의 사물이나 개념들을 소프트웨어 안의 '객체'라는 단위로 만들고, 이 객체들이 서로 협력하여 문제를 해결하도록 시스템을 구축하는 방식입니다.
오늘은 이 객체 지향 분석과 설계의 기본 개념부터 시작하여, 어떻게 현실 세계의 문제를 객체로 바라보고, 이를 코드로 옮길 수 있는지 함께 알아보겠습니다.
로 그룹화(분류)됩니다.
클래스는 동일한 종류의 객체들을 만들기 위한 '틀' 또는 '설계도'입니다.
클래스 이름
보통 단수 명사로, 첫 글자는 대문자로 작성합니다. (예: Customer, Product, Order)
속성 (Attributes)
클래스에 속한 객체들이 가지는 상태 정보나 특징을 나타냅니다. (예: Customer 클래스의 속성 - 이름, 주소, 이메일)
행동/메서드 (Behaviors/Methods)
클래스에 속한 객체들이 수행할 수 있는 동작이나 기능을 나타냅니다. (예: Customer 클래스의 메서드 - 주문하다(), 정보수정하다())
✅
CRC 카드 (Class-Responsibility-Collaborator) 기법 활용 🔗
CRC 카드는 각 클래스의 이름(Class), 책임(Responsibility), 그리고 협력하는 다른 클래스(Collaborator)를 간단한 카드에 적어보며 객체와 클래스를 식별하고 설계하는 데 도움이 되는 간단하고 효과적인 기법입니다.
팀원들과 함께 브레인스토밍하며 카드를 만들고 배치해보는 과정을 통해 초기 설계를 구체화할 수 있습니다.
이미지 제안: CRC 카드 예시. 카드 앞면이 세 칸으로 나뉘어 맨 위에는 클래스 이름, 왼쪽 아래 칸에는 책임 목록, 오른쪽 아래 칸에는 협력자 클래스 목록이 적혀 있는 모습. 여러 장의 CRC 카드가 테이블 위에 놓여 있는 모습도 좋음.
CRC 카드
🚀
2단계: 책임 기반 설계 접근법 (Responsibility-Driven Design, RDD) 🔗
객체 지향 설계에서 중요한 개념 중 하나는 각 객체가 자신만의
책임(Responsibility)
을 가진다는 것입니다.
책임 기반 설계는 시스템의 기능을 수행하기 위해 각 객체(또는 클래스)가 어떤 책임을 가져야 하는지, 그리고 그 책임을 수행하기 위해 다른 어떤 객체와
하여 단순화하고 모델링하는 과정입니다.
불필요한 세부 사항은 숨기고, 문제 해결에 필요한 본질적인 부분에만 집중할 수 있도록 합니다.
목적
복잡성 감소
시스템을 이해하고 설계하기 쉽게 만듭니다.
재사용성 증가
잘 정의된 추상화는 다양한 상황에서 재사용될 수 있습니다.
Java 예시
인터페이스 (Interface)
객체가 수행해야 하는
행동의 명세(계약)
만을 정의합니다. 실제 구현은 이 인터페이스를 구현하는 클래스에서 담당합니다.
추상 클래스 (Abstract Class)
공통된 속성과 메서드를 가지지만, 일부 메서드는 자식 클래스에서 반드시 구현하도록 남겨둔 불완전한 클래스입니다.
// 추상화 예시: 인터페이스를 사용한 도형 그리기interface Drawable { // "그릴 수 있는" 이라는 추상적인 개념 정의 void draw(); // 무엇을 그릴지는 구체적인 클래스가 결정}class Circle implements Drawable { private int x, y, radius; public Circle(int x, int y, int radius) { this.x=x; this.y=y; this.radius=radius; } @Override public void draw() { System.out.println("원 그리기 (중심: " + x + "," + y + " 반지름: " + radius + ")"); }}class Rectangle implements Drawable { private int x1, y1, x2, y2; public Rectangle(int x1, int y1, int x2, int y2) { /* ... */ } @Override public void draw() { System.out.println("사각형 그리기"); }}public class DrawingEditor { public void drawShape(Drawable shape) { // Drawable 인터페이스 타입으로 받음 shape.draw(); // 구체적인 도형이 무엇인지는 신경 쓰지 않고 "그린다"는 행위만 호출 }}
DrawingEditor는 구체적인 Circle이나 Rectangle을 알 필요 없이, Drawable이라는 추상적인 "그릴 수 있는 것"에만 의존하여 draw() 메서드를 호출합니다.
하나의 인터페이스나 부모 클래스 타입을 통해 서로 다른 여러 자식 클래스의 객체를 동일한 방식으로 다룰 수 있는
특성을 말합니다.
메서드 오버라이딩(Overriding)과 오버로딩(Overloading)을 통해 구현됩니다.
목적
코드의 유연성 및 확장성 향상
새로운 자식 클래스가 추가되어도 기존 코드를 거의 수정하지 않고 동일한 방식으로 처리할 수 있습니다. (OCP 원칙과 관련)
코드 간결성
다양한 타입의 객체를 동일한 인터페이스로 처리하므로 코드가 간결해집니다.
Java 예시 (메서드 오버라이딩을 통한 다형성)
위의 Drawable 예제에서 DrawingEditor의 drawShape 메서드가 다형성의 좋은 예입니다. shape.draw()를 호출할 때, shape 변수에 실제로 어떤 Drawable 구현 객체(Circle 또는 Rectangle)가 전달되었는지에 따라 해당 객체의 draw() 메서드가 실행됩니다.
public class Main { public static void main(String[] args) { DrawingEditor editor = new DrawingEditor(); Drawable circle = new Circle(10, 10, 5); Drawable rectangle = new Rectangle(0,0, 20, 20); editor.drawShape(circle); // Circle의 draw() 호출 editor.drawShape(rectangle); // Rectangle의 draw() 호출 // 새로운 Drawable 구현체가 추가되어도 editor.drawShape() 코드는 변경 불필요 Drawable triangle = new Drawable() { // 익명 클래스로 Triangle 구현 @Override public void draw() { System.out.println("삼각형 그리기"); } }; editor.drawShape(triangle); }}
이처럼 캡슐화, 추상화, 다형성은 객체 지향 분석 및 설계를 통해 만들어지는 소프트웨어의 품질을 높이는 핵심적인 원리들입니다.
오늘은 객체 지향 분석(OOA)과 설계(OOD)의 첫걸음을 내딛어 보았습니다.
문제 영역에서 객체를 식별하고 클래스로 분류하는 방법, 각 객체의 책임을 정의하고 협력 관계를 설계하는 책임 기반 접근법, 그리고 객체 지향의 핵심 원리인 캡슐화, 추상화, 다형성을 효과적으로 사용하는 방법에 대해 알아보았습니다.
다음 시간에는 지금까지 배운 여러 방법론들을 좀 더 넓은 시각에서 비교해보고, 특히 현대 소프트웨어 개발에서 많이 사용되는 반복적, 진화적, 그리고 애자일 개발 방법론에 대해 자세히 알아보겠습니다.