객체 지향 프로그래밍 입문 수강 정리
2025, Mar 09
oop java
강의정보
- 인프런 강의 주소
- 강사 : 최범균님
- 총 2시간 32분
- 선수지식 : 자바 기본
- 난이도 : 초급
- 수강료 : 유료
비용
- 비용발생 주요 원인
- 코드 분석 시간 증가 → 코드 변경 시간 증가
- 소프트웨어의 가치는 = 변화
- 변화하는 세상속에서 가치있게 사용성있게 만드는 것 . “keep being useful in a changing world” (by jessica kerr)
- 비용과 변화
- 낮은 비용으로 변화할 수 있어야 함
- 방법
- 패러다임 : 객체지향, 함수형, 리액티브..
- 코드, 설계, 아키텍처 : dry, tdd, solid, ddd, 클린아키텍처, msa…
- 업무 프로세스/ 문화 : 애자일, devops
- 방법
- 낮은 비용으로 변화할 수 있어야 함
객체
- 객체 지향 vs 절차 지향
- 절차 지향 : 데이터를 여러 프로시저가 공유하는 방식
- 단점: 시간이 흐를수록 수정이 어렵다.
- 객체 지향 : 데이터와 프로시저를 객체 단위로 묶고, 다른 객체는 다른 객체를 바로 조회할 수 없다.
- 객체는 프로시저 호출을 통해서 데이터에 접근하게 된다.
- 절차 지향 : 데이터를 여러 프로시저가 공유하는 방식
- 객체의 핵심 → 기능으로 정의된다. 어떤 기능을 제공하는가.
- 내부적으로 가진 필드(데이터)로 정의하지 않는다. 기능으로 정의한다.
- 예를 들어서, 회원 객체는 암호 변경하기 기능으로 정의한다.
- 내부적으로 가진 필드(데이터)로 정의하지 않는다. 기능으로 정의한다.
- 객체와 객체
- 객체끼리는 기능을 사용해서 연결한다. (기능 사용 = 메서드 호출)
- 메시지
- 객체끼리의 상호 작용 = 메시지를 주고받는다 와 같다.
- 즉, 메서드를 호출하는 메시지, 리턴하는 메시지, 익셉션 메시지 등등을 포함해서 표현하는 것
- 객체끼리의 상호 작용 = 메시지를 주고받는다 와 같다.
- 일반 엔티티+ vo 객체는 = 데이터 개념의 클래스로만 보고, 객체라고 하지 않는다.
- 메서드를 이용해서 기능을 명세한다
- 이름, 파라미터, 결과로 구성하는 것
캡슐화
- 정의
- 데이터와 관련 기능을 묶는 것을 말한다
- 객체가 기능을 어떻게 구현했는지, 데이터의 상세 내용을 외부에 감추는 것
- 정보 은닉 의미 포함
- 캡슐화를 통해 기능을 사용하는 코드에 영향을 주지 않고 (또는 최소화) 내부 구현을 변경할 수 있는 유연함을 갖게 한다.
- 캡슐화하는 이유는?
- 외부에 영향 없이 객체 내부 구현 변경을 가능하게 하기 위해서
- 캡슐화된 코드만 봐도 기능에 대한 (의도) 이해를 높일 수 있게 된다.
- 캡슐화하지 않으면
- 반복되는 비지니스 코드가 여러곳에 산재되어 있게 되고, 변경되는 요구사항을 따라갈 수 없다.
- 요구사항의 변화가 데이터 구조/사용에 변화를 발생시킨다.
- 캡슐화한다는 것은?
- 기능을 제공하고 구현 상세를 안쪽으로 감춘다는 것
- 캡슐화된 객체
public class Account { private Membership membership; private Date expDate; public boolean hasRegularPermission() { return memberhsip == REGULAR && expDate.isAfter(now()) } }
- 캡슐화 규칙
- Tell, Don’s Ask 데이터 달라하지 말고 해달라고 하기
- acc.getMembership() == REGULAR (x) → acc.hasRegularPermission()
- Demeter’s Law
- 메서드에서 생성한 객체의 메서드만 호출하는 것
- 파라미터로 받은 객체의 메서드만 호출하는 것
- 필드로 참조하는 객체의 메서드만 호출하는 것
ex1) acc.getExpDate().isAfer(now) (x) -> acc.isExpired() ex2) Date date = acc.getExpDate(); data.isAfter(now); --> acc.isValid(now);
- Tell, Don’s Ask 데이터 달라하지 말고 해달라고 하기
다형성과 추상화
- 다형성
- 여러 모습을 갖는것
- 객제 지향에서는 한 객체가 여러 타입을 갖는 것
- 즉, 한 객체가 여러 타입의 기능을 제공
- 타입 상속으로 다형성 구현
- 하위 타입은 상위 타입도 됨
- 추상화
- 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미있는 표현으로 정의하는 과정
- 방법
- 두 가지 방식의 추상화
- 특정한 성질 또는 공통 성질 뽑아서 일반화
- 추상 타입을 이용한 프로그래밍을 한다.
- 추상타입은 구현을 감춤으로써 기능의 구현이 아닌 의도를 더 잘 드러나게 해준다.
- 서로 다른 구현을 추상화하는 것
- 타입 추상화
- 여러 구현 클래스를 대표하는 상위 타입 도출
- 흔히 인터페이스 타입으로 추상화
- 인터페이스는 기능에 대한 의미를 제공할 뿐, 구현은 하지 않는다. (어떻게 구현할지 알수 없다)
- 추상화 타입과 구현은 타입 상속으로 연결
- 흔히 인터페이스 타입으로 추상화
- 여러 구현 클래스를 대표하는 상위 타입 도출
- 장점
- 유연해진다.
- 단점
- 아직 존재하지 않는 기능에 대한 이른 추상화는 주의한다. → 잘못된 추상화 가능성 및 복잡도만 증가하므로 변경,확장이 발생할때 추상화를 시도한다.
- 추상 타입이 증가하면 그만큼 복잡도도 증가한다.
- 추상화가 필요한 시점은?
- 추상화는 의존 대상이 변경하는 시점에 한다.
- 추상화를 잘 하려면 “구현을 한 이유가 무엇 때문인지 생각해야 한다”
- opc를 따르는 구로조 구현하도록 한다.
- 수정엔 닫혀있고확장엔 열려있도록
상속보단 조립
- 상속 단점
- 상위 클래스 변경 어려움
- 상위 클래스를 변경하면, 변경의 여파가 계층을 따라 하위 클래스에 영향이 전파됨
- 앞으로 어떤 하위 클래스가 추가될지 알수 없으므로, 하위클래스가 증가할 수록 상위 클래스를 변경하기 어려움 → 즉 캡슐화가 약해진다.
- 클래스 증가
- 상속받은 하위 클래스들끼리의 조합이 필요한 요구사항이 생길때, 또 다른 하위클래스가 양산되게 되므로 애매한 상황 발생
- 상속 오용
- 상속 오용을 막기 위해 조립을 사용한다.
- 오용 예
- Container가 ArrayList의 진짜 하위타입이 아닌데, 잘못사용함으로써 put 메서드를 오용하게 된다.
public class Container extends ArrayList<Luggage> { private int maxSize; private int currentSize; .. public void put(Luggage lug) { if (!canContain(lug)) throw new NotEnoughSpaceException(); super.add(lug); currentSize += lug.size(); } }
- 상위 클래스 변경 어려움
- 조립
- 여러 객체를 묶어서 더 복잡한 기능을 제공할때 사용한다. 상속보다는..조립을 통해 기능 재사용을 하도록 한다.
- 보통 필드로 다른 객체를 참조하는 방식으로 조립 또는 객체를 필요시점에 생성하거나 구하도록 한다.
- 상속하기에 앞서 조립으로 풀 수 없는지 검토하고 진짜 하위 타입인 경우에만 상속을 사용한다.
기능과 책임 분리
- 분리한 하위기능을 누가 제공할지 결정하는 것 = 객체 지향
- 기능은 곧 책임
- 분리한 각 기능을 알맞게 분리
- 하지만 클래스나 메서드가 커지면, 절차 지향과 동일한 문제가 발생한다
- 큰 클래스 ⇒ 많은 필드를 많은 메서드가 공유
- 큰 메서드 ⇒ 많은 변수를 많은 코드가 공유
- 여러 기능이 한 클래스/메서드에 섞여있을 가능성
- 책임에 따라 알맞게 코드 분리 필요
- 방법
- 패턴 적용
- 전형적인 역할 분리
- 간단한 웹의 경우 : 컨트롤러, 서비스, dao
- 복잡한 도메인 : 엔티티, 밸류, 리포지토리, 도메인 서비스
- AOP : aspect(공통 기능)
- GoF : 팩토리, 빌더, 전략, 템플릿 메서드, 프록시/데코레이터 등
- 전형적인 역할 분리
- 계산 기능 분리
- 외부 연동 분리
- 조건별 분리는 추상화
- 패턴 적용
- 주의: 의도가 잘 드러나는 이름사용
- 장점
- 역할분리가 잘되면 테스트도 용이해진다.
- 방법
의존과 DI
- 의존이란?
- 기능 구현을 위해 다른 구성요소를 사용하는 갓 : 객체 생성이나 메서드 호출, 데이터 사용 등
- 의존은 변경이 전파될 가능성을 의미한다.
- 즉, 의존하는 대상이 바뀌면 자신도 바뀔 가능성이 높아진다.
- 호출하는 메서드의 파라미터가 변경되거나, 호출하는 메서드가 발생할 수 있는 익셉션 타입이 추가된다거나
- 즉, 의존하는 대상이 바뀌면 자신도 바뀔 가능성이 높아진다.
- 순환 의존
- 변경 연쇄 전파 가능성이 있다는 것으로
- 클래스, 패키지, 모듈 등 모든 수전에서 순환 의존이 없도록 해야한다.
- 의존하는 대상은 적을 수록 좋다.
- 대상이 많다는 것은? = 하나의 클래스에서 많은 기능을 제공하는 경우
- 각 기능마다 의존하는 대상이 다를 수 있다.
- 한 기능 변경이 다른 기능에 영향을 줄수 있다.
- 테스트하기도 어려워진다.
- 이 경우, 기능별로 분리를 고려해본다.
- 묶어보기 로 검토해본다
- 몇 가지 의존 대상을 단일 기능을 묶어서 추상화해서 제공하면, 의존 대상을 줄 일 수 있게 된다.
- 의존 대상 객체를 직접 생성한다면?
- 직접 생성할 경우, 생성 클래스가 바뀌면 의존하는 코드도 바꿔야하므로.. (추상화의 단점)
- 의존 대상 객체를 직접 생성하지 않고 사용하는 방식을 사용해본다.
- 팩토리, 빌더
- DI
- 서비스 로케이터
- 대상이 많다는 것은? = 하나의 클래스에서 많은 기능을 제공하는 경우
- DI(의존성 주입)
- 외부에서 의존 객체를 주입한다는 것 = 생성자나 메서드를 이용해서 주입
- 조립기 사용(assembler) = 스프링 프레임워크 같은 것을 사용한다는 것
- 조립기가 객체 생성 및 의존 주입을 처리한다 (예 : 스프링 프레임워크)
- 장점
- 상위 타입을 사용할 경우, 의존대상이 바뀌면 조립기(설정)만 변경하면 된다.
- 의존하는 객체를 실체 구현안해도 대역 객체를 사용해서 테스트 가능
- 의존 객체를 주입받아서 장점을 누리도록 코딩하는 습관을 갖자.
DIP
- 고수준 모듈, 저수준 모듈
- 고수준 모듈
- 의미있는 단일 기능을 제공
- 상위 수준의 정책 구현
- 저수준 모듈
- 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현
- 예시:
- 고수준: 도면 이미지를 저장하고, 측정 정보를 저장하고, 도면 수정 의뢰를 한다
- 저수준: NAS에 이미지를 저장하고, MEAS_INFO 테이블에 저장하고, BP_MOD_REQ 테이블에 저장한다.
- 고수준 모듈
- 고수준이 저수준에 직접 의존하면?
- 그렇게 되면, 저수준 모듈이 변경되었을때 고수준 모듈에도 영향이 미치게 되고 코드 변경이 필요하게 된다.
- 위의 상황을 방지하기 위한 방법 ⇒ 의존성 역전의 원칙
- 고수준 모듈은 저수준 모듈의 구현에 의존하면 안된다
- 저수준 모듈이 고수준 모듈에서 정의한 추상타입에 의존해야 한다.
- 추상화는 고수준 관점에서 해야한다.
- 고수준 입장에서 저수준 모듈을 추상화해야하며, 구현 입장에서 추상화하지 말아야한다.
- dip 장점
- 고수준 모듈의 변경을 최소화할 수 있고
- 저수준 모듈의 변경 유연성을 높여준다.
- 부단한 추상화 노력 필요
- 처음부터 좋은 설계가 나오지 않음 → 요구사항/업무 이해가 높아지면서 저수준 모듈을 인지하고 상위 수준 관점에서 저수준 모듈에 대한 추상화를 시도해야한다.
수강 후기
- 실습은 없지만, 현장에서 고민하는 것을 예시로 들면서 설명을 들을 수 있어서, 기억에 많이 남게 된다!!
- 다시 한번 기억을 더듬으면서 복습하는 시간을 가질 수 있었다.
- 내년에 또 복습해야겠다.