java

40. 객체 지향 프로그래밍(OOP)

dalc3154 2025. 3. 6. 15:48

OOP는 Object-Oriented Programming의 약자로 객체 지향 프로그래밍이란 뜻이다

 

SOLID원칙

객체 지향 프로그래밍의 5가지 소프트웨어 개발 원칙

SRP, OCP, LSP, ISP, DIP

소프트웨어 설계에서 유지보수성과 확장성을 높임

다양한 디자인 패턴이 SOLID 설계 원칙에 의해 만들어짐

 

SOLID 원칙 적용 시 주의점

원칙들은 적용 순서 X, 모두 필수 X, 서로 독립적 개념 X

원칙들은 개념적 연관이 있을 뿐임

 

상속, 추상화, 다향성, 캡슐화 등은 OOP의 4가지 특징

 

SRP: Single REsponsibility Principle(단일 책임 원칙)

클래스는 단 하나의 책임만을 가져야 함

한 가지 역활(기능)만을 담당 / 하나의 변화 이유만을 가져야 함

하나의 클래스가 가지는 하나의 역할과 관련된 변경사항만 클래스에 영향을 끼쳐야 함

 

잘못된 예제)

하나의 클래스가 여러 책임을 가짐

class SchoolHelper{
	void cleanClassroom() {
		System.out.println("교실을 청소합니다");
	}
	void prepareLunch() {
		System.out.println("급식을 준비합니다");
	}
}

 

 

올바른 예제)

각각의 클래스가 역할을 담당

class Janitor {
	void cleanClassroom() {
		System.out.println("교실을 청소합니다");
	}
}
class LunchStaff {
	void prepareLunch() {
	System.out.println("급식을 준비합니다");
	}
}

 

 

OCP: Open-Closed Principle(개방 폐쇄 원칙)

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려있어야 하고 수정에는 닫혀있어야 한다

기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있어야 함

 

잘못된 예제)

기존 코드를 직접적으로 수정해야 확장 가능

class LunchMenu{
	void serveLunch(String studentType) {
		if(studentType.equals("일반 학생")) {
			System.out.println("알레르기가 없습니다");
		}else if(studentType.equals("주의 학생")) {
			System.out.println("특별 배식구로 가주세요");
		}
	}
}

 

올바른 예제)

기존 코드를 수정하지 않고 기능을 확장함

>>추상 클래스와 인터페이스를 사용하여 상속, 구현을 통한 클래스 관계 구축

interface LunchMenuInterface{
	void serveLunch();
}
class RegularStudent implements LunchMenuInterface{
	@Override
	public void serveLunch() {
		System.out.println("일반 학생 - 알레르기가 없습니다");
	}
}
class VegetableStudent implements LunchMenuInterface{
	@Override
	public void serveLunch() {
		System.out.println("채식 학생 - 샐러드를 제공합니다");
	}
}
class AllergyStudent implements LunchMenuInterface{
	@Override
	public void serveLunch() {
		System.out.println("주의 학생 - 특별 배식구로 이동하세요");
	}
}

 

LSP: Liskov Substitution Principle(리스코프 치환 원칙)

상위 클래스의 객체를 하위 클래스의 객체로 치환하더라도 프로그램의 동작이 일관되게 유지되어야 함

자식 클래스는 부모 클래스의 기능을 완전하게 대체할 수 있음

업캐스팅 상태에서 부모의 메소드 사용 시 동작 가능

 

잘못된 예제)

>> 추상적인 메소드를 통해 하위 클래스에서 구체적인 기능의 선택이 가능하도록 설계해야 함

class Student {
	void playSoccer() {
		System.out.println("축구를 할 수 있습니다");
	}
}
class InjuredStudent extends Student{
	void playSoccor() {
		// 부모가 가진 기능의 일관성 X
		System.out.println("다쳐서 축구를 할 수 없습니다");
	}
}

올바른 예제)

abstract class StudentAbstract{
	abstract void activity();
}
class SoccorPlayer extends StudentAbstract{
	@Override
	void activity() {
		System.out.println("저는 축구를 합니다");
	}
}
class InjuredSoccorPlayer extends StudentAbstract{
	@Override
	void activity() {
		System.out.println("다쳐서 축구를 할 수 없습니다. 재활 훈련을 합니다");
	}
}

 

ISP: Interface Segregation Principle (인터페이스 분리 원칙)

하나의 큰 인터페이스보다 여러개의 작은 인터페이스 사용을 권장

인터페이스의 단일 책임을 강조

클래스는 자신이 사용하지 않는 메소드에 의존하지 않아야 함

cf) SRP는 하나의 클래스가 하나의 역할을 가져야 함을 강조

 

잘못된 예제)

필요하지 않은 행위를 강제로 구현

interface Worker{
	void work();
	void eat();
}
class Robot implements Worker{
	@Override
	public void work() {
		System.out.println("로봇은 일을 할 수 있습니다");
	}
	@Override
	public void eat() {
		System.out.println("로봇은 음식을 먹지 않습니다");
	}
}

올바른 예제)

각 행위의 인터페이스가 분리

interface Workable{
	void work();
}
interface Eatable{
	void eat();
}
class Employee implements Workable, Eatable{
	@Override
	public void eat() {
		System.out.println("12시부터 13시까지 점심시간 입니다");	
	}
	@Override
	public void work() {
		System.out.println("9시부터 18시까지 일을 합니다");
	}
}
class RobotClass implements Workable{
	@Override
	public void work() {
		System.out.println("로봇이 일을 합니다");	
	}
}

 

DIP: Dependency Inversion Principle (의존 역전 원칙)

고수준 모듈은 저수준 모듈에 의존해서는 안됨.

모두 추상화에 의존해야 함

>>세부 구현이 아닌, 추상화 된 인터페이스에 의존하도록 설계해야 함

 

잘못된 예제)

하위클래스가 상위클래스에 의존

class MathBook{
	void read() {
		System.out.println("수학책을 읽습니다");
	}
}
class WrongStudent{
	private MathBook book;

	public WrongStudent() {
		this.book = new MathBook();
	}
	void study() {
		book.read();
	}
}

 

올바른 예제)

클래스 간의 결합도를 줄이고, 추상화에 의존

interface Book{
	void read();
}

class ScienceBook implements Book{
	public void read() {
		System.out.println("과학책을 읽습니다");
	}
}
class KoreanBook implements Book{
	public void read() {
		System.out.println("국어책을 읽습니다");
	}
}
class CorrectStudent{
	private Book book;
	
	public CorrectStudent(Book book) {
		this.book = book;
	}
	void study() {
		book.read();
	}
}