SOLID 원칙이란?
SOLID 법칙이란, 객체지향 프로그래밍 및 설계의 다섯가지 기본 원칙이다. 시간이 지나도 유지 보수 및 확장이 쉬운 시스템을 만들고자 할때 이 원칙들을 함께 적용할 수 있다. SOLID 원칙에는 총 5가지의 원칙이 있으며, 하나씩 알아보자
1. 단일책임의 원칙(SRP, Single Responsibility Principle)
단일 책임 원칙이란, 하나의 클래스(혹은 객체)는 단 하나의 책임만을 가진다 라는것을 가진 원칙이다. '책임'이 많다라는것은, 곧 변경될 여지가 많다는 것과 동일한 의미이다. 또한 책임을 많이 가질수록, 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높다. 두가지 이상의 기능이 필요한 경우에는, 클래스를 나눠야한다.
SRP와 관련되어 "산탄총 수술" 문제가 있다. 이는 변경 이유가 발생했을때 소스코드의 변경할 곳이 많아지는 현상을 의미하며, 이런 경우, 변경될 곳을 빠짐없이 찾아서 일관되게 변경해야된다.
2. 개방 폐쇄 원칙(OCP, Open Close Principle)
개방 폐쇄 원칙이란, 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 하는것을 의미한다. 여기서 '변화의 단위' 는 '클래스'를 의미한다. 인터페이스 혹은 추상클래스를 사용하여 준수할 수 있다.
3. 리스코프 치환 원칙(LSP, Liskov Substitution Principle)
리스코프 치환 원칙은 부모클래스와 자식클래스 사이에 행위가 일관성 있어야 된다의 의미를 가지고 있는 원칙이다. LSP는 일반화(즉, 상속 의미)에 관한 이야기이며, 최소한 자신의 부모 클래스에서 가능한 행위는 수헹할 수 있어야 한다. LSP를 만족하면, 프로그램에서 부모클래스의 인스턴스 대신에 자식클래스의 인스턴스로 대체해도 프로그램의 의미는 변화되지 않는다. LSP를 만족하는 가장 단순한 방법은 부모클래스의 메소드를 자식클래스에서 재정의하지 않는것이다.
class Bag{
private int price;
public void setPrice(int price){
this.price = price;
}
public int getPrice(){
return this.price;
}
}
class DicountedBag extends Bag{
private double discountRate = 0;
public void setDiscountRate(double discountRate){
this.discountRate = discountRate;
}
public void applyDiscount(int price){
super.setPrice(price - (int)(discountRate * price));
}
}
4. 의존 역전 원칙(DIP, Dependency Inversion Priciple)
DIP는 의존관계를 맺을때 변화하기 쉬운것 또는 자주 변화하는것보다는 변화하기 어려운것, 거의 변화가 없는것에 의존하라는 원칙이다.
- 인터페이스 -> 변화하기 어려운것(정책,전략과 같은 큰 흐름이나 개념을 주로 의미한다.)
- 구체 클래스 -> 변화하기 쉬운것(구체적인 방식, 사물등)
여기서 "Toy"는 '장난감' 이라는 큰 개념이고, Robot, Lego, Toy Car는 장난감이라는 집합에 속하는 '구체적인 사물'이다.
의존성 주입(Dependency Injection)
클래스의 외부에서 의존되는것을 대상 객체의 인스턴스 변수에 주입하는것을 의미한다. 아래 코드를 보면, Kid클래스의 setToy() 메소드에 매개변수 타입을 상위 개념인 Toy type으로 지정함으로서, Toy를 상속받은 Robot, Lego 모두 매개변수로 넘겨줄 수 있는것을 볼 수 있다.(객체지향의 다형성 성질 이용)
class Kid{
private Toy toy;
public void setToy(Toy toy){
this.toy = toy;
}
public void play(){
System.out.println(toy);
}
}
abstract class Toy{
@Override
public String toString() {
return getClass().getSimpleName();
}
}
class Robot extends Toy{}
class Lego extends Toy{}
class main_{
public static void main(String[] args){
Kid k = new Kid();
k.setToy(new Robot());
k.play();
k.setToy(new Lego());
k.play();
}
}
5. 인터페이스 분리원칙(ISP, Interface Segregation Principle)
인터페이스를 클라이언트에 특화되도록 분리시키라는 원칙이다. 클라이언트 관점에서 자신이 이용하지 않는 기능에는 영향을 받지 않아야 한다는 의미를 담은 원칙이다. 효율적인 메소드 작성을 하기 위해서는 꼭 해당 기능과 관련있는 메소드만 작성해 주어야 한다.
import java.util.ArrayList;
interface onlyFax{
public abstract void fax();
}
interface onlyPrint{
public abstract void print();
}
interface onlyCopy{
public abstract void copy();
}
class IntegratedMachine implements onlyCopy,onlyFax,onlyPrint{
@Override
public void fax() {
System.out.println("fax");
}
@Override
public void print() {
System.out.println("Do print");
}
@Override
public void copy() {
System.out.println("Do copy");
}
}
interface client{
public abstract void use();
}
class printClient implements client{
onlyPrint p;
public printClient(onlyPrint p){
this.p = p;
}
public void use(){
p.print();
}
}
class copyClient implements client{
onlyCopy c;
public copyClient(onlyCopy c){
this.c = c;
}
public void use(){
c.copy();
}
}
class main_{
public static void main(String[] args){
IntegratedMachine i = new IntegratedMachine();
ArrayList<client> a = new ArrayList<>();
a.add(new printClient(i));
a.add(new copyClient(i));
for(client c : a){
c.use();
}
}
}