24. 객체 지향 설계

김민영·2023년 3월 7일
0

C# 기초 프로그래밍

목록 보기
18/18

객체지향 프로그래밍은 캡슐화, 추상화, 다형성, 상속의 특성으로 확장성이 높아 주류가 되었다. 하지만 이 특성들을 사용했음에도 잘못된 설계로 유지보수가 어려워질 수 있다.

객체지향 설계를 위한 테크닉 중 책임 주도 설계에 대해 알아보자!


✨ 책임 주도 설계

책임 주도 설계란 객체가 책임지고 있는 행동과 객체간 공유하고 있는 정보를 고려해 계약에 초점을 맞추는 설계

1. 협력

1) 객체 간의 요청과 응답
2) 소프트웨어의 기능은 작은 책임들로 분할되고, 책임은 적절한 역할을 가진 객체에 의해 수행됨
→ 적절한 객체에게 적절한 책임을 부여하는 것에서 객체 지향 설계가 시작

2. 객체

▶ 객체란?

: 상태(필드)와 행동(메소드)을 함께 지닌 실체

▶ 좋은 객체를 만드는 방법

1) 객체는 충분히 협력적이어야 함

  • 다른 객체의 요청을 잘 처리하고, 다른 객체에 적극적으로 도움을 요청해야 함
  • 전지전능한 객체(God Object)
    : 하나의 객체가 너무 많은 책임을 가지고 있는 경우로, 이는 높은 내부복잡도에 의해 유지보수가 어려워짐

2) 객체는 충분히 자율적이어야 함

  • 요청에 응답하는 방식은 객체 스스로 판단해야 함
  • 요청에 대한 응답 여부도 객체 스스로 결정해야 함
    ex. 발사체를 예시로 할 때, 발사체 자체의 메소드에서 발사 여부를 판단하는 확률을 계산해야하지 메소드를 호출하는 객체로부터 그 확률을 전달받아 사용하는 것은 좋지 못함
  • 객체의 내부는 객체 스스로 관리하고, 외부에서 간섭할 수 없도록 차단해야 함 (데이터 은닉)

▶ 객체의 역할

1) 여러 객체가 동일한 역할을 수행할 수 있음

2) 역할은 대체 가능성을 의미
: 추후 같은 역할을 수행하기 위해 필요한 객체가 달라지더라도 대체할 수 있음

3) 각 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있음
: 유저가 자신의 캐릭터에 공격이라는 요청을 보낼 때, 캐릭터에 따라 공격 방법을 다르게 구현할 수 있음 (다형성)

4) 하나의 객체가 동시에 여러 역할을 수행할 수 있음

3. 책임

1) 요청을 처리하기 위해 객체가 수행하는 행동

2) 객체가 '어떻게' 행동하는지가 아니라 '무엇을' 해야하는지 나타냄

3) 책임은 수행 방법을 제한할 정도로 구체적이어도, 협력의 의도를 명확하게 표현하지 못할 정도로 추상적이어도 안됨

  • AttackWithAxe()
    : 너무 구체적인 책임의 예시, 추후 공격 수단이 다양해질 경우 확장성이 떨어짐
  • Do()
    : 협력 의도가 명확하지 않은 예시
  • Attack(Weapon weapon)
    : 다른 무기에 대한 확장성도 좋으며, 설계 의도 또한 명확히 드러남

4. 메시지

▶ 메시지란?

1) 객체지향의 세계에서 요청을 전달하는 유일한 수단
2) 송신자 (Sender): 메시지를 전송하는 객체
3) 수신자 (Receiver): 메시지를 받는 객체
4) 메소드: 객체가 수신된 메시지를 처리하는 방법
→ 메시지를 수신한 객체는 런타임에 메소드를 선택할 수 있음 (동적 바인딩)
→ 객체지향에서 협력이란 메시지를 전송하는 객체와 메시지를 수신하는 객체 사이의 관계로 구성

▶ 고려할 점

1) 외부의 요청이 무엇인지 표현하는 메시지와 요청을 처리하기위한 메소드를 분리하는 것은 객체의 자율성을 높이는 핵심 메커니즘
→ 확장성은 다형성에서 나오는데, 추상 메소드 및 가상함수를 통해 다형성을 잘 활용해야 함

2) 메소드 호출을 위해 인스턴스에 접근할 때 인스턴스의 참조 방법을 고민하는 것 또한 객체지향으로 설계하는 핵심
→ 접근이 어렵다고 모두 static으로 선언해선 안됨

✨ 다형성

1) 서로 다른 객체가 다형성을 만족시킨다는 것은 객체들이 동일한 책임을 공유하는 것

  • 송신자 관점에서 요청을 처리하는 상대가 어떤 객체인지는 상관없이 동일한 책임을 수행하는 것
  • ex. 음료를 만드는 책임을 여러 바리스타가 수행하고, 손님 입장에선 바리스타가 누군지는 상관없이 주문한 음료를 받을 수만 있으면 됨

2) 다형성을 이용해 협력을 유연하게 만들 수 있어야 함

interface IShootable
{
	void Shoot();
}

abstract class Gun : IShootable
{
	
}

abstract class Monster : IShootable
{
	public void Shoot()
	{
		// Monster만의 쏠 수 있는 기능
	}
}

abstract class Character
{
	// 이러면 IShootable을 상속받는 무엇이든 올 수 있음 (좋은 예)
    // 결합도 낮음
	public abstract void Shoot(IShootable shootable);
}

abstract class Character
{
	// 이러면 결합도 높고 안좋은 예시
	public abstract void Shoot(Gun gun);
}

✨ SOLID 원칙

클린 아키텍처의 바탕이되는 5가지 원칙

1. 단일 책임 원칙 (SRP, Single Responsibility Principle)

1) 각 소프트웨어 모듈은 변경의 이유가 하나여야 함
2) 하나의 객체는 하나의 책임만 가져야 함

2. 개방-폐쇄 법칙 (OCP, Open-Closed Principle)

1) 객체는 확장에 열려있어야하고, 변경에 닫혀있어야 함
2) 기존 코드를 수정하기보다 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야 함
3) 캡슐화 관련 법칙

3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)

1) 프로그램 P에서 T타입의 객체 자리에 S 타입의 객체로 모두 치환하더라도 P의 동작이 변하지 않는다면 S는 T의 하위타입
2) is-a 관계를 만족하게 상속 계층도를 구성해야 함
3) 상속 관련 법칙

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

1) 어떤 타입이 다른 타입에 의존한다고 할 때, 필요한 인터페이스만을 분리해 의존하게 설계해야 함
2) 결합도를 메시지 수준으로 낮춰야 함

5. 의존성 역전 원칙 (DIP, Dependency Inversion Principle)

1) 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안되며, 세부사항이 정책에 의존해야 함
2) 협력관계에서 송신자는 수신자에게 의존한다고 표현하는데, 의존하는 대상이 항상 base 클래스여야 함

객체 지향적 설계를 위해 이 원칙들이 무엇인지 알기만 하는 것이 아니라, 추상 클래스와 인터페이스의 차이를 잘 이해하고 활용할 수 있어야 함


✨ 디자인 패턴

1) 이전에 해결한 문제에 대한 해결 방안을 정리한 것

2) 패턴을 적용했다고 무조건 좋은 코드가 아니라, 올바른 곳에 적절히 사용할 수 있어야 함

3) 디자인 패턴을 올바르게 적용하면 손쉽게 주도 설계의 결과를 표현할 수 있음

0개의 댓글