객체지향 프로그래밍이란, 프로그래밍에서 필요한 데이터를 추상화시켜 **상태와 행동**을 가진 객체를 만들고
그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다. (from. 객체지향의 사실과 오해)
이렇게 보니 알다가도 모르겠습니다.
먼저 객체에 대해서 알아볼까요?
객체를 가장 잘 이해하기 위해서는 객체를 상태(state)
, 행동(behavior)
, 식별자(identity)
를 지닌 실체로 보는 것이 중요합니다.
하나하나씩 살펴봅시다!
상태
상태는 특정 시점에 객체가 가지고 있는 정보의 집합으로 객체의 구조적 특징을 표현합니다.
텔레비전 전원을 켜지 않는 한 채널을 변경할 수 없습니다. 반대로, 전원이 켜져있는 텔레비전의 채널을 바꿨다면 원하는 채널로 잘 옮겨갔을 것입니다.
이것처럼 어떤 행동의 결과(즉, 현재의 상태)는 과거에 어떤 행동들이 일어났었느냐에 의존합니다. 상태를 이용하면 과거의 모든 행동 이력을 설명하지 않고도 행동의 결과를 쉽게 예측하고 설명할 수 있습니다. 이전에 텔레비전을 몇번을 껐다 켰던지 모르더라도 현재 상태만 알면 결과를 행동에 대한 결과를 예측할 수 있겠죠?
상태의 특징
행동
행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동입니다.
은행의 서비스를 객체들로 만든다고 생각해봅시다!
이렇게 객체간의 아무런 교류, 상호작용이 없다면 은행의 서비스는 돌아가지 않겠죠!
이처럼 프로그램들 간의 교류와 협력이 존재해야 프로그램이 운영됩니다. (객체 간의 협력이 있어야 상호작용이 발생) 객체가 다른 객체와 협력하는 유일한 방법은 “다른 객체에게 요청을 보내는 것”(= 행동)입니다. 요청을 수신한 객체는 요청을 처리하기 위해 적절한 방법에 따라 행동합니다. 객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법입니다.
식별자
객체는 식별 가능한 경계를 가지고 있습니다. 객체가 식별 가능하려면 객체를 서로 구별할 수 있는 특정한 프로퍼티(속성)이 객체 안에 존재해야 합니다.
객체를 서로 구별할 수 있는 프로퍼티를 식별자라고 합니다.
객체에 대한 어느 정도 설명이 되었습니다. 정리해봅시다!
- 객체는 상태를 가지며, 상태 중 일부(동적인 프로퍼티)는 변경 가능하다.
- 객체의 상태를 변경시키는 것은 객체의 행동이다.
- 행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다.
- 행동의 순서가 실행 결과에 영향을 미친다
- 객체는 어떤 상태에 있더라도 유일하게 식별 가능하다.
우리가 알고 있는 객체지향 언어의 특징을 다시 한번 되새겨 봅시다.
객체지향 프로그래밍에서는 위에서 설명한 객체의 상태와 내부 정보들을 캡슐화하고, 객체를 상속받아 상태나 행위(행동)의 다형성을 가지고, 객체의 행위를 추상화합니다. 이 네가지 키워드는 두번째 챕터에서 더 자세히 다룹니다!
다시 한번, 위에서 설명했던 객체지향 프로그래밍의 의미를 살펴봅시다.
객체지향 프로그래밍이란, 프로그래밍에서 필요한 데이터를 추상화시켜 **상태와 행동**를 가진 객체를 만들고
그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다.
이제 좀 갈피가 잡히는 것 같죠?
그런데, 우리가 꼭 집고 넘어가야하는 부분들이 있습니다.
클래스에 대한 오해
객체지향 프로그래밍 언어에서 정적인 모델은 클래스를 이용해 구현됩니다. 그래서 보편적으로 타입을 클래스를 이용해 구현합니다. 여기서 가장 크게 오해하는 점이 타입 == 클래스
라고 생각하는 부분입니다.
하지만, 클래스는 타입을 구현할 수 있는 여러 구현 매커니즘 중 하나일 뿐인 것을 잊지 말아주세요.
❗️ 실제로 js같은 프로토타입 기반의 언어에는 클래스가 존재하지 않습니다 ❗️
행동이 상태를 결정한다.
객체지향에서 가장 많이 실수하는 부분은 상태를 중심으로 객체를 바라보는 것입니다.
객체지향 설계는 필요한 협력을 생각하고 협력에 참여하는데 필요한 행동을 수행할 객체를 선택하는 방식으로 수행됩니다. 행동을 결정한 후, 행동에 필요한 정보가 무엇인지를 고려하게 되며 이 과정에서 필요한 상태가 결정됩니다.
객체의 행동을 결정하고 그 후에 행동에 적절한 상태를 선택해야 합니다!
이제 객체지향에 대해 조금 알았으니, 자바에서는 객체지향이 어떻게 쓰이고 있는지 알아보러 갑시다!
부제 - 객체지향의 4가지 키워드
abstract
추상화란, 객체들의 공통적인 요소들을 추출해 불필요한 부분들을 생략하고 객체의 속성 중 중요한 것에만 중점을 두어 추출하여 표현한 것을 말합니다.
그렇다면 왜 객체지향 프로그래밍에 추상화가 필요한가.
각 동물은 우는 방법이 다르지만, “우는 행동”을 한다는 것은 모두 동일합니다. 이러한 공통사항을 추상화하여 관리하면, 다음과 같이 동물들로 추상화할 수 있겠죠!
Java의 추상 클래스와 추상 메서드 구현
부제: 선언부는 있는데 구현부가 없는 추상 메서드
추상 클래스란, 선언되어 있지만 구현되어 있지 않은 미완성 메소드를 포함한 미완성 클래스입니다. 이 구현되어 있지 않은 미완성 메소드를 추상 메서드라 부릅니다.
abstract class Person { //추상 클래스
abstract public void walk(); // 추상 메서드 -> 어떻게 작동하는지(구현부)가 적혀있지 않습니다.
}
구체화된 클래스는 추상 클래스를 상속 받아 구현합니다.
public class Unan extends Person {
@Override
public void walk {
System.out.println("뚜벅");
}
}
Unan
자식 클래스가 Person
추상 클래스를 상속받았습니다.
💡 이 때 자식 클래스는 추상 클래스 내부에 들어있던 추상 메소드를 반드시 오버라이딩해야 합니다. @Override
라는 어노테이션을 붙여 메소드를 구현해줍니다.
추상 클래스의 특징을 정리해봅시다!
- 추상 클래스를 상속 받은 자식 클래스는 반드시 추상 메소드를 오버라이딩해야 함.
추상 클래스가 자식 클래스에 추상 메소드의 재정의를 강요함
- 추상 클래스는 프로그램의 설계를 목적으로 함
<-> 일반 클래스는 여러 개의 객체를 생성하고 데이터를 저장하는 것을 목적으로 함
- 추상 클래스는 객체로 생성할 수 없고, 자식 클래스에서 상속 받아 구현 클래스를 이용해 객체를 생성해야합니다.
private
캡슐화란, 객체 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말합니다.
캡슐화는 언제, 어떻게 사용하는가.
캡슐화는 두 가지 관점에서 사용합니다. 상태와 행위, 그리고 불안정한 비밀을 캡슐화합니다. 그러면 이 캡슐화를 어떤식으로 구현하는 걸까요?
객체는 상태와 행위를 한데 묶은 후 외부에서 반드시 접근해야만 하는 행위만 골라 공용 인터페이스를 통해 노출합니다.
캡슐화를 통해 변경이 빈번하게 일어나는 불안정한 비밀을 안정적인 인터페이스 뒤로 숨길 수 있습니다. 이러한 캡슐화를 이용하면 외부의 불필요한 공격과 간섭으로부터 내부 상태를 격리시킬 수 있죠!
이러한 캡슐화를 통해 객체는 공용 인터페이스를 수정하지 않는 한 협력하는 외부 객체에 영향을 미치지 않고 내부의 구현을 자유롭게 수정할 수 있게 됩니다.
💡 특정 언어에 종속되지 않은 의미로서 인터페이스란 두 사물의 경계지점에서 서로 상호작용할 수 있게 이어주는 방법이나 장치입니다! 그렇다면 공용 인터페이스란, 경계지점에서 서로 상호작용할 있는 공용 장치겠죠!
왜 객체지향 프로그래밍에 캡슐화가 필요한가.
객체의 행동을 유발하는 것은 외부로부터 전달된 요청사항이지만 객체의 상태를 변경할지 여부는 객체 스스로 결정합니다. 상태를 외부에 노출시키지 않고 행동을 경계로 캡슐화하는 것은 결과적으로 객체의 자율성을 높입니다.
자율적인 객체는 스스로 판단하고 스스로 결정하기 때문에 객체의 자율성이 높아질수록 협력에 참여하는 객체들의 지능이 높아질 수록 협력은 유연하고 간결해집니다.
결과적으로, 캡슐화는 설계를 단순하고 유연하게, 변경하기 쉽게 만들어줍니다!
Java의 접근 제어자
Java에서는 클래스를 그룹 단위로 묶어서 관리하는데요. 이 그룹을 패키지라고 부릅니다! 쉽게 생각하면 객체(클래스)가 있는 폴더라고 생각하시면 됩니다. Java에서의 접근 제어자는 패키지를 경계로 접근 가능 유무를 결정해줍니다.
클래스 접근 제어자
default
: 동일 패키지의 클래스(class)에만 인스턴스(객체)를 생성가능하다.
public
: 다른 패키지에서 인스턴스(객체)를 생성가능하다.
메소드 접근 제어자
private
: 동일한 클래스 안에서만 접근이 가능하고, 외부 패키지에서 접근 불가능하고, 상속도 안된다.
default
: 접근제어자가 없는 형태로 동일한 패키지 안에서만 접근이 가능하다.
protected
: 동일한 패키지 안에서 사용가능하고, 다른 패키지라도 상속받은 클래스에는 접근이 가능하다.
public
: 모든 객체에서 접근 가능하다.
서비스의 사용자인 Member
클래스를 만든다고 가정해봅시다! 우리는 사용자의 정보를 외부로부터 감춰야 할 의무가 있습니다. 아래와 같이 구현할 수 있겠죠!
public class Member{
private String name;
private String email;
private String password;
private int age;
private String sex;
private String description;
private Member(String name, String email, String password, int age, String sex, String description) {
this.name = name;
this.email = email;
this.password = password;
this.age = age;
this.sex = sex;
this.description = description;
}
public Member create(String name, String email, String password, int age, String sex, String description) {
return new Member(name, email, password, age, sex, description);
}
public String getName() {
return name;
}
}
사용자의 정보인 name
, email
, password
,age
, sex
, description
은 private
접근 제어자를 사용하여 외부로부터 감춥니다. 대신 Member
객체를 만들기 위해 create
라는 메소드를 public
접근 제어자를 사용하여 공용 인터페이스로 작동하게 합니다!
Overloading
, Overriding
다형성이란, 어떤 객체의 속성이나 기능이 그 맥락에 따라 다른 역할을 수행할 수 있는 객체지향의 특성을 의미합니다.
다형성은 어떻게 사용하는가.
다형성을 구현하는 대표적인 방법으로는 오버로딩과 오버라이딩이 있습니다.
오버로딩이란 동일한 이름의 메소드가 다른 반환 타입, 다른 매개변수의 개수나 타입을 가질 수 있는 것입니다. 대표적으로 print 함수를 예로 들 수 있습니다.
System.out.println("안녕하세요"); //매개변수가 문자열로 전달
System.out.println(1): //매개변수가 숫자로 전달
System.out.printlnt(true); //배개변수가 boolean 값으로 전달
이처럼 여러 종류의 타입을 넣었을 때 같은 기능(원하는 내용을 출력하는 기능)을 수행합니다. 메소드를 동적으로 조건을 변경하여 호출할 수 있으므로 다형성을 구현하는 방법 중 하나입니다.
오버로딩과 이름이 비슷하지만, 오버라이딩은 상위 객체의 메서드를 하위 객체에서 재정의하는 것을 말합니다. 앞서 추상클래스에서 설명했던 방식과 같이 구현부가 없는 메서드를 오버라이드 하면 자식 객체는 본인에 맞는 기능을 구현할 수 있습니다.
왜 객체지향 프로그래밍에 다형성이 필요한가.
- 다형성은 상속과 메서드의 재정의를 활용하여 확장성 있는 프로그램을 만들게 해줍니다.
- 다형성을 사용하지 않는다면 어떻게 될까요? 조건에 따라 달라지는 구현때문에
if-else
문이 반복적으로 구현됩니다. 이러한 무분별한 분기처리문은 코드의 유지보수가 어렵게 만듭니다.
ex) 예를 들어.. print 함수가 오버로딩을 지원하지 않는다면..?
if("넘어온 객체" = String) {
...
} else if("넘어온 객체" = int) {
...
} ... 이하 생략
이렇게 되겠죠! 이 코드… 어떻게 생각하세요? 너무 많은 분기 처리는 유지 보수에 좋지 않습니다!
Java의 오버로딩
Java에는 객체의 인스턴스를 생성하는 생성자가 존재합니다. 아까 보았던 Member
클래스를 이어서 보면,
private Member(String name, String email, String password, int age, String sex, String description) {
this.name = name;
this.email = email;
this.password = password;
this.age = age;
this.sex = sex;
this.description = description;
}
이렇게 생성자가 만들어져 있습니다. 그런데 만약 사용자가 본인의 description을 입력하지 않은 경우에는 어떻게 해야할까요? 오버로딩으로 해결할 수 있습니다. 아래처럼 매개변수가 달라도 객체를 생성하는 기능을 구현할 수 있습니다.
private Member(String name, String email, String password, int age, String sex, String description) {
this.name = name;
this.email = email;
this.password = password;
this.age = age;
this.sex = sex;
this.description = description;
}
private Member(String name, String email, String password, int age, String sex) {
this.name = name;
this.email = email;
this.password = password
this.age = age;
this.sex = sex;
}
public Member create(String name, String email, String password, int age, String sex, String description) {
return new Member(name, email, password, age, sex, description);
}
public Member create(String name, String email, String password, int age, String sex) {
return new Member(name, email, password, age, sex);
}
이 외에도 Java의 인터페이스, 추상 클래스 등으로 다형성을 구현할 수 있습니다!
extends
상속은, 상위 객체(부모 객체)의 멤버변수와 메서드를 하위 객체(자식 객체)가 물려받는 기술을 의미합니다. 위에서 살펴본 추상화, 다형성에서 상속에 대해 많이 살펴보았기 때문에 간단히 넘어가겠습니다.
왜 객체지향 프로그래밍에 상속이 필요한가.
부모 객체가 가진 필드와 메소드 그리고 타입을 물려주고 자식 객체는 새로 작성할 필요없이 이를 자신의 것처럼 사용합니다. 이러한 상속의 특징은 다음과 같은 필요성을 동반합니다.
이제 Java 얘기는 끝입니다..! 그런데! 추가적으로 공부하셨으면 하는 부분이 있는데요!
interface
- 추상 클래스와의 차이점, 객체지향적 설계에서의 interfacenew
instanceOf
static
…더 있지만.. 하지만! 그럼에도! 하우에버! 네버더레스! 강제는 아니고, 여러분이 앞으로 자바에 대해서 더 알아가고 싶으시다면 공부해보시는 걸 추천드리면서 넘어갑니다!
객체지향의 핵심은 다형성입니다.
역할은 구현되어 있지 않은 추상화된 인터페이스나 추상 메서드를 의미하며, 구현은 이러한 역할을 구현한 객체를 의미합니다.
객체지향 설계를 위해서는 구현 세부 사항보다 역할을 먼저 생각하고 구현해야합니다. 역할을 먼저 부여한 후 그 역할을 수행하는 구현 객체를 만드는 방법으로 역할과 구현을 분리할 수 있습니다.
역할과 구현을 분리하면 어떤 장점이 있을까요?
다형성의 본질은 무엇일까요?
정도로 생각할 수 있습니다.
다형성을 구현한 뿌리였던 역할(인터페이스)자체가 변하면 클라이언트, 서버 모두에 큰 변경이 발생합니다. 따라서 인터페이스를 안정적으로 설계하는 것이 중요합니다.
SOLID 원칙은 객체지향 설계에서 지켜야 할 5개의 개발 원칙입니다. 면접 단골 질문이기도 하지요!
어떤 클래스를 변경하는 이유는 오직 하나뿐이어야 한다 - 로버트 C. 마틴
로버트 마틴은 단일 책임 원칙에서 책임을 변경되는 이유로 정의하고, 어떤 클래스나 모듈은 변경하려는 단 하나 이유만을 가져야한다고 결론지었습니다.
단일 책임 원칙을 만족하기 위해서는 객체는 하나의 액터에 대한 책임만 가지고 있어야 합니다.
여기서 책임이란, 하나의 특정 액터를 위한 기능 집합을 의미하고, 액터란 기능(클래스, 모듈)을 사용하는 주체를 의미합니다. 이렇게 클래스를 나누면, 각 클래스의 의미를 파악하기도 쉽고 한 책임의 변경으로부터 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있습니다.
불편한 상황을 생각해봅시다!
회사에서 회계팀에서 급여를 계산하기 위해, 인사팀이 근무 시간을 보고하기 위해 Checktime
이라는 근무시간 측정 모듈을 참조합니다. 회계팀, 인사팀은 각각 Checktime
모듈을 사용하고 있는 액터이겠죠! SRP를 위배했네요!
어떤 상황이 발생할까요?
어느날 회계팀에서 6시 이후부터 측정하던 초과 근무 급여를 7시 이후로 측정하기로 변경했다고 합시다. 그러면 회계팀은 Checktime
모듈에서 초과 근무 측정 방법을 변경할 것입니다.
그런데, 인사팀은 이러한 변경사항을 모르고 어느 날부터 보고할 근무 시간 측정에 이상이 생긴것을 알고 본인의 모듈이나, 클래스를 수정하게 될 것입니다.
이렇게 SRP를 지키지 않으면 변경의 연쇄작용이 일어납니다. 이를 해결하기 위해서는 하나의 모듈이나 객체의 책임(액터)는 하나만 존재해야하고, 위의 문제 상황에서는 급여 계산용 근무시간 측정 모듈과 보고용 근무시간 측정 모듈을 나누는 방법으로 해결할 수 있습니다!
OCP란, 기존의 코드는 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야한다는 원칙을 말합니다.
보통 OCP를 확장에는 개방적이고(open) 수정에는 폐쇄적(closed)이어야 한다는 의미로 정의합니다. (여기서 확장이란 새로운 기능이 추가됨을 의미합니다.)
정리하자면, OCP를 만족하려면 새로운 변경 사항이 발생했을 때 객체를 직접 수정하지 않고 변경 사항을 적용할 수 있도록 설계해야 합니다.
무엇이 생각나지 않으시나요?
이러시면 안됩니다..
OCP 원칙은 위에서 배운 추상화를 의미한다고 생각하시면 됩니다!
여러분도 추상화를 사용해서 만일 요구사항이 생겨 클래스를 추가해야한다면, 기존 코드를 크게 수정하지 않고 적절하게 상속 관계에 맞춰 추가해 확장이 가능한 설계를 할 수 있겠죠?
리스코프 치환 원칙은, 부모 객체와 이를 상속한 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙입니다.
왜 이런 원칙을 준수해야할까요? LSP 위배 사례를 보며 살펴봅시다.
마트의 할인 기간에 상품에 대해 할인을 해주고 있다고 생각해봅시다. 하지만 특정 상품은 할인을 못받는다고 하고, 이 상품을 Special Item이라고 한다면, 아마 이 Special Item은 기본 상품인 Item을 상속받곘죠. 그리고 Coupon 클래스는 다음과 같이 정의할 수 있을 것 입니다.
public class Coupon{
public int calculateDiscountAmount(Item item){
return item.getPrice() * discountRate;
}
}
하지만, 기본 Item의 경우는 할인이 들어가지 않기 때문에 이런 코드도 추가되어야 합니다.
public class Coupon{
public int calculateDiscountAmount(Item item){
if(item instanceof SpecialItem){ //LSP 위반
return 0;
}
return item.getPrice() * discountRate;
}
}
Item의 하위 타입(SpecialItem)을 Coupon에 넘겨줬을 때 기능이 잘못 작동하고, 이를 해결하기 위해 instanceof
로 예외처리를 해주었습니다. 하지만, 이 경우는 SpecailItem
이 Item
객체를 완벽하게 대체하지 못하고 있어 LSP를 위배했습니다.
만일 새로운 할인 정책이 늘어날 경우, Coupon 클래스를 계속해서 수정해주어야 하기 때문에 OCP에도 어긋나게 되겠죠!
그러면 LSP를 준수해서 구현하기 위해서는 어떻게 해야할까요?
Item(상위 객체)를 더 정교하게 추상화해주면 됩니다!
public class Item {
public boolean isDiscountAvailable(){
return true;
}
}
public class SpecialItem extends Item{
@Override
public boolean isDiscountAvailable(){
return false;
}
}
이렇게 할인이 가능한지 여부를 판단하는 기능을 Item에 포함시키면 아래와 같이 어떤 종류의 Item 구현체가 와도 Coupon클래스는 수정할 필요가 없어집니다.
public class Coupon{
public int calculateDiscountAmount(Item item){
/*
if(item instanceof SpecialItem){ //LSP 위반
return 0;
}
*/
if(!item.isDiscountAvailable())
return 0;
return item.getPrice() * discountRate;
}
}
이렇듯 LSP를 지켜야 OCP를 지킬 수 있고, 기능의 확장에 용이해집니다.
인터페이스 분리 원칙은, 객체는 자신이 호출하지 않은 메소드를 참조하지 말아야하며, 인터페이스를 잘게 분리함으로써 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것을 말합니다.
ISP는 인터페이스가 응집력 측면에서 작게 분할하여 필요한 작업만 실행하도록 하는 것을 목표로 합니다. 그리고 작게 분리된 인터페이스를 클래스가 지원되는 기능만을 선별하여 구현하면 ISP 원칙이 지켜집니다.
ISP가 안지켜지면 어떻게 될까요?
ISP를 위반했을 경우 구현체가 실제로 사용하지 않는 기능임에도 불필요한 구현을 해야합니다.
비행기와 자동차, 자전거가 모두 운송수단이라는 인터페이스의 구현체라고 생각해봅시다. 자동차나 자전거는 나는 기능이 없지만 fly()
메소드를 참조합니다. 자전거 또한, 시동을 켜지 않지만 startEngine()
메소드를 참조합니다.
자전거와 자동차의 경우 사용하지 않는 메소드를 깡통 메서드로 구현해야겠죠! 그러면 어떻게 해결할 수 있을까요?
이렇게 인터페이스의 적절한 분리를 통해 ISP원칙을 지킬 수 있습니다!
의존 역전 원칙은, 객체에서 어떤 하위 객체를 참조해야하는 상황에서 그 객체를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙을 말합니다.
DIP를 위반할 경우 하위 객체의 구체적인 내용에 클라이언트가 의존하게 되고, 하위 객체에 변화가 있을 때마다 클라이언트나 상위 객체의 코드를 자주 수정해야합니다.
정리하자면, DIP는 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 원칙입니다.
의존 역전 규칙을 위반한 경우는 무엇이 있을까요?
어떤 마트에서 신한카드만 받는다고 생각해봅시다.
초반 단계에 “어쩌피 신한카드결제시스템만 가능하니 신한카드결제시스템에만 의존하면 되지 않을까?”라는 생각에 위와 같이 구현했다고 해봅시다.
어느 날부터 우리은행카드도 가능하다는 소식이 들렸습니다.. 그러면 우리은행카드결제시스템은 어디 껴야할까요?
구현체에 의존하면…. 생기는 문제.. 느껴지시죠?
해결하려면 어떻게 해야할까요? 당연한 얘기지만 상위 객체를 구현해야합니다!
위와 같이 상위 객체에 의존하면 나중에 국민카드가 나타나도 문제가 되지 않겠죠?
의존 역전 원칙의 원칙의 지향점은 각 클래스간 결합도를 낮추는 것입니다. 상위 객체를 구현한 구현체라면 무엇이든 참조하고 있는 객체 안에서 이용이 가능합니다! 다형성을 활용하여 변경에 유연한 구조가 될 수 있겠죠!
추가 질문
- 추상 클래스와 인터페이스의 차이점이 무엇인가요?
- 추상 클래스는 기본적으로 클래스이며 이를 상속, 확장하여 사용하기 위한 것입니다. 반면 인터페이스는 해당 인터페이스를 구현한 객체들에 대한 동일한 사용방법과 동작을 보장하기 위해 사용합니다.
- 어떤 객체가 특정 인터페이스를 구현했다면, 그 객체는 인터페이스의 기능을 모두 갖추고 있음을 보장합니다. 그리고 인터페이스를 통해 해당 객체를 제어할 수 있음을 보장하는 것입니다.
오늘 객체지향설계에 대해서 톺아보았는데요. 궁금하거나 모르는 점, 잘못된 부분이 있다면 댓글 부탁드립니다!