개발관련 도서/객체지향과 디자인 패턴

2-1 설계원칙/ DI와 서비스 로케이터

prden 2021. 6. 3. 09:23

설계 원칙 : SOLID

1. 단일 책임원칙:

1) 단일 책임 원칙 (클래스는 단 한 개의 책임을 가져야 한다.)

 : 다른 말로 클래스를 변경하는 이유는 단 한 개여야 한다. 그러나 하나의 책임의 개념이 명확하지 않고, 하나의 책임을 도출하려면 많은 경험이 필요하기 때문에 어려운 원칙이다. 

 

2) 단일 책임원칙 위반 시 불러오는 문제점

 :  예를 들어 데이터를 읽는 책임과 데이터를 화면에 출력하는 책임 2가지를 동시에 하나의 클래스에서 관리하면, 책임의 개수가 많아질수록 책임의 기능 변화가 다른 책임에 주는 영향이 비례해서 증가해서 결국 코드를 절차 지향적으로 만든다. 이는 유지보수, 재사용의 어려움을 야기한다. 

3) 책임이란 변화에 대한 것

 : 단일 책임 원칙을 잘 지킬 수 있는 방법은 메서드를 실행하는 주체가 누구인지 확인해보는 것이다. 메서드의 사용자들이 서로 각각 다른 주체이면 그 메서드들은 각각 다른 책임에 속할 가능성이 높다. 

 

2. 개방 폐쇄 원칙 : 

1) 기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정하지 않는다.

(확장에 열려있어야 하고, 변경에는 닫혀있어야 한다. )

-> 추상화, 상속(오버 라이딩)을 통해 구현 

 

2) 개방 폐쇄 원칙을 어기는 코드의 전형적인 특징 : 

 a. 다운 캐스팅을 한다. :

 특정 타입인 경우에 별도의 처리를 하도록 메서드를 구현하면 그 메서드는 특정 클래스가 확장될 때 함께 수정되기 때문에 변경에 닫혀있지 않게 된다. 예를 들어 instnace of와 같은 타입 확인 연산자가 사용될 경우가 있을 수 있다.

 

 b. 비슷한 if-else블록이 존재할 경우 :

-> a, b 두 경우 다 추상화를 통해서 클래스를 만들고 그것을 의존 주입해줌으로써 해결 가능하다. 

 

 3) 개방 폐쇄 원칙은 변경의 유연함과 관련된 원칙이다. 

 변화되는 부분을 추상화(interface)함으로써 사용자 입장에서 변화를 고정시키면 개방 폐쇄의 원칙을 지킬 수 있다.

 

3. 리스 코프 치환 원칙 : 다형성에 관한 원칙

1) 의미 : 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 작동해야 한다. 

 = 하위 타입은 상위 타입에서 정의한 명세를 벗어나지 않는 범위에서 구현해야 한다. 

ex) 대표적인 

public void someMethod(SuperClass sc) {
	sc.some Method();
    }
//에서 아래와 같이 하위 타입의 객체를 전달해도 someMethod()가 정상적으로 동작해야
someMethod(new SubClass());

2) 리스 코프 치환 원칙을 어기는 예

 a. 직사각형, 정사각형문제

 b. 상위 타입에서 지정한 리턴 값의 범위에 해당되지 않는 값을 리턴

 

3) 리스 코프 치환 원칙은 계약과 확장에 대한 것 :

보통 instanceof 연산자를 사용하는 경우 LSP 위반 발생하는 경우 종종 발생

 

public class Coupon{
	public int calculate DiscountAmount(Item item){
    	if (item instanceof Specialitem) // LSP 위반 발생
        	return 0;
            
        return item.getPrice() * discountRate;
    }
}

 하위 타입인 SpecialItem이 상위 타입인 Item을 완벽하게 대체하지 못하는 상황 발생

원인 : Item에 대한 추상화가 덜 되었기 때문이다. 

 

따라서 아래와 같이 수정해준다. 

public class item {
//변화되는 기능을 상위 타입에 추가
 	public boolean isDiscountAvailable(){
    	return true;
    }
    
    ...
    
public class Spectialitem extends item{
	//하위 타입에서 알맞게 오버라이딩
    @Override
    public boolean isDiscountAvailable() { 
    	return false;
    }
}

public class Coupon{
	public int calculateDiscountAmount(Item item){
    	if(! item.isDiscountAvailable())
        	return0;
        return item.getPrice() * discountRate;
        
     }
}

4. 인터페이스 분리 원칙

1) 의미 : 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다. 

 사용하지 않는 인터페이스 변경에 의해 발생하는 소스 재컴파일의 문제 발생(c++) + 용도에 맞게 인터페이스를 분리하는 것은 단일 책임 원칙과도 연결된다. 

 

2) 인터페이스 분리 원칙은 클라이언트를 기준으로 분리해야 한다. 분리하지 않을 경우 개별 인터페이스가 변경되며 전체 인터페이스 변화에 영향을 주고 그 전체 인터페이스의 변화로 인해 그에 의존해있던 다른 인터페이스들이 영향을 받게 된다. 

 

5. 의존 역전의 원칙(추상화)

1) 의미 : 고수준 모듈(상위 수준)은 저수준 모듈(상세)의 구현에 의존해서는 안 된다. 저 수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다. 

  •  고수준 모듈 : 데이터 읽기, 암호화, 데이터 쓰기라는 하위 기능으로 구성
  •  저수준 모듈 : 위의 하위 기능을 실제로 어떻게 구현할지에 대한 내용을 다룬다. 예를 들어 고수준 모듈에서 암호화라는 하위 기능을 AES 알고리즘이라는 저수준 모듈로 구현 

2)고 수준 모듈이 저수준 모듈에 의존할 경우 문제 예시

쿠폰을 적용해서 가격 할인을 받을 수 있게 하며, 쿠폰은 동시에 한 개만 적용 가능할 때 (상위 수준) 쿠폰은 상황에 따라 다양한 종류가 될 수 있다(하위 수준) 만약, 쿠폰에 따라 가격 계산 모듈이 변경되면 의존 역전 원칙이 깨지는 것이다. 

 

3) 의존 역전 원칙을 통한 변경의 유연함 확보 = 추상화

4) 의존 역전의 원칙은 소스코드 상에서 의존이 역전된다는 의미이지, 런타임에서의 의존을 의미하는 것이 아니다. 

 

src p133, 개발자가 반드시 정복해야 할 객체지향과 디자인 패턴

6. 정리

객체지향 설계의 궁극적인 목적은 '변화에 유연하게 대응'이다.

단일 책임 원칙과 인터페이스 분리 원칙은 객체가 커지지 않도록 막아준다. 

리스코프 치환 원칙(다형성)과 의존 역전 원칙은(추상화) 개방 폐쇄 원칙을 지원한다. 개방 폐쇄 원칙은 변화되는 부분을 추상화하고 다형성을 이용함으로써 기능 확장을 하면서 기존 코드를 수정하지 않도록 도와준다. 

마지막으로 SOLID원칙은 사용자 입장에서의 기능 사용을 중시한다. 

※개념적으로만 정리하는 것은 아무 의미 없다.  코드 작성하면서 적용해보는 연습 계속 하자