기본기 닦기(3) - OOP의 4가지특징과 SOLID
간만에 쓴다. 연말이라고 너무 해이해졌다. 다시 열심히 해보자.
오늘은 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):
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 양쪽 모두 추상화에 의존해야 한다. 또한, 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항은 추상화에 의존해야 한다.