객체지향 설계를 할 때 지키면 좋겠다는 기본 원칙 5가지의 앞글자를 따서 SOLID라고 부른다.
- SRP(Single Responsibility Principle) : 단일 책임 원칙
- OCP(Open Closed principle) : 개방 폐쇄 원칙
- LSP(Liskov Substitution principle) : 리스코프 치환 원칙
- ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependency inversion principle) : 의존관계 역전 원칙
모든 클래스는 하나의 책임만 가져야 한다
책임이라는 단어의 뜻이 모호한데, 어떤 기능에 문제가 생겼을 때 어떤 특정 모듈(패캐지, 클래스, 메소드) 때문이라고 지목할 수 있다면 단일 책임 원칙을 잘 따른 것이라고 볼 수 있다.
SRP를 잘 지켰다면, 응집도는 높이고 결합도를 낮춰 유지보수가 용이해진다
단일 책임 원칙 위배 예시
public class Hyemco {
public static void main(String[] args) {
coding();
posting();
youtube();
cleaning();
laundering();
calling();
gift();
}
}
단일 책임 원칙 만족 예시
공통점을 가진 "Person" 클래스를 extends하여 책임에 따라 하위 클래스로 분류한다..
pacakage srp;
public class Person {
youtube();
}
pacakage srp;
public class JopSeeker extends Person {
coding();
posting();
}
pacakage srp;
public class Daughter extends Person {
cleaning();
laundering();
}
SW 요소는 확장에 대해서는 열려있고 수정에 대해서는 닫혀있어야 한다
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위타입을 사용하는 프로그램은 정상적으로 동작해야 한다
쉽게 말하자면, 자식 클래스는 최소한 부모 클래스에서 수행한 기능은 수행할 수 있어야 된다는 뜻으로 부모 클래스와 자식 클래스 사이는 행위가 일관되어야 한다
리스코프 치환 원칙 위배 예시
리스코프 치환 원칙 만족 예시
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
인터페이스란?
인터페이스로 구현된 클래스를 동일한 기능을 수행하도록 강제하는 것
인터페이스 분리 원칙 위배 예시
interface MultifunctinPrinter {
public void copy();
public void print();
public void fax();
}
Fax 클래스가 복합기 인터페이스를 implements 했기에 copy(), print(), fax() 3 메소드를 다 Override한다
public class Fax implements MultifunctinPrinter{
@Override
public void copy() {
}
@Override
public void print() {
}
@Override
public void fax() {
}
}
그러나 Fax 클래스에서 fax()의 기능만 필요할 수도 있다. fax() 기능만 이용하는 클라이언트가 print() 기능의 변경으로 인해 발생하는 문제에 영향을 받지 않도록 해야한다
인터페이스 분리 원칙 만족 예시
interface Fax {
public void fax();
}
public class Fax implements Fax{
@Override
public void fax() {
}
}
interface Print {
public void print();
}
public class Print implements Print{
@Override
public void print() {
}
}
상위 모듈이 하위 모듈에 의존해서는 안된다. 두 모듈 모두 추상화에 의존해야 한다
의존관계 역전 원칙 위배 예시
class Carrot {
public String toString() {
return "Carrot";
}
}
public class Rabbit {
private Carrot carrot;
public void setCarrot(Carrot carrot) {
this.carrot = carrot;
}
public void eat() {
System.out.println(carrot.toString());
}
}
토끼는 Carrot 클래스에 의존하고 있는데, 만약 토끼가 Carrot이 아니라 Apple로 먹이를 바꾼다면 코드에 상당한 변화가 필요하게 된다
의존관계 역전 원칙 만족 예시
위의 문제를 해결하기 위해 의존관계를 역전시켜 Carrot과 Apple을 추상적인 Vegetable이라는 상위 클래스를 상속받게 하고 토끼는 Vegetable 클래스에 의존하게 만들어 줘야한다
public class Carrot extends Vegetable {
public String toString() {
return "Carrot";
}
}
public class Apple extends Vegetable {
public String toString() {
return "Apple";
}
}
public class Main {
public static void main(String[] args) {
// 먹이가 Carrot일 경우
Vegetable v = new Carrot();
Rabbit r = new Rabbit();
r.setVegetable(v);
r.eat();
// 먹이가 Apple일 경우
Vegetable v = new Apple();
Rabbit r = new Rabbit();
r.setVegetable(v);
r.eat();
}
}
SOLID - 객체 지향을 할 때 지켜야 하는 5대 원칙