디자인 패턴

객체 지향 프로그래밍

iksadnorth 2023. 7. 21. 12:28

👣 개요

객체들의 집합으로 프로그램을 표현하며
데이터를 객체로 취급해 객체 내부에 선언된 메서드를 활용하는 방식.

👣 OOP 특징

1. 추상화
복잡한 시스템으로부터 핵심 개념을 간추리는 것.

2. 캡슐화
객체의 속성, 메소드를 하나로 묶고 외부에 감추는 것.

3. 상속성
상위 클래스의 하위 클래스가 이어받아 재사용, 확장하는 것.

4. 다형성
하나의 메서드를 다양한 방법으로 동작시키는 것.

👣 OOP 설계 원칙

1. SRP - 단일 책임 원칙
모든 클래스는 하나의 책임만 가진다.

// 위반 예시
class Report {
    public void generateReport() {
        // 보고서를 생성하는 코드
    }
    public void saveReportToFile() {
        // 보고서를 파일로 저장하는 코드
    }
    public void sendReportByEmail() {
        // 보고서를 이메일로 보내는 코드
    }
}

// 지킨 예시
class ReportGenerator {
    public void generateReport() {
        // 보고서를 생성하는 코드
    }
}

class ReportSaver {
    public void saveReportToFile() {
        // 보고서를 파일로 저장하는 코드
    }
}

class ReportMailer {
    public void sendReportByEmail() {
        // 보고서를 이메일로 보내는 코드
    }
}

2. OCP - 개방-폐쇄 원칙
유지 보수 사항이 생길 때,
코드 확장에는 열린, 코드 수정에는 닫힌 상태여야 한다.

// 위반 예시
class Shape {
    private String type;
    
    Constructor & getter ...
    
    // 새로운 도형이 생길 때마다 코드를 수정해야 함.
    public void draw() {
        if (type.equals("circle")) {
            drawCircle();
        } else if (type.equals("rectangle")) {
            drawRectangle();
        } else if (type.equals("triangle")) {
            drawTriangle();
        }
    }
    
    public void drawCircle() {
        // 원 그리기 기능
    }
    
    public void drawRectangle() {
        // 직사각형 그리기 기능
    }
    
    public void drawTriangle() {
        // 삼각형 그리기 기능
    }
}

// 지킨 예시
// 별 차이 없어 보이지만 각 클래스는 파일 단위로 구분되어
// 파일을 생성할 뿐 작성했던 파일을 수정하지는 않는다.
interface Shape {
    void draw();
}

class Circle implements Shape {
    @Override
    public void draw() {
        // 원 그리기 기능
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        // 직사각형 그리기 기능
    }
}

class Triangle implements Shape {
    @Override
    public void draw() {
        // 삼각형 그리기 기능
    }
}

3. LSP - 리스코프 치환 법칙
상위 클래스의 객체에 하위 클래스의 객체를 적용해도 문제없이 돌아가야 한다.

// 위반 예시
class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // LSP 원칙 위배: 정사각형의 경우 width와 height가 동일해야 함
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height; // LSP 원칙 위배: 정사각형의 경우 width와 height가 동일해야 함
        this.height = height;
    }
}

// 지킨 예시
interface Shape {
    int getArea();
}

class Rectangle implements Shape {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    @Override
    public int getArea() {
        return width * height;
    }
}

class Square implements Shape {
    protected int side;
    
    public void setSide(int side) {
        this.side = side;
    }
    
    @Override
    public int getArea() {
        return side * side;
    }
}

4. ISP - 인터페이스 분리 원칙
하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스를 만들어야 한다.

// 위반 예시
interface Worker {
    void work();
    void eat();
    void sleep();
}

class Human implements Worker {
    @Override
    public void work() {
        // 일하는 코드
    }
    
    @Override
    public void eat() {
        // 먹는 코드
    }
    
    @Override
    public void sleep() {
        // 잠자는 코드
    }
}

class Robot implements Worker {
    @Override
    public void work() {
        // 일하는 코드
    }
    
    @Override
    public void eat() {
        // 로봇은 먹지 않음 - ISP 원칙 위배
    }
    
    @Override
    public void sleep() {
        // 로봇은 잠들지 않음 - ISP 원칙 위배
    }
}

// 지킨 예시
interface Worker {
    void work();
}

interface Eater {
    void eat();
}

interface Sleeper {
    void sleep();
}

class Human implements Worker, Eater, Sleeper {
    @Override
    public void work() {
        // 일하는 코드
    }
    
    @Override
    public void eat() {
        // 먹는 코드
    }
    
    @Override
    public void sleep() {
        // 잠자는 코드
    }
}

class Robot implements Worker {
    @Override
    public void work() {
        // 일하는 코드
    }
}

5. DIP - 의존 역전 원칙
쉽게 변하지 않는 추상화된 인터페이스나
쉽게 변하지 않는 상위 클래스를 사용해
변화에 영향을 받지 않게 해야 한다.

// 위반 예시
class FileLogger { // 해당 클래스는 추상화가 되지 않아 변동 가능성이 크다.
    public void log(String message) {
        // 파일에 로그 기록하는 코드
    }
}

class UserManager {
    private FileLogger logger;
    
    public UserManager() {
        logger = new FileLogger(); // 저수준 모듈에 직접 의존 - DIP 원칙 위배
    }
    
    public void createUser(String username) {
        logger.log("User created: " + username);
    }
}

// 지킨 예시
interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 파일에 로그 기록하는 코드
    }
}

class UserManager {
    private Logger logger;
    
    public UserManager(Logger logger) {
        this.logger = logger; // 추상화된 인터페이스를 통한 의존성 주입
    }
    
    public void createUser(String username) {
        logger.log("User created: " + username);
    }
}

'디자인 패턴' 카테고리의 다른 글

Composite Pattern - 구조  (0) 2023.07.30
Facade Pattern - 구조  (0) 2023.07.30
Programming Paradigm  (0) 2023.07.19
MVC Pattern - 아키텍쳐  (0) 2023.07.19
Iterator Pattern - 행위  (0) 2023.07.19