본문 바로가기
클라이언트 개발자 노트

객체 지향 프로그래밍 OOP(Object Oriented Programming)

by 펜싱이 2026. 4. 22.

 

객체 지향 프로그래밍(OOP)의 핵심과 SOLID 원칙

최근 게임 개발을 진행하며 코드의 구조에 대해 많은 고민을 하게 되었습니다. 단순히 돌아가는 코드가 아니라, 유지보수가 쉽고 확장성이 좋은 코드를 만들기 위해 반드시 알아야 할 OOP(Object-Oriented Programming)에 대해 정리해 봅니다.


1. OOP란 무엇인가?

객체 지향 프로그래밍은 데이터와 그 데이터를 처리하는 기능을 하나로 묶은 '객체'를 중심으로 프로그램을 설계하는 방식입니다.

  • 절차지향 (Procedural): 데이터와 함수가 분리되어 순차적으로 실행됨 (예: C언어)
  • 객체지향 (OOP): 데이터와 기능을 '객체'라는 단위로 묶어 관리 (예: C#, Java)

 

2. OOP의 4가지 주요 특성

특성 설명
캡슐화 (Encapsulation) 데이터와 메서드를 하나로 묶고, 외부 접근을 제한하여 데이터를 보호합니다. (정보 은닉)
상속 (Inheritance) 기존 클래스의 기능을 물려받아 재사용하거나 확장할 수 있게 합니다.
다형성 (Polymorphism) 오버라이딩(Override)이나 인터페이스를 통해 하나의 인터페이스로 다양한 기능을 구현합니다.
추상화 (Abstraction) 객체의 공통적인 특징을 추출하여 상위 개념으로 일반화합니다.

 

3. 깨끗한 설계를 위한 5가지 원칙: SOLID

  • S- Single Responsibility Principle (단일 책임 원칙)
    : 클래스는 오직 하나의 책임(변경 이유)만 가져야 합니다.
  • O- Open Closed Principle (개방-폐쇄 원칙)
    :
    기존 코드는 수정하지 않으면서(Closed), 새로운 기능은 쉽게 추가(Open)할 수 있어야 합니다.
  • L- Liskov Substitution Principle (리스코프 치환 원칙)
    :
    자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 합니다.
  • I- Interface Segregation Principle (인터페이스 분리 원칙)
    :
    사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작게 쪼개야 합니다.
  • D- Dependency Inversion Principle (의존성 역전 원칙)
    :
    구체적인 구현 클래스가 아니라, 인터페이스나 추상 클래스에 의존해야 합니다.

 

4. SOLID 원칙 예시 코드 (C#)

가장 기본이 되는 SRP(단일 책임)와 DIP(의존성 역전)를 활용한 간단한 구조입니다.

// 1. DIP 준수: 추상화된 인터페이스에 의존
public interface ILogger {
    void Log(string message);
}

// 구체적인 구현체
public class ConsoleLogger : ILogger {
    public void Log(string message) => Console.WriteLine($"Log: {message}");
}

// 2. SRP 준수: Player 클래스는 오직 플레이어의 로직만 담당
public class Player {
    private ILogger _logger;

    public Player(ILogger logger) {
        _logger = logger; // 외부에서 주입받음 (DIP)
    }

    public void Move() {
        // 이동 로직...
        _logger.Log("플레이어가 이동했습니다.");
    }
}

// 3. LSP 준수: 참새와 타조는 둘 다 새지만 같은 동작을 하진 않는다
// 부모 클래스: 새
public class Bird {
    public virtual void Fly() {
        Console.WriteLine("하늘을 납니다.");
    }
}

// 자식 클래스: 참새 (정상 작동)
public class Sparrow : Bird { }

// 자식 클래스: 타조 (LSP 위반!)
public class Ostrich : Bird {
    public override void Fly() {
        // 타조는 날 수 없으므로 예외를 던지거나 아무것도 안 함
        throw new NotSupportedException("타조는 날 수 없습니다!");
    }
}

// 실행부
void MakeBirdFly(Bird bird) {
    bird.Fly(); // 만약 여기에 Ostrich가 들어오면 프로그램이 터집니다.
}

 

# 마치며


나는 사실 여태까지 유니티로 개발을 하면서 이런한 원칙을 알지 못하고 있었다,
그냥 단순히 효율적으로 코드를 짜려 노력했고, 기능 확장을 염두하고 짜다보니 공부하는 것들을
나도 모르게 써먹고 있을때가 있었다,
반대로 "아 이건 이렇게 만들면 안되겠네?" 싶은 것들도 많았다.
적재적소에 원칙들을 잘 이용하여 효율적이고 이쁜 코드와 구조를 짜는게 중요하다는 것을 깨달았다.