급하신 분들은 마지막에 요약해놨으니, 그부분만 보셔도 무관합니다!
들어가기에 앞서...
♤ 객체 지향 프로그래밍?
- 정의
- 절차적 프로그래밍 : 반복될 가능성이 있는 것들을 재사용이 가능한 함수로 만들어 사용하는 방식
- OOP : 특정한 개념의 함수와 자료형을 함께 묶어서 관리하기 위해 탄생한 방법
- 객체 내부에 자료형(field)와 함수(method)가 같이 존재함.
- 중요한 4가지 특징
- 추상화
- 필요로 하는 속성이나 행동을 추출하는 작업
- 세부적인 사물들의 공통적인 특징을 파악 후, 하나의 집합으로 만들어 내는것. (아우디, 벤츠는 모두 “자동차” → 자동차 라는 추상화 집합을 만듬!. 여기서 공통 특징을 만드는 것!!)
- 캡슐화
- 낮은 결합도를 유지할 수 있도록 설계하는 것
- 한 곳에서 변화가 일어나도, 다른 곳에 미치는 영향을 최소화시키는 것!
- 결합도란, 어떤 기능을 실행할때, 다른 class 나 module이 얼마나 의존적인지를 나타내는 것.
- 낮은 결합도, 높은 응징도? → 정보 은닉을 활용함! private를 선언하는 이유임..ㅎ
- 상속
- 일반화 관계 라고 하며, 여러 개체들이 지닌 공통 특성을 부각 → 하나의 개념이나 법칙으로 성립하는 과정
- 자식 클래스를 외부로부터 은닉하는 캡슐화의 일종
- 기능의 확장 관점일때 사용하기!!!
- 상위 클래스 변경 X, 불필요한 클래스 증가, 상속 잘못 → Composition으로 해결
- Composition : 필드에서 다른 객체를 참조하는 방식
- 다형성 (핵심)
- 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것!!!!!!!!!!!!!!!!!!!!!
- 오버로딩 vs 오버라이딩
- 오버로딩 : 메서드 이름은 동일, 매개변수의 타입, 개수가 다름.
- 얘는 부생성자 등등
- 오버라이딩 : 상위 클래스의 메서드를 하위 클래스에서 재정의!!
- 얘는 그냥 super() 등
- 오버로딩 : 메서드 이름은 동일, 매개변수의 타입, 개수가 다름.
- 추상화
SOLID 원칙
- 객체 지향 프로그래밍에서 유지보수성과 확정성을 높이기 위한 설계 원칙
- 즉, 코드를 더 유지보수 가능하고, 확장 가능하며, 유연한 설계로 만드는데 도움을 주는 것!
Single Responsibility Principle (단일 책임 원칙) - SRP
- 원칙 : 클래스는 하나의 책임만 가져야 하며, 변경의 이유도 하나여야 한다.
- 목표 : 코드의 응집도를 높이고, 수정이 필요할 때 영향을 최소화한다.
// 잘못된 설계: 한 클래스가 두 가지 책임(보고서 생성, 출력)을 가짐
public class Report {
public void generateReport(String data) {
System.out.println("Report generated with data: " + data);
}
public void printReport(String data) {
System.out.println("Printing report: " + data);
}
}
// 개선된 설계: 책임을 분리
public class ReportGenerator {
public String generateReport(String data) {
return "Report generated with data: " + data;
}
}
public class ReportPrinter {
public void printReport(String report) {
System.out.println("Printing report: " + report);
}
}
// 사용
public class Main {
public static void main(String[] args) {
ReportGenerator generator = new ReportGenerator();
ReportPrinter printer = new ReportPrinter();
String report = generator.generateReport("Sales Data");
printer.printReport(report);
// 출력: Printing report: Report generated with data: Sales Data
}
}
Open/Closed Principle (개방-폐쇄 원칙) - OCP
- 원칙 : SW 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야하지만, 수정에는 닫혀 있어야한다.
- 목표 : 기존 코드를 변경하지 않고도 기능을 확장할 수 있도록 설계해야한다.
// 잘못된 설계: 새로운 도형 추가 시 기존 코드 수정 필요
class Shape {
public String type;
}
class DrawingTool {
public void draw(Shape shape) {
if (shape.type.equals("circle")) {
System.out.println("Drawing a Circle");
} else if (shape.type.equals("rectangle")) {
System.out.println("Drawing a Rectangle");
}
}
}
// 개선된 설계: 인터페이스를 활용하여 새로운 도형 추가 가능
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a Circle");
}
}
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing a Rectangle");
}
}
class DrawingTool {
public void drawShape(Shape shape) {
shape.draw();
}
}
// 사용
public class Main {
public static void main(String[] args) {
DrawingTool tool = new DrawingTool();
Shape circle = new Circle();
Shape rectangle = new Rectangle();
tool.drawShape(circle); // 출력: Drawing a Circle
tool.drawShape(rectangle); // 출력: Drawing a Rectangle
}
}
Liskov Substitution Principle(리스코프 치환 원칙) - LSP
- 원칙 : 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
- 목표 : 상속 관계에서 부모 클래스를 사용하는 코드가 자식 클래스에서도 동작하도록 보장한다.
// 잘못된 설계: 자식 클래스가 부모 클래스의 행동을 위반
class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Penguin extends Bird {
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
// 개선된 설계: 공통 행동을 추상화하고, 자식 클래스에서 올바르게 구현
abstract class Bird {
public abstract void move();
}
class FlyingBird extends Bird {
public void move() {
System.out.println("Flying");
}
}
class Penguin extends Bird {
public void move() {
System.out.println("Swimming");
}
}
// 사용
public class Main {
public static void main(String[] args) {
Bird flyingBird = new FlyingBird();
Bird penguin = new Penguin();
flyingBird.move(); // 출력: Flying
penguin.move(); // 출력: Swimming
}
}
Interface Segregation Principle (인터페이스 분리 원칙) - ISP
- 원칙 : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
- 목표 : 큰 인터페이스를 작은 인터페이스로 분리하여 필요한 기능만 제공한다.
// 잘못된 설계: 모든 메서드를 가진 거대한 인터페이스
interface Machine {
void print();
void scan();
void fax();
}
class Printer implements Machine {
public void print() {
System.out.println("Printing...");
}
public void scan() {
throw new UnsupportedOperationException("Scan not supported");
}
public void fax() {
throw new UnsupportedOperationException("Fax not supported");
}
}
// 개선된 설계: 기능별로 인터페이스 분리
interface Printer {
void print();
}
interface Scanner {
void scan();
}
class SimplePrinter implements Printer {
public void print() {
System.out.println("Printing...");
}
}
class AdvancedPrinter implements Printer, Scanner {
public void print() {
System.out.println("Printing...");
}
public void scan() {
System.out.println("Scanning...");
}
}
// 사용
public class Main {
public static void main(String[] args) {
Printer printer = new SimplePrinter();
printer.print(); // 출력: Printing...
AdvancedPrinter advancedPrinter = new AdvancedPrinter();
advancedPrinter.print(); // 출력: Printing...
advancedPrinter.scan(); // 출력: Scanning...
}
}
Dependency Inversion Principle (의존성 역전 원칙) - DIP
- 원칙 : 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.
- 목표 : 구체적인 구현이 아닌, 추상화에 의존함으로써 유연한 설계를 해야한다.
// 잘못된 설계: 고수준 모듈이 저수준 모듈에 직접 의존
class Light {
public void turnOn() {
System.out.println("Light On");
}
}
class Switch {
private Light light;
public Switch(Light light) {
this.light = light;
}
public void toggle() {
light.turnOn();
}
}
// 개선된 설계: 인터페이스를 통해 의존성 역전
interface Switchable {
void turnOn();
}
class Light implements Switchable {
public void turnOn() {
System.out.println("Light On");
}
}
class Fan implements Switchable {
public void turnOn() {
System.out.println("Fan On");
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void toggle() {
device.turnOn();
}
}
// 사용
public class Main {
public static void main(String[] args) {
Switchable light = new Light();
Switchable fan = new Fan();
Switch lightSwitch = new Switch(light);
Switch fanSwitch = new Switch(fan);
lightSwitch.toggle(); // 출력: Light On
fanSwitch.toggle(); // 출력: Fan On
}
}
전체 요약
- S : 하나의 클래스는 하나의 책임만 가져야 한다
- O : 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있어야 한다.
- L : 부모 클래스 대신 자식 클래스를 사용할 수 있어야 한다.
- I : 클라리언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다.
- D : 고수준 모듈과 저수준 모듈이 모두 추상화에 의존해야 한다.
'CS > 기타' 카테고리의 다른 글
(CS) 네트워크 - 네트워크 계층 IP (0) | 2025.03.22 |
---|---|
(CS) 네트워크 - 물리 계층과 데이터 링크 계층 (0) | 2025.03.19 |
(CS) 네트워크 - 기본구조 (1) | 2025.03.18 |
(CS) 정렬 - 퀵 정렬 (0) | 2025.01.21 |
(CS) 의존성 주입 (0) | 2025.01.09 |