객체지향 SOLID 의 원칙
in Programming on Design Pattern
객체지향 SOLID 다섯가지 원칙을 나름대로 정리해보려고 한다.
공부하면서 느낀건데 단순 설명보다는 역시 예제코드가 있어야 이해가 빠르게 되더라.
이에 각 원칙 별 설명과 예제코드를 첨부한다.
SRP(Single Responsibility Principle) - 단일 책임의 원칙
하나의 객체는 하나의 책임만을 가져야함.
public class Student{
public void getCourses(){..}
public void addCourse(Course c){..}
public void save(){...}
public Student load() {...}
public void printOnReportCard(){..}
public void printOnAttendanceBook(){..}
}
위와같이 Student Class가 너무나 많은 책임을 수해앻야하는것은 SRP에 위배된다.
Student Class에 할당된 책임 중에 가장 잘할 수 있는 것은 수강과목을 추가하고 조회하는일이다. 데이터베이스에 학생정보를 저장하거나 불러오는 일이나 성적표, 출석부에 출력하는 일은 다른 Class가 더 잘할 여지가 많다.
public class Student{
public void getCourses(){..}
public void addCourse(Course c){..}
}
이와같이 Student Class는 수강과목 추가, 조회 책임만 수행하게하고 다른 일들은 StudentDAO class, 성적표 class, 출석부class 등을 만들어 수행하게 하는것이 객체간의 결합을 느슨하게하고 기능의 수정 및 추가를 용이하게 만든다.
OCP(Open-Closed Principle) - 개방-폐쇄 원칙
기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야함.
클래스를 변경하지 않고도(Closed) 대상 클래스의 환경을 변경할 수 있는(Open) 설계가 되어야함.
public class TimeReminder{
private MP3 m;
public void reminder(){
Calendar cal = Calendar.getInstance();
m = new MP3();
int hour = cal.get(Calendar.HOUR_OF_DAY);
if (hour >= 22){
m.playSong()
}
}
}
이 코드를 제대로 동작하는지 테스트하려면 22시까지 기다려야한다. OCP를 적용하여 아래와같이 설계한다면 TimeReminder 클래스를 전혀 수정하지않고 시간을 설정해 테스트할 수 있다.
public interface TimeProvider { // interface 도입
public void setHours(int hours);
public int getTime();
}
public class FakeTimeProvider implements Timeprovider{ // Timeprovider test stub
private Calendar cal;
public FakeTimeProvider(){
cal = Calendar.getInstance();
}
public FakeTimeProvider(int hours){
cal = Calendar.getInstance();
setHours(hours)
}
public void setHours(int hours){
cal.set(Calendar.HOUR_OF_DAY, hours); // 주어진시간으로 설정
}
public int getTime(){
return cal.get(Calendar.HOUR_OF_DAY); // 현재 시간 반환
}
}
public class TimeReminder{
TimeProvider tProv;
private MP3 m;
public void setTimeProvider(TimeProvider tProv){
this.tProv = tProv; // test stub 혹은 실제 시간 제공 instance 주입
}
public void reminder(){
int hours = tProv.getTime();
if (hours >= 22){
m.playSong();
}
}
}
public static void main(String[] args){
sut = new TimeReminder();
tProvStub = new FakeTimeProvider();
tProvStub.setHours(18);
sut.setTimeProvider(tProvStub);
}
LSP(Liskov Substitution Principle) - 리스코프 치환 원칙
자식클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 함.
부모클래스와 자식클래스 사이의 행위가 일관성이 있어야 한다는 의미.
슈퍼클래스의 메서드를 새로운기능으로 override하면 이 원칙에 위배됨.
public class Bag{
private int price;
public void setPrice(int price){
this.price = price;
}
public int getPrice(){
return price;
}
}
public class DiscountedBag extends Bags{
private double discountedRate = 0;
public void setDiscounted(double discountedRate){
this.discountedRate = discountedRate;
}
public void applyDiscount(int price){
super.setPrice(price - (int)(discountedRate * price));
}
}
DiscountedBag 클래스는 할인율을 설정해 할인된 가격을 계산하는 기능이 추가되었다.
기존의 Bag class에 있던 가격설정/조회기능은 변경없이 그대로 상속받았다.
Bag class의 메서드들이 DiscountedBag class에서 재정의되지않았으므로 가격설정/조회 기능을 그대로 이용할 수 있다.
따라서 두 클래스의 상속관계는 LSP를 위반하지 않는다.
DIP(Dependency Inversion Principle) - 의존 역전의 원칙
객체간 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운것, 거의 변화가 없는 것에 의존하라는 원칙이다.
public class Kid{
private Toy toy;
public void setToy(Toy toy){
this.toy = toy;
}
public void play(){
System.out.println(toy.toString());
}
}
이때 실제 가지고 노는 구체적인 장난감은 변하기 쉬운것이고,
아이가 장난감을 가지고 노는 사실은 변하기 어려운 것이다. 따라서 Toy를 Kid의 객체로 선언해주고, (Kid는 Toy에 의존)
외부에서 setToy로 객체를 주입해 장난감의 종류를 바꿀 수 있다.
public class Main{
public static void main(String[] args){
Toy t = new Robot();
Kid k = new Kid();
k.setToy(t);
k.play();
}
}
이와같이 DIP를 만족하면 의존성주입(DI:Dependency Injection) 기술로 변화를 쉽게 수용할 수 있는 코드를 작성할 수 있다.
ISP(Interface Segregation Principle) - 인터페이스 분리 원칙
클라이언트 자신이 이용하지 않는 기능에 영향을 받지 않도록 인터페이스를 분리시키는 설계 원칙이다.
이와같이 프린터, 팩스, 복사기 기능이 모두 포함된 복합기 클래스는 매우 비대하지만
이 비대한 클래스의 모든 기능을 client가 동시에 사용하지는 않는다.
이를테면 프린터 클라이언트는 print 기능만 사용한다.
따라서 아래와 같이 인터페이스를 client에 특화되도록 분리시켜야한다.
이제 clinet들은 자신이 사용하지 않는 메서드들에게 영향을 받지 않아도 된다.