ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [오브젝트 - 3] 인터페이스 추상화
    개발/프로그래밍 2022. 6. 1. 17:11

    인터페이스

    최소한의 인터페이스와 추상적인 인터페이스를 만족하라

    꼭 필요한 오퍼레이션(다른객체에 제공하는 추상적인 서비스) 만 인터페이스에 넣어주고 어떻게가 아닌 무엇을 하는지(추상적) 표현한다.

    디미터 법칙

    “낯선자에게 말하지 말라", 객체 내부 구조에 강한 결합을 하지 않도록 협력 경로를 제한하는것이다.

    협력대상은 아래와같이 제한한다

    1. this
    2. 메소드 내 매개변수
    3. this 속성
    4. 메소드 내 생성된 객체
    

    메소드 명

    메소드의 이름은 어떻게가 아닌 무엇을 하는지 드러내게 지어야한다.

    무엇을 하는지는 내부 구현을 설명하는게 되어버린디.

    원칙

    무조건 원칙을 지키는것이 좋은건 아니다 설계는 트레이드 오프이다. 좋은 프로그래머는 적재적소에 원칙을 지킨다.

    객체에게 묻지말고 시키라한다 (내부구조 캡슐화) 그러나 가끔은 물어야 할때가 있다. 흔히 자료구조인 경우는 내부구조를 노출해야한다.

    명령 - 쿼리

    명령이란 부수효과를 낸다. 객체 상태를 수정하는경우을 의미하고

    쿼리란 객체 상태 정보를 반환하는것이다. 어떤 함수도 명령이면서 쿼리일 수는없다. 명령이거나 쿼리둘중 하나여야한다.

    func isSatisfied(schedule:Schedule) -> Bool{
    	if( dayOfWeek() != schedule.dayOfWeek() ) { 
    		reschedule(schedule)
    		return false 
    	}
    
    	return true
    }
    

    위와 같이 명령과 쿼리를 동시에 한다면 버그의 원인을 파악하기 힘들것이다.

    추상화와 분해

    인간은 단기기억을 이용해 문제를 해결한다. 그러나 단기기억에는 용량적 한계가 있다.

    단기적으로 기억을 할수있는데 5개라면 6개이상의 기억 요소가 필요하다면 인지적 과부하가 오게된다.

    특히 큰문제라면 큰 문제를 작은 문제로 분해하려는 경향이 있기때문에 더욱 그렇다.

    그럴때 그 작은 문제들을 볼때 본질적인 정보만 남기고 불필요한 세부사항을 거른다면

    기억할 양을 줄일수 있다. 이를 추상화라한다. 11자리 정수를 기억하는거보다 11자리 전화번호를 기억하는게 편할것이다. 이는 전화번호라는 추상화가 있기 때문이다.

    하향식 기능분해

    하향식 기능분해란 최 상위 기능을 정의하고 하위 기능으로 분해해 나가는 방법을 말한다.

    예로 급여를 계산하라는 최상위 기능을 세율을구하고 직원정보를 받아 급여를 계산하는 세분화된 기능으로분해되는것을 말한다.

    하지만 하향식 기능분해는 문제점을 가지고있다.

    예로 새로운 기능이 추가되거나 하면 메인함수(최상위)가 더이상 메인함수가 아닐경우가 많다. 실제 시스템에 정상이란 없다.

    예로 직원들 급여의 평균을 계산하는 기능을 추가 한다면?

    급여계산 기능은 최상위 함수가 아닐것이다.

    또한 비즈니스로직과 UI 가 결합되어 메인함수에 들어어간다.

    UI가 변경된다면 비즈니스로직도 영향을 받을것이다.

    만약 직원급여뿐만아닌 아르바이트 급여도 관리한다면 어떻게 될까 아르바이트는 월급이 아닌 시급으로 계산이 된다. 그렇게 새로운 데이터들이 추가될것이다. (아르바이튼지 직원인지, 시급이 얼만지, ㅁ몇시간 일했는지..)

    이 데이터들을 가지고 급여 계산 기능을 수정하여도 끝이아니다.

    급여 평균 계산 기능도 수정해야할것이다.

    만약 이것을 까먹고있다면 버그가 발생했을것이다.

    모듈

    시스템을 모듈로 분해한 후에 각 모듈 내부를 기능분해할수있다.

    해당 모듈을 추상화 할수있는 간단한 인터페이스를 제공하여 복잡성을 낮춰야 하고

    하나의 모듈만 수정하면 되도로 설계 결정은 모듈 내부로 감추고 잘 변경되지 않는 인터페이스를 외부에 제공해야 한다.

    위와같은 기능에서는 직원리스트, 급여, 직원타입, 일한시간등 정보들을 내부로 감추고

    급여계산, 평균급여 함수를 인터페이스로 제공해주면 될것이다.

    이렇게 모듈화를 하면 모듈 내부 데이터 변경에도 모듈에만 영향을 줄것이고 비즈니스로직과 UI가(예로 직원이름을 입력받는다 가정시) 분리된다.

    하지만 모듈은 인스턴스를 제공하지 않는다. 직원 하나를 독립적으로 다루어야 한다. 다수의 직원 인스턴스가 존재하는 추상화 메커니즘이 필요하다.

    추상데이터 타입

    직원의 급여를 계산한다. 라는 사고가 아니라 직원과 급여라는 추상화 개념을 사고하고 계산이라는 절차를 생각한다.

    struct Employee {
         var name: String
    	 var basePay: Int
    	var hourly: Bool
    	var timeCard: Int
    }

    Employee 라는 개념은 아까 Employees 모듈보다 좀더 일반적인 사고방식에 가깝다.

    추상 데이터는 말그대로 시스템의 상태 데이터를 표현한다. 이데이터를 가지고 기능을 구현하는것은 외부에 존재한다.

    클래스

    클래스와 추상 데이터타입의 차이는 상속과 다형성을 지원하는가이다.

    위의 Employee 는 내부의 정규직원과 아르바이트직원 두가지 타입이 공존한다. 구체적 직원 타입을 캡슐화하였다. 이를 타입추상화라 한다.

    정규직원고 알바를 두 클래스로 분리할경우 각각 로직들을 구현할것이다.

    그러나 공통로직의 경우 두클래스 모두 부모클래스를 상속받을것이다.

    클라이언트 입장에서는 같은 메시지를 전송하지만 실제 클래스에 따라 다른 로직을 탈것이다. 이게 다형성이다. 클라이언트 입장에서 두 인스턴스는 동일하게 보이고 내부 절차는 다른것이 절차 추상화이다.

    추상 데이터타입은 타입을 추상화하고

    클래스는 절차를 추상화 한것이다.

    타입추상화에서 급여를 계산하는 함수이다.

    func calculatePay(){
     if ( hourly) {
    	return caculateHourlyPay() 
    }else {
    	return calculateSalriedPay()
    }
    }
    

    객체지향을 사용하면 위와같은 조건문이 필요가 없다.

    클라이언트 입장에서 calculatePay() 를 요청해도 두 다른 클래스가 각자 다른 로직을 실행할 것이다. 새로운 클래스가 추가되어도 클라이언트는 영향 받지 않는다 새로운 조건문을 붙이지 않아도된다.

    이런 기존코드에 영향주지않고 새로운 객체를 추가할수있는 특성을 개방폐쇄 원칙이라한다.

    추상데이터타입이 무조건 나쁜것은 아니다. 만약 기능 추가 & 변경내역이

    오퍼레이션을 추가하는것이라면 (예로 직원의 근속년수를 구하는 작업)

    객체지향의 경우 클래스마다 모두 수정해야한다.

    변경축에 따라 더 올바른 해결방법을 찾아야한다.

    댓글

Designed by Tistory.