C++ 인터페이스 설계
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 61번째.
C++에서 인터페이스(interface)는 모든 멤버 함수가 순수 가상(pure virtual) 함수인 추상 클래스입니다. 일부 구현을 포함할 수 있는 추상 클래스와 달리, 인터페이스는 어떤 연산이 존재해야 하는지만을 정의하며, 어떠한 동작도 없이 계약(contract)만을 명시합니다.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};이 Drawable 인터페이스는 이를 구현하는 모든 클래스가 draw() 및 resize() 메서드를 가질 것임을 보장하지만, 이들이 어떻게 작동하는지에 대해서는 아무것도 설명하지 않습니다. 이러한 분리는 강력합니다. 코드는 구체적인 타입에 대해 전혀 알지 못해도 인터페이스에 의존할 수 있습니다.
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* 버튼 크기 조정 */ }
};클래스는 여러 인터페이스를 구현할 수 있어, 객체가 다양한 역할을 수행할 수 있는 유연한 설계를 가능하게 합니다:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};인터페이스는 느슨한 결합(loose coupling)을 촉진합니다. 코드가 구체적인 구현체가 아닌 추상화에 의존하게 함으로써 확장과 테스트를 더 쉽게 만들어 줍니다.
챌린지
쉬움인터페이스가 서로 다른 유형의 장치에 대해 계약을 정의하는 방법을 보여주는 장치 관리 시스템을 구축해 보겠습니다. 장치가 구현할 수 있는 두 개의 개별 인터페이스를 만든 다음, 이러한 계약 중 하나 또는 둘 모두를 충족하는 구체적인 장치 클래스를 빌드합니다.
코드는 세 개의 파일로 구성됩니다.
Interfaces.h: 서로 다른 장치 기능을 나타내는 두 개의 순수 추상 클래스(인터페이스)를 정의합니다.Powerable— 켜고 끌 수 있는 모든 장치:- 순수 가상
powerOn()메서드 - 순수 가상
powerOff()메서드 std::string을 반환하는 순수 가상getPowerStatus()메서드- 가상 소멸자
Connectable— 네트워크에 연결할 수 있는 모든 장치:- 순수 가상
connect(const std::string& network)메서드 - 순수 가상
disconnect()메서드 std::string을 반환하는 순수 가상getConnectionInfo()메서드- 가상 소멸자
- 순수 가상
Devices.h: 이러한 인터페이스를 사용하는 구체적인 장치 클래스를 구현합니다.Lamp—Powerable만 구현합니다.- 프라이빗
bool isOn멤버 (false로 시작) - 프라이빗
std::string name멤버 - 램프의 이름을 인자로 받는 생성자
powerOn()은isOn을 true로 설정하고<name>: Light turned on을 출력합니다.powerOff()는isOn을 false로 설정하고<name>: Light turned off를 출력합니다.getPowerStatus()는 상태에 따라"ON"또는"OFF"를 반환합니다.
SmartTV—Powerable과Connectable을 모두 구현합니다.- 프라이빗 멤버:
bool isOn(false로 시작),std::string brand,std::string currentNetwork(빈 문자열로 시작) - TV 브랜드를 인자로 받는 생성자
powerOn()은isOn을 true로 설정하고<brand> TV: Powered on을 출력합니다.powerOff()는isOn을 false로 설정하고, 네트워크를 초기화하며,<brand> TV: Powered off를 출력합니다.getPowerStatus()는"ON"또는"OFF"를 반환합니다.connect()는 네트워크 이름을 저장하고<brand> TV: Connected to <network>를 출력합니다.disconnect()는 네트워크를 초기화하고<brand> TV: Disconnected를 출력합니다.getConnectionInfo()는 연결된 경우"Connected to <network>"를 반환하고, 비어 있으면"Not connected"를 반환합니다.
- 프라이빗
main.cpp: 세 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨).- 램프 이름
- TV 브랜드
- 네트워크 이름
Lamp와SmartTV를 생성합니다. 동일한 인터페이스가 서로 다른 장치에서 어떻게 사용될 수 있는지 보여줍니다.먼저,
Powerable인터페이스를 통해 두 장치를 모두 다룹니다. 두 장치의 포인터를Powerable*배열에 저장한 다음, 루프를 돌며 각각에 대해powerOn()을 호출하고, 상태를Status: <powerStatus>형식으로 출력합니다.빈 줄을 하나 출력한 다음,
Connectable인터페이스를 통해 SmartTV를 다룹니다. SmartTV에 대한Connectable*포인터를 생성하고, 네트워크 이름으로connect()를 호출한 후Connection: <connectionInfo>를 출력합니다.다시 빈 줄을 하나 출력한 다음,
Powerable배열을 통해 두 장치의 전원을 끄고 최종 상태를 표시합니다.
예를 들어, 입력값이 Desk Lamp, Samsung, HomeWiFi인 경우:
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFF사용하는 인터페이스 포인터에 따라 SmartTV가 Powerable 또는 Connectable 중 하나로 취급될 수 있음에 주목하세요. 이러한 유연성이 여러 인터페이스를 구현하는 것의 힘입니다. 여러분의 코드는 구체적인 타입을 알 필요 없이 필요한 계약을 충족하는 모든 장치와 함께 작동할 수 있습니다.
치트 시트
C++에서 인터페이스는 모든 멤버 함수가 순수 가상 함수인 추상 클래스입니다. 인터페이스는 구현 없이 어떤 연산이 존재해야 하는지만을 정의합니다.:
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};클래스는 모든 순수 가상 함수를 오버라이딩하여 인터페이스를 구현합니다.:
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};클래스는 다중 상속을 사용하여 여러 인터페이스를 구현할 수 있습니다.:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};인터페이스는 코드가 구체적인 구현이 아닌 추상화에 의존하도록 함으로써 느슨한 결합(loose coupling)을 촉진합니다.
직접 해보기
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// 입력 읽기
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Lamp 및 SmartTV 객체 생성
// TODO: 두 장치를 모두 포함하는 Powerable* 포인터 배열 생성
// TODO: 배열을 순회하며 각 장치에 대해 powerOn() 호출
// 각 powerOn() 호출 후, 다음을 출력: Status: <powerStatus>
// TODO: 빈 줄 출력
// TODO: SmartTV를 가리키는 Connectable* 포인터 생성
// 네트워크 이름과 함께 connect() 호출
// 출력: Connection: <connectionInfo>
// TODO: 빈 줄 출력
// TODO: Powerable 배열을 순회하며 각 장치에 대해 powerOff() 호출
// 각 powerOff() 호출 후, 다음을 출력: Status: <powerStatus>
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.