-
[오브젝트] 객체, 설계개발/프로그래밍 2023. 2. 12. 21:46
모듈이 가져야 하는 기능
모듈은 제대로 동작해야 하고, 변경 용이성을 가지며 이해하기 쉬워야 한다.
일반적으로 코드로 구현되는 모듈이 기능적으로 문제가 있다면 코드의 로직을 바꾸면 된다.
하지만 다른 두 가지 이유에 충족하지 않는다면 어떻게 해야할까?
public class Theater { private TicketSeller ticketSeller; public Theater(TicketSeller ticketSeller) { this.ticketSeller = ticketSeller; } public void enter(Audience audience) { if (audience.getBag().hasInvitation()) { Ticket ticket = ticketSeller.getTicketOffice().getTicket(); audience.getBag().setTicket(ticket); } else { Ticket ticket = ticketSeller.getTicketOffice().getTicket(); audience.getBag().minusAmount(ticket.getFee()); ticketSeller.getTicketOffice().plusAmount(ticket.getFee()); audience.getBag().setTicket(ticket); } } }
위의 예제와 코드로 확인해보자.
변경 용이성
위의 예제 코드를 보면
Theater
는Audience
와TicketSeller
에 의존적이다.Audience
하위 속성이나 메소드가 바뀐다면?TicketSeller
는 불변한다는 보장이 있나?여러 상황이 생길 수 있고 이에 따라 당연히 모든 설계가 바뀐다.
이런 객체 사이의 "의존성"이 변화에 영향을 미치면 다른 객체도 같이 변경되어야 하는 문제가 생긴다.
이렇게 의존성이 강한 경우 "결합도"가 높다고 표현한다.
모든 의존성을 없앨 수는 없다. 객체 지향적인 코드는 객체 그래프 사이에서 서로 의존적으로 동작한다.
따라서, 객체 사이의 의존성은 불필요한 부분만 제거하는 것이 좋다.
이해하기 쉬운 코드
요구사항이나 동작을 설명할 때, 우리는 주체와 객체를 명확하게 표현한다.
하지만 동작이 같은 결과를 불러오더라도 우리가 생각한 방식과 다르게 동작할 수 있다.
결제는 우리가 결제 수단을 가지고 결제 시스템에 요청할 때 이루어진다.
이는 명확하게 "우리"라는 주체가 존재한다.
하지만 이해하기 힘든 코드에서는 "결제 시스템이 우리의 결제 수단을 가져가 결제하고 그 결과를 우리에게 적용한다."
라는 동작 방식이 적용될 수 있다.
이런 형태는 정상적인 결과 자체는 우리의 예상과 같다.
하지만 그 방식에 있어서 "결제 시스템"이 우리의 결제 수단에 마음대로 접근한다는 문제가 있다.
또한 위의 코드에서는
Theater
클래스만 표현하였지만 다이어그램이나 내부 메소드를 확인하면이 동작을 위해서 더 많은 클래스가 필요한 것을 확인할 수 있다.
많은 클래스와 그 내부에서도 동작하는 메소드들은 하나의 동작을 위해서 존재한다.
이런 불필요하게 많은 코드들은 코드를 이해함에 있어서 마이너스 요소가 된다.
이해하기 쉬운 코드는 내부 로직 또한 우리의 생각대로 동작할 것이다.
어떻게 개선할까?
위의 두 가지 문제를 확인할 수 있다.
코드의 이해도가 떨어지지만 의존성이 강해 변경이 어려운 코드다.
맨 위의 다이어그램을 확인하면
Audience
는Bag
을 가진다는 사실이나TicketSeller
가TicketOffice
를 가진다는 사실을 상위 객체에서 알 필요는 없다.자신의 하위 객체에 대한 동작은 스스로 처리하게 한다면 의존도를 낮출 수 있다.
Theater
가Audience
나TicketSeller
의 하위 객체까지 접근할 수 없게 하고 하위 객체들이 동작을 처리하게 한다.public class Theater { private TicketSeller ticketSeller; public Theater(TicketSeller ticketSeller) { this.ticketSeller = ticketSeller; } /* 기존에 존재하던 접근 로직을 ticketSeller 객체 내부 메소드로 옮긴다. */ public void enter(Audience audience) { ticketSeller.sellTo(audience); } }
public class TicketSeller { private TicketOffice ticketOffice; public TicketSeller(TicketOffice ticketOffice) { this.ticketOffice = ticketOffice; } public void sellTo(Audience audience) { ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket())); } }
캡슐화를 통해서
Theater
에서는TicketOffice
에 접근할 수 없다.마찬가지로
Audience
와Bag
에 접근하는 로직을Audience
내부로 옮기고 가시성을 제거한다.이제 직접 기존의 동작을 처리할 수 있다.
의존성이 있는 객체에서는 의존성에서 노출된 인터페이스를 통해서만 동작을 수행할 수 있을 뿐이다.
절차적인 코드를 객체지향적인 코드로 바꾼 것이다.
절차적인 코드는 동작에 대해서는 명확할 수 있으나 다른 사항에서 직관적이지 못하고 이전 절차에 의존적이게 된다.
결과
동작 자체의 수행 결과는 변경 전, 후 모두 동일하다.
하지만 캡슐화를 통해서 의존도를 낮추고 이해도를 높일 수 있다.
의존도를 낮춘 결과로 추가적인 변경이 있을 때 변경이 필요한 객체에서만 수정하면 된다.
상세한 내부 로직은 감춘 상태로 직관적인 호출과 응답 만을 남기면 된다.
객체의 자율성을 높이면서 자신의 직접적인 동작과 관련 없는 동작을 해당 객체에 위임한다.
즉, 자신에게 필요한 직관적인 동작 만을 가지는 것이다.
이렇게 각각 객체 자신의 책임을 가지게 만들 수 있다.
이런 객체들은 "응집도"가 높다고 할 수 있다.
의존이 아닌 협력 관계로써 구현한다.
하지만 모든 변경이 항상 설계가 개선되었다고 할 수는 없다.
코드가 변경되었다는 것은 어디선가 변경이 적용되었고 그 영향으로 다른 곳에서 의존성이 추가될 수도 있다.
어떤 객체는 수동적일 수 밖에 없을 수도 있다.
이런 자율성과 결합도 사이에서 트레이드 오프를 고려해서 더 나은 방식을 찾아야 한다.
이렇게 수동적이고 의존도가 높은 코드를 각 객체의 책임으로 이동시키면서 우리는 객체지향으로 나아갈 수 있다.
참고
'개발 > 프로그래밍' 카테고리의 다른 글
[오브젝트] 객체지향 프로그래밍 (0) 2023.02.20