늦은 프로그래밍 이야기
SOLID 원칙 본문
SRP (Single Responsibility Principle : 단일 책임 원칙)
- 작성된 클래스는 하나의 기능만 가지며, 하나의 책임(변화의 축)을 수행하는데 집중되어 있어야 한다는 원칙.
- 클래스를 변경해야 하는 이유가 오직 하나여야 한다.
- 하나의 책임의 기준은 변경이다.
- 변경이 발생하였을 때, 변경해야 될 부분이 적으면, 단일 책임 원칙을 잘 따른 것이다.
- 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유롭다.
- 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수가 용이하다.
응집도
- 단일 책임 원칙은 "응집도(cohesion)와 관련이 있다.
- 응집도 : 하나의 클래스는 하나의 추상적인 개념을 나타내야 한다. 또한 각 클래스는 클래스의 목적과 의미를 한 줄로 기술할 수 있어야 한다. 만약 클래스를 간단하게 기술할 수 없다면, 아마도 하나 이상의 추상적인 개념을 나타내고 있을 것이다. 클래스에 추가된 책임들은 클래스의 설명에 부합해야 한다.
적용방법
- 책임을 각각의 개별 클래스로 분할하여 클래스 당 하나의 책임만을 맡도록 한다.
- 책임만 분리하는 것이 아니라 분리된 두 클래스 간의 관계의 복잡도를 줄이도록 설계한다.
- 각각의 클래스들이 유사하고 비슷한 책임을 중복해서 갖고 있다면 각각의 클래스들의 공유되는 요소를 부모 클래스로 정의하여 부모 클래에 위임한다.
적용예시
SRP 적용 전
class Galaxy {
private String serialNumber;
private String cpu;
private String memory;
private int battery;
private double weight;
}
- serialNumber는 변화요소가 아니고 고유정보라고 할 수 있다.
- cpu, memory, battery, weight는 모두 특성 정보군으로 변경이 발생 할 수 있는 부분. 변화요소.
- 특성 정보군에 변화가 발생하면 항상 해당 클래스를 수정해야 하는 상황이 발생하므로 이 부분이 SRP적용의 대상이 된다.
SRP 적용 후
// 스펙만 관리
class GalaxySpec {
private String cpu;
private String memory;
private int battery;
private double weight;
}
class Galaxy {
private String serialNumber;
private GalaxySpec spec;
public Galaxy(String serialNumber, GalaxySpec spec) {
this.serialNumber = serialNumber;
this.spec = spec;
}
}
- 변화가 예상되는 특성 정보군을 분리한다.
- 따라서 특성 정보에 변경이 일어나면 GalaxySpec 클래스만 변경하면 된다.
OCP (Open Close Principle : 개방 폐쇄의 원칙)
- 확장에는 열려있고, 변경에는 닫혀 있어야 한다는 원칙.
- 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한 극대화 해야한다.
- 변경이나 추가사항이 발생 하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며 기존 기성요소를 쉽게 확장해서 재사용할 수 있어야 한다.
- 중요 메커니즘은 추상화와 다형성. 객체지향의 장점을 극대화하는 아주 중요한 원리.
템플릿 메소드 패턴 (Template Method Pattern)
적용방법
- 변경될 것과 변하지 않을 것을 엄격히 구분한다.
- 두 모듈이 만나는 지점에 인터페이스를 정의한다.
- 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성한다.
적용예시
OCP 적용 전
class GalaxySpec {
private String cpu;
private String memory;
private int battery;
private double weight;
}
class Galaxy {
private String serialNumber;
private GalaxySpec spec;
public Galaxy(String serialNumber, GalaxySpec spec) {
this.serialNumber = serialNumber;
this.spec = spec;
}
}
- SRP원칙을 적용하여 변경이 예상되는 부분을 뽑아 새로운 클래스에 변화요소들을 하나로 모았다.
- 하지만 갤럭시 외의 새로운 핸드폰들이 생겨나면 변경이 발생할 수 있다.
OCP 적용 후
class Phone {
private String serialNumber;
private PhoneSpec spec;
public Phone(String serialNumber, PhoneSpec spec) {
this.serialNumber = serialNumber;
this.spec = spec;
}
}
class PhoneSpec {
private String cpu;
private String memory;
private int battery;
private double weight;
}
class Galaxy extends Phone
class IPhone extends Phone
class 샤오미 extends Phone
class Sony extends Phone
- 추가될 핸드폰들의 공통 속성을 모두 담은 Phone 클래스 생성. 추상화 작업.
- 새로운 핸드폰이 추가되면서 발생하는 부분을 분리하여 수정을 최소화.
LSP (Liskov Substitution Principle : 리스코브 치환의 원칙)
- 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
- 상속은 궁극적으로는 다형성을 통한 확장성 획득을 목표로 한다.
- 다형성과 확장성을 극대화 하려면 하위 클래스를 사용하는 것보다는 상위의 클래스(인터페이스)를 사용하는 것이 좋다.
- 일반적으로 선언은 기반 클래스로, 생성은 구체 클래스로 대입하는 방법을 사용.
적용방법
- 만약 두 개체가 똑같은 일을 한다면 둘을 하나의 클래스로 표현하고 이들을 구분할 수 있는 필드를 둔다.
- 똑같은 연산을 제공하지만, 이를 약간씩 다르게 한다면 공통의 인터페이스를 만들고 둘이 이를 구현한다. (인터페이스 상속)
- 공통된 연산이 없다면 완전 별개인 2개의 클래스를 만든다.
- 만약 두 개체가 하는일에 추가적으로 뭔가를 더 한다면 구현 상속을 사용한다.
적용예시
class Rectangle {
...
}
class Square extends Rectangle {
...
}
public static void main(String[] args) {
Ractangle rectangle = new Rectangle();
...
Rectangle square = new Square();
...
ISP (Interface Segregation principle : 인터페이스 분리의 원칙)
- 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원칙.
- 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용.
- 하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다.
- 인터페이스의 단일책임을 강조.
- 인터페이스 분리를 통해 변화에의 적응성을 획득.
적용예시
ISP 적용 전
public interface Phone {
void call(String phoneNumber);
void pay(String cardName);
void wirelessCharge();
}
public class Galaxy implements Phone {
@Override
public void call(String phoneNumber) {
// ..
}
@Override
public void pay(String cardName) {
// ..
}
@Override
public void wirelessCharge() {
// ..
}
}
public class SkyPhone implements Phone {
@Override
public void call(String phoneNumber) {
// ..
}
@Override
public void pay(String cardName) {
// ..
}
@Override
public void wirelessCharge() {
// ..
}
}
- call, pay, wirelessCharge 기능들이 하나의 인터페이스에 작성이 되었다.
- wirelessCharge 기능을 사용하지 않는 객체를 추가했을 때 문제가 발생한다.
ISP 적용 후
public interface Phone {
void call(String phoneNumber);
}
public interface WirelessCharge {
void wirelessCharge();
}
public interface Payment{
void pay(String cardName);
}
public class Galaxy implements Phone, WirelessCharge, Payment {
@Override
public void call(String phoneNumber) {
// ..
}
@Override
public void pay(String cardName) {
// ..
}
@Override
public void wirelessCharge() {
// ..
}
}
public class SkyPhone implements Phone {
@Override
public void call(String phoneNumber) {
// ..
}
}
- 여러 개의 구체적인 인터페이스를 작성하여 클래스마다 필요한 인터페이스만 사용한다.
DIP (Dependency Inversion Principle : 의존성 역전의 원칙)
- 구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의 역전.
- 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙.
적용방법
- 변하기 쉬운 것과 어려운 것을 구분 해야한다.
- 변하기 쉬운 것 : 구체적인 행동
- 변하기 어려운 것 : 흐름이나 개념 같은 추상적인 것.
- 상위 클래스와 하위 클래스를 바로 의존하게 하는 것이 아니라 둘 사이에 존재하는 추상적인 개념을 통해 의존해야 한다.
적용예시
public interface Phone {
void call(String phoneNumber);
}
public class SmartPhone implements Phone {
@Override
public void call(String phoneNumber) {
System.out.println("스마트폰 : " + phoneNumber);
}
}
public class PublicPhone implements Phone {
@Override
public void call(String phoneNumber) {
System.out.println("공중 전화 : " + phoneNumber);
}
}
public class InternetPhone implements Phone {
@Override
public void call(String phoneNumber) {
System.out.println("인터넷 전화 : " + phoneNumber);
}
}
public class Person {
private Phone phone;
public void setPhone(Phone phone) {
this.phone = phone; // 폰이 계속 바뀜.
}
public void call(String phoneNumber) {
phone.call(phoneNumber);
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setPhone(new SmartPhone());
person.setPhone(new InternetPhone());
// 코드 수정 X
person.call("01012341234"); // 스마트폰 전화,
}
}
- 구체적인 행동 : 스마트폰으로 전화를 건다. 공중전화로 전화를 건다. 인터넷전화로 전화를 건다.
- 추상적인 행동 : 전화를 건다.
- 추상적 개념인 call을 작성한 Phone이라는 인터페이스를 매개로 상위 클래스인 Person과 하위 클래스들의 메시지를 주고 받는다.
'내일배움캠프 > 객체지향 프로그래밍' 카테고리의 다른 글
| 3계층 구조 (0) | 2022.11.28 |
|---|