본문 바로가기
CS공부

기본기 닦기(3) - OOP의 4가지특징과 SOLID

by 티코딩 2023. 12. 28.

간만에 쓴다. 연말이라고 너무 해이해졌다. 다시 열심히 해보자.

오늘은 OOP의 5대원칙(SOLID), 4가지 특징에대해 포스팅 해보겠다.

 

ㅇ OOP의 4가지 특징

OOP는 다들 알다시피 객체지향프로그래밍이다. 객체지향 프로그래밍의 4가지 특징은 캡상추다.

 

  • 캡슐화 : 쉽게말하면 데이터와 해당 데이터를 다루는 메서드를 클래스라는 캡슐에 넣어 객체의 내부 구현을 외부로부터 숨기고, 외부에선 오직 공개된 메서드를 통해서만 객체와 상호작용을 하도록 만든것이다. 왜 이렇게 할까? 정보은닉을 통한 장점으로 외부의 직접적인 접근을 방지해 객체의 내부 구현이 바뀌더라도 외부 코드에 미치는 영향이 최소화된다. 이는 유지보수성도 좋아진다. 구현방법은 멤버변수에 접근제어자를 private로 설정하고 getter/setter로 변수를 설정하고 꺼내올수 잇는 메서드를 만들어 사용한다.

 

  • 상속화 : 하나의 클래스가 다른 클래스의 특성과 동작을 그대로 물려받아 확장하는 메커니즘으로 코드의 재사용성과 계층구조의 정의를 통해 객체 간 관계를 명확히 할 수 있다. 상속은 공통된 특성이나 동작을 갖는 클래스를 디자인할 때, 이를 부모 클래스로 정의하고 다양한 자식 클래스에서 상속받아 사용할 수 있다. 새로운 요구사항이나 기능이 추가되면, 해당 요구사항을 수용할 새로운 자식 클래스를 만들거나 기존의 클래스를 수정하여 확장할 수 있는 장점이 있다. 이거 쓰다가 class 를 extends하는것과 interface를 implements 하는 차이가 궁금해져서 찾아봤다. 클래스의 상속은 단일상속만 되고, 자식 클래스는 부모 클래스의 멤버를 사용하거나 재정의할 수 있다. 인터페이스 구현은 다중 상속을 지원하며, 클래스에게 여러 개의 인터페이스를 구현할 수 있으며, 메서드는 선언만하므로 다양한 클래스가 동일한 인터페이스를 구현할 수 있다. 둘은 언제 쓰는게 좋을까?

클래스 상속을 사용하는 경우:

- "IS-A" 관계:

클래스 간에 명확한 "IS-A" 관계가 있을 때 클래스 상속을 사용합니다. 예를 들어, "개는 동물이다"와 같이 개가 동물의 일종이라는 관계가 있습니다.

- 공통된 코드와 동작:

부모 클래스와 자식 클래스 간에 공통된 코드나 동작이 많이 있을 때 클래스 상속을 사용하여 중복을 피할 수 있습니다.

- 상속 계층 구조:

클래스 간의 계층 구조를 형성하고 싶을 때 클래스 상속을 사용합니다. 예를 들어, 여러 동물의 종류를 나타내는 클래스들이 상속 계층을 형성할 수 있습니다.

인터페이스 구현을 사용하는 경우:

- "CAN-DO" 관계:

클래스 간에 "CAN-DO" 관계가 있을 때 인터페이스 구현을 사용합니다. 예를 들어, 비행 가능한 객체는 Flyable 인터페이스를 구현할 수 있습니다.

- 다중 상속이 필요한 경우:

클래스가 여러 개의 상속을 받아야 하는 경우, 인터페이스 구현을 사용합니다. Java에서는 다중 클래스 상속을 허용하지 않기 때문에, 다양한 기능을 제공하는 여러 인터페이스를 구현할 수 있습니다.

- 결합도 낮추기:

클래스와 클래스 간의 결합도를 낮추고 유연성을 높이고 싶을 때 인터페이스를 사용합니다. 특히, 여러 클래스가 동일한 인터페이스를 구현함으로써 서로 독립적으로 변경 및 확장할 수 있습니다.

- 다형성 활용:

다양한 객체가 동일한 인터페이스를 구현하면서 다형성을 활용하고 싶을 때 인터페이스를 사용합니다.

 

  • 추상화 : 추상화는 복잡한 현실 세계를 단순화하고 모델화하는 프로세스를 의미함. 객체 지향 프로그래밍(OOP)에서 추상화는 주로 클래스와 인터페이스를 통해 이루어지며, 공통된 특성과 동작을 추출하여 추상적인 개념으로 표현하는 것을 포함한다. 

- 클래스를 통한 추상화

class Car {
    String brand;
    String model;

    void start() {
        // 구체적인 구현
    }

    void accelerate() {
        // 구체적인 구현
    }
}

- 인터페이스를 통한 추상화

interface Shape {
    void draw(); // 추상 메서드
    double calculateArea(); // 추상 메서드
}

class Circle implements Shape {
    // Circle에 특화된 draw 및 calculateArea의 구체적인 구현
}

class Square implements Shape {
    // Square에 특화된 draw 및 calculateArea의 구체적인 구현
}

- 추상 클래스를 통한 추상화

 

 

  • 다형화 : 어떤 객체의 속성이나 기능이 상황에 따라 여러 형태로 변할 수 있는 특징을 말한다. 정적 다형화로 오버로딩을 쓴다. 동일한 이름의 메서드로 다른 매개변수를 갖는 방법이다.
class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
}

동적 다형화로는 부모 클래스의 메서드를 자식 클래스에서 동일한 시그니처로 다시 정의하는 오버라이딩을 쓴다.

class Animal {
    void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Bark! Bark!");
    }
}

class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow");
    }
}

 

 

ㅇ OOP의 5가지 원칙(SOLID)

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

하나의 클래스는 하나의 책임만 가져야 한다. 클래스가 여러 책임을 가지면 코드를 이해하고 수정하기가 어려워지며, 한 책임의 변경이 다른 책임에 영향을 미칠 수 있음.

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

소프트웨어의 구성 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다. 새로운 기능이 추가될 때 기존의 코드를 변경하지 않고 확장이 가능하도록 해야 한다.

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

자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다. 이것은 상속 관계에서 하위 클래스가 상위 클래스의 기능을 정확하게 수행할 수 있어야 함을 의미함.

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

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 됨. 즉, 큰 인터페이스들을 나누어 더 작은 구성 요소로 분리하여 클라이언트가 필요로 하는 것만 이용할 수 있도록 해야 한다.

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

고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 양쪽 모두 추상화에 의존해야 한다. 또한, 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항은 추상화에 의존해야 한다.