Menu
Coddy logo textTech

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)을 촉진합니다. 코드가 구체적인 구현체가 아닌 추상화에 의존하게 함으로써 확장과 테스트를 더 쉽게 만들어 줍니다.

challenge icon

챌린지

쉬움

인터페이스가 서로 다른 유형의 장치에 대해 계약을 정의하는 방법을 보여주는 장치 관리 시스템을 구축해 보겠습니다. 장치가 구현할 수 있는 두 개의 개별 인터페이스를 만든 다음, 이러한 계약 중 하나 또는 둘 모두를 충족하는 구체적인 장치 클래스를 빌드합니다.

코드는 세 개의 파일로 구성됩니다.

  • Interfaces.h: 서로 다른 장치 기능을 나타내는 두 개의 순수 추상 클래스(인터페이스)를 정의합니다.

    Powerable — 켜고 끌 수 있는 모든 장치:

    • 순수 가상 powerOn() 메서드
    • 순수 가상 powerOff() 메서드
    • std::string을 반환하는 순수 가상 getPowerStatus() 메서드
    • 가상 소멸자

    Connectable — 네트워크에 연결할 수 있는 모든 장치:

    • 순수 가상 connect(const std::string& network) 메서드
    • 순수 가상 disconnect() 메서드
    • std::string을 반환하는 순수 가상 getConnectionInfo() 메서드
    • 가상 소멸자
  • Devices.h: 이러한 인터페이스를 사용하는 구체적인 장치 클래스를 구현합니다.

    LampPowerable만 구현합니다.

    • 프라이빗 bool isOn 멤버 (false로 시작)
    • 프라이빗 std::string name 멤버
    • 램프의 이름을 인자로 받는 생성자
    • powerOn()isOn을 true로 설정하고 <name>: Light turned on을 출력합니다.
    • powerOff()isOn을 false로 설정하고 <name>: Light turned off를 출력합니다.
    • getPowerStatus()는 상태에 따라 "ON" 또는 "OFF"를 반환합니다.

    SmartTVPowerableConnectable을 모두 구현합니다.

    • 프라이빗 멤버: 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: 세 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨).
    1. 램프 이름
    2. TV 브랜드
    3. 네트워크 이름

    LampSmartTV를 생성합니다. 동일한 인터페이스가 서로 다른 장치에서 어떻게 사용될 수 있는지 보여줍니다.

    먼저, 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

사용하는 인터페이스 포인터에 따라 SmartTVPowerable 또는 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;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨