ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [오브젝트 - 1 ] 객체 지향 프로그래밍
    개발/프로그래밍 2022. 5. 14. 16:42

    제대로 실행 되어야하고
    변경이 용이해야 하고
    이해하기 쉬워야 한다.

     

    class Theather {
    	private ticketSeller : TicketSeller
    	init(ticketSeller : TicketSeller){
    		self.ticketSeller = ticketSeller
    	}
    
    	func enter(Audience audience){
    		if(audience.getBag().hasInvitation()){
    			let ticket = ticketSeller.getTicketOffice().getTicket()
    			audience.getBag().setTicket(ticket)
    		}else{
    			let ticket = ticketSeller.getTicketOffice().getTicket()
    			audience.getBag().minusAmount(ticket.getFee())
    			ticket.getTicketOffice().plusAmount(ticket.getFee())
    			audience.getBag().setTicket(ticket)
    	}
    }
    

     

    위 코드의 문제점은 세가지가있다.

     

    첫번째로 audience와 ticketSeller가 수동적인 존재라는것이다.

    실제로 관객은 직접 돈을 내고 티켓을 사는것이지 판매원이 관객의 가방에서 꺼내가는것이 아니다.

    이런경우는 상식과 다르며 코드를 읽는사람과 의사소통이 힘들다.

     

    두번째로 전체 세부 내용을 모두 기억하고 있어야한다는 점이다.

    audience가 bag을 가지고있고 bag에 돈과 티켓이 있다는점을 알고있어야

    해당 코드가 이해가 간다.

     

    세번쨰로 변경에 취약하다

    만약 audience가 bag을 가지고있지 않다면? 현금이 아닌 카드를 쓴다면?

    audience 의 bag을 제거하고 enter() 메소드 역시 변경 되어야 한다.

    이것은 객체 사이의 의존성과 관련된 문제다.

    Theather는 다른 객체에 많이 의존하고있다.

    의존성은 “변경에 대한 영향"을 암시한다.

    객체가 변경될때 그 객체의 의존하는 다른 객체도 변경될수 있다는 사실이다.

    의존성을 없애는게 아니라 (객체지향 설계란 객체들끼리 의존 협력하는 공동체 구축이다)

    최소한의 의존성을 유지 하는것이 목적이다.

     

     

     

    Theather가 알필요 없는 audience 와 ticketSeller의 세세한 부분은 모르면 된다.

    audience 와 ticketSeller 가 자율적인 존재로 만들어 지면 된다.

    1. ticketSeller 가 직접 티켓을 판다.
    class TicketSeller{
    	private office: Office
    	init(office:Office){
    		self.office = office
    	}
    	func sell(audience : Audience){
    		let ticket = office.getTicket 
    		audience.getBag().minusAmout(ticket.getFee())
    		office.plusAmount(ticket.getFee())
    		audience.getBag().setTicket(ticket)
    	}
    }
    

    여기서 office 는 private으로 캡슐화 되었다. 외부에서 접근이 불가하고 객체의 내부 사항을 감춘다.

    이는 결합도를 낮추며 변경하기 쉬운 객체를 만드는데 좋다

    class Theather {
    	private ticketSeller : TicketSeller
    	init(ticketSeller : TicketSeller){
    		self.ticketSeller = ticketSeller
    	}
    
    	func enter(Audience audience){
    		ticketSeller.sell(audience)	
    	}
    }
    

    아지만 여전히 audience 가방에 seller가 접근하고있으므로 여기서 의존성을 낮출수있다

     

    2. audience가 티켓을 산다

    class Audience{
    	private bag : Bag
    	init(bag:Bag){
    		self.bag = bag
    	}
    	func buy(ticket:Ticket){
    		bag.setTicket(ticket)
    		bag.minusAmount(ticekt.getFee())
    		return ticket.getFee()
    	}
    }
    
    class TicketSeller{
    	private office: Office
    	init(office:Office){
    		self.office = office
    	}
    	func sell(audience : Audience){
    		let ticket = office.getTicket 
    		let price = audience.buy((ticket)
    		office.plusAmount(price)
    	}
    }
    

    audience의 의존성을 개선했으며 캡슐화하였다.

    객체 지향 프로그래밍을 위해서는 위와 같이 “책임의 이동”이 필요하다

    Theather의 많은 책임들을 audience 와 seller가 가져갔다.

    각 객체는 자신을 스스로 책임져야한다.

     

     

    자율적 객체

    캡슐화 , 접근제어 를 위해서는 객체를 두 부분으로 나눈다

    Public interface - Implementation

    객체의 “상태” 를 숨기고(private) “행동”을 외부에 공개(public) 해야 한다.

    class Money {
    	init(amount: BigDecimal){
    		self.amount = amount
    	}
    	public final amount : BigDecimal
    }
    
    

    금액을 구현하기위해 Int 나 Double 타입을 사용할수도 있겠지만

    Money 객체를 구현하여 의미를 명시적이고 분명하게 표현 할수있다.

    비록 하나의 인스턴스 변수만 가지고있더라도 이렇게하면 전체적 설계의 명확성과 유연성을 높일수있다.

    추상화

    protocol DiscountCondition {
        func isSatisfiedBy(screening:Screening) -> Bool
    }
    
    class DiscountPolicy{
        private let conditions : [DiscountCondition]
    
        init(conditions : [DiscountCondition]) {
            self.conditions = conditions
        }
        
        func calculateDiscountAmount(screening:Screening){
            for condition in conditions {
                if condition.isSatisfiedBy(screening: screening) {
                    return getDiscountAmount(screening:screening)
                }
            }
        }
        
        func getDiscountAmount(screening:Screening){
           //need to be override
        }
    }
    
    class AmountDiscountPolicy : DiscountPolicy {
    	override func getDiscountAmount(screening:Screening){
           //need to be override
        }
    }
    
    class PercentDiscountPolicy : DiscountPolicy {
    	override func getDiscountAmount(screening:Screening){
           //need to be override
        }
    }
    

    DiscountPolicy가 만약 여러정책이 있다면 추상클래스를 사용하는것이 좋다. 그럼으로 자식 클래스에서 의 중복코드를 방지할수있다.

    condition.isSatisfiedBy() 을 통해 할인 여부를 알아내지만

    실제 할인 금액에 관해서는 구현 클래스(자식클래스)에서 계산한다.

    그러기위해 해당 메소드를 overrice해야한다

    이렇게 부모클래스에 기본적인 알고리즘을 넣고

    중간에 필요한 처리를 자식클래스에 위임하는것을 Templete Method패턴이라 한다.

    class Movie{
    	let discountPolicy : DiscountPolicy
    	func getMovieFee(){
    		discountPolicy.getDiscountAmount()
    	}
    }
    

    나중에 Movie 객체가 discountPolicy에 접근할때

    컴파일 타임에서 Movie 클래스는 AmountDiscountPolicy 나 PercentDiscountPolicy 클래스에 의존하는것이 아니라

    추상클래스인 DiscountPolicy에 의존한다.

     

    하지만 런타임에는 AmountDiscountPolicy 나 PercentDiscountPolicy 클래스에 의존하게 된다.

    (Movie객체 생성시 주입 )

    이렇게 클래스와 객체가 각각 의존성이 달라질수 있다.

     

    Movie객체가 어떤 구현클래스를 의존하는지 객체 생성 코드를 봐야 확인할수있다.

    의존성이 달라지면 코드를 이해하기 어려워지는 단점이 있고

    유연해지고 확장가능해지는 장점이있다.

    항상 코드를 짤때 유연한 코드를 짤지 가독성이 좋은 코드를 짤지 잘 고려해야한다.

    이 둘은 배타적이다.

     

    추상화를 사용하면 요구 사항 정책을 높은 수준에서 서술할수있다.

    세부적인 내요을 무시한채 상위정책을 간단히 표현할수있다.

     

    Movie -> DiscountPolicy -> DiscountCondition
    

    위와같이 단순하게 높은수준 에서 설계를 가능하게 해준다.

    할인 정책에는 뭐가있고 할인 조건에는 또 뭐가있고 등등

    복잡한 하위 구체적인 객체들이 무시될수있다. 

     

    또 유연한 설계가 가능하다

    예로 새로운 DiscountPolicy를 만들기 위해서 하나의 클래스만 만들어주면 된다

    class NoneDiscountPolicy : DiscountPolicy {
    		override func getDiscountAmount(screening:Screening){
    	      return 0
        }
    }
    

    앞으로 추가될 미지의 새로운 할인정책도 수용할수있는 유연한 설계가 가능해진다.

     

    역할

    Movie 객체가 “할인요금을 계산해줘" 라는 메시지를 받을

    대상을 찾고있다고 가정해보자. 역할이라는것은 이때 필요하다.

    할인정책에 관련한 객체가 여러개라면 그 객체들이 모두 메시지를 처리할 로직이 필요할것이다. 그러면 코드가 중복된다.

    할인 요금을 계산해주는 역할 이라는것을 생각한다면 그 모든 (할인정책)객체들을 하나로 통합해 줄수있다. 이때 추상화를 사용한다.

    역할이란 배우와 유사하다. 배우는 다양한 배역을 수행할수있으며

    같은 배역을 다른배우들도 할수있다.

    이와같이 객체는 다양한 역할을 수행할수 있고 같은 역할을 다른 객체들도 수행할 수있다.

    댓글

Designed by Tistory.