JAVA/Architecture

객체지향 설계의 5가지 원칙 SOLID

호두밥 2022. 1. 9. 20:36

SRP (Single Responsibility principle) 단일 책임 원칙

  • 하나의 클래스는 하나의 책임만 가진다.
  • 클래스의 책임을 완전히 캡슐화한다.
    public class Movie {
        private String title;
        private LocalDateTime runningTime;
        private int fee;
    }
    영화 클래스의 역할은 영화에 대한 정보를 관리하는 것이지, 요금을 계산하는 것이 아니다. 위의 예시처럼 영화(Movie) 클래스에서 fee(요금)을 직접 관리하도록 하면 안된다. 요금을 직접 관리하게 하면, 요금 계산 정책이 변경될 때마다 영화 구현체의 요금을 하나씩 변경해주어야 하는 일이 발생할 수 있다.

OCP (Open/Closed Principle) 개방 폐쇄 원칙

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
public calss DiscountPolicy {
    private final int fixDiscountPrice = 1000;
    public int getDiscountPrice( int fee ){
        return fixDiscountPrice;
    }
}
    • 기존 요금 할인 정책 (DiscountPolicy)가 1000원 고정금액을 할인해주는 방식이었는데, 비율(%) 할인 정책이 추가된다고 하면, 할인 정책 클래스이 소스를 수정해야 한다. (메소드 추가) 이를 피하기 위해 아래와 같이 할인정책 인터페이스를 만들고, 고정 할인 정책 구현체를 설계한다.
public class FixDiscountPolicy implements DiscountPolicy {
	private final int fixDiscountPrice = 1000;
	public int getDiscountPrice( int fee ){
		return fixDiscountPrice;
	}
}
public interface DiscountPolicy { 
	public int getDiscountPrice(int fee); 
}

 

  • 여기서 할인정책의 구현체인 비율 할인 정책 클래스를 만들어, 로직을 구현한다. 그러면 기존 할인정책(DiscountPolicy)과 고정 할인 정책( FixDiscountPolicy) 클래스를 수정하지 않고도 비율 할인 정책(RateDiscountPolicy) 기능을 추가할 수 있다.
public class RateDiscountPolicy implements DiscountPolicy {
	private final int discountRate = 10;
	public int getDiscountPrice( int fee ){
    	return fee*discountRate/100; 
	} 
}

LSP (Liscov Substitution Principle) 리스코프 치환 원칙

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 자동차에서 accerlate(악셀밟기) 메소드는 앞으로 가야하는데, 하위 타입 클래스에서 뒤(back)로 가서는 안된다. (프로그램의 정확성이 깨짐)

ISP (Interface Segregation Principle) 인터페이스 분리 원칙

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 인터페이스를 세분화할수록 기능이 명확해지고, 대체 가능성이 높아진다.
  • 특정 클라이언트가 사용하지 않는 기능으로 영향을 받아서는 안된다는 원칙이다. 인터페이스를 잘게 쪼개어 (역할 인터페이스) 클라이언트가 자신에게 필요한 인터페이스만 사용할 수 있도록 해야 한다.
public interface 청소 {
	void 쓸기();
	void 닦기();
	void 먼지털기();
}
  • '걸레' 라는 '청소' 인터페이스 구현 클래스를 만드려고 한다. 그러나 '걸레'는 '닦기' 기능만 갖고 있다. 또 빗자루라는 구현 클래스는 '닦기' 기능을 갖고 있지 않다. 이런 경우 인터페이스를 세분화하여, 각 특성이 맞는 인터페이스들을 구현하도록 해야 한다. (범용 청소 인터페이스를 그대로 사용하게 되면 '걸레' 클래스에 먼지털이 메소드를 호출하게 될 수 있다.)
public interface 쓸기 {
	void 쓸기();	
}

public interface 닦기 {
	void 닦기();	
}

public interface 먼지털기 {
	void 먼지털기();	
}
public class 걸레 implements 닦기 {
	public void 닦기(){
    	//////////////
	}
}

public class 빗자루 implements 쓸기, 먼지털기 {
	public void 쓸기(){
    	//////////////
	}
	public void 먼지털기(){
    	//////////////
	}
}

DIP (Dependency Inversion Principle) 의존관계 역전 원칙

  • 상위 모듈이 하위 모듈에 의존해서는 안 된다. 모두 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.  
public class 컴퓨터(){
    private final 키보드 키보드;
    private final 모니터 모니터;
    
    //생성자 constructor
    public 컴퓨터(){
    	this.키보드 = new 기본키보드();
        this.모니터 = new 기본모니터();
    }
}
  • 처음 컴퓨터를 받았을때, 함께 온 기본(번들) 키보드와 모니터를 사용했었지만, 시간이 지난 후 저소음 적축 키보드와 27인치 와이드형 모니터로 교체했다. 이 상황을 위의 소스에 반영하기 위해서는 컴퓨터 내부의 소스를 변경해야 하는데, 이는 다른 코드에 영향을 줄 수 있다. 
  • public class 컴퓨터(){
        private final 키보드 키보드;
        private final 모니터 모니터;
        
        //생성자 constructor
        public 컴퓨터(){
        	this.키보드 = new 저소음_적축_키보드();
            this.모니터 = new 27인치_와이드_모니터();
        }
    }
     
  • 외부에서 구현체를 주입받아 사용할 수 있도록 코드를 변경하면, 다른 코드에 영향을 주지 않고, 변경할 수 있다.
  • public class 컴퓨터(){
        private final 키보드 키보드;
        private final 모니터 모니터;
        
        //생성자 constructor
        public 컴퓨터(키보드 keyborad, 모니터 moniter){
        	this.키보드 = keyboard;
            this.모니터 = moniter;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
    		컴퓨터 내컴퓨터 = new 컴퓨터 ( new 저소음_적축_키보드(), new 27인치_와이드_모니터());
        }
    }

참고자료