C++'ta Arayüz Tasarımı
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 61 / 104.
C++'da bir arayüz (interface), tüm üye fonksiyonların saf sanal (pure virtual) olduğu soyut bir sınıftır. Bazı uygulamalar içerebilen soyut sınıfların aksine, arayüzler yalnızca hangi işlemlerin var olması gerektiğini tanımlar; herhangi bir davranış olmaksızın bir sözleşme belirtirler.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Bu Drawable arayüzü, uygulayan herhangi bir sınıfın draw() ve resize() metotlarına sahip olacağını garanti eder, ancak bunların nasıl çalıştığı hakkında hiçbir şey söylemez. Bu ayrım güçlüdür; kod, somut türler hakkında hiçbir şey bilmeden arayüze bağımlı olabilir.
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 { /* butonu yeniden boyutlandır */ }
};Bir sınıf birden fazla arayüz uygulayabilir; bu da nesnelerin farklı rolleri üstlenebildiği esnek tasarımlara olanak tanır:
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; }
};Arayüzler gevşek bağlılığı (loose coupling) teşvik eder; kodunuz somut uygulamalar yerine soyutlamalara dayanır, bu da genişletmeyi ve test etmeyi kolaylaştırır.
Görev
KolayArayüzlerin farklı cihaz türleri için sözleşmeleri nasıl tanımladığını gösteren bir cihaz yönetim sistemi kuralım. Cihazların uygulayabileceği iki ayrı arayüz oluşturacak, ardından bu sözleşmelerden birini veya her ikisini yerine getiren somut cihaz sınıfları inşa edeceksiniz.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
Interfaces.h: Farklı cihaz yeteneklerini temsil eden iki saf soyut sınıf (arayüz) tanımlayın:Powerable— açılıp kapatılabilen herhangi bir cihaz:- Saf sanal bir
powerOn()metodu - Saf sanal bir
powerOff()metodu std::stringdöndüren saf sanal birgetPowerStatus()metodu- Sanal bir yıkıcı (destructor)
Connectable— bir ağa bağlanabilen herhangi bir cihaz:- Saf sanal bir
connect(const std::string& network)metodu - Saf sanal bir
disconnect()metodu std::stringdöndüren saf sanal birgetConnectionInfo()metodu- Sanal bir yıkıcı (destructor)
- Saf sanal bir
Devices.h: Bu arayüzleri kullanan somut cihaz sınıflarını uygulayın:Lamp— yalnızcaPowerablearayüzünü uygular:- Özel (private) bir
bool isOnüyesi (falseolarak başlar) - Özel (private) bir
std::string nameüyesi - Lambanın adını alan bir kurucu (constructor)
powerOn(),isOndeğerini true yapar ve şunu yazdırır:<name>: Light turned onpowerOff(),isOndeğerini false yapar ve şunu yazdırır:<name>: Light turned offgetPowerStatus(), duruma göre"ON"veya"OFF"döndürür
SmartTV— hemPowerablehem deConnectablearayüzlerini uygular:- Özel (private) üyeler:
bool isOn(false olarak başlar),std::string brand,std::string currentNetwork(boş olarak başlar) - TV'nin markasını alan bir kurucu (constructor)
powerOn(),isOndeğerini true yapar ve şunu yazdırır:<brand> TV: Powered onpowerOff(),isOndeğerini false yapar, ağı temizler ve şunu yazdırır:<brand> TV: Powered offgetPowerStatus(),"ON"veya"OFF"döndürürconnect(), ağ adını saklar ve şunu yazdırır:<brand> TV: Connected to <network>disconnect(), ağı temizler ve şunu yazdırır:<brand> TV: DisconnectedgetConnectionInfo(), bağlıysa"Connected to <network>", boşsa"Not connected"döndürür
- Özel (private) bir
main.cpp: Üç girdi okuyun (her biri ayrı bir satırda):- Lamba adı
- TV markası
- Ağ adı
Bir
Lampve birSmartTVoluşturun. Aynı arayüzün farklı cihazlarla nasıl kullanılabileceğini gösterin:İlk olarak, her iki cihazla da
Powerablearayüzü üzerinden çalışın. Her ikisinin işaretçilerini (pointer) birPowerable*dizisinde saklayın, ardından döngü ile her biri içinpowerOn()metodunu çağırın ve durumlarını şu şekilde yazdırın:Status: <powerStatus>Boş bir satır yazdırın, ardından SmartTV ile
Connectablearayüzü üzerinden çalışın. SmartTV'niz için birConnectable*işaretçisi oluşturun, ağ adıylaconnect()metodunu çağırın ve şunu yazdırın:Connection: <connectionInfo>Başka bir boş satır yazdırın, ardından
Powerabledizisi üzerinden her iki cihazı da kapatın ve son durumlarını gösterin.
Örneğin, Desk Lamp, Samsung ve HomeWiFi girdileriyle:
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: OFFSmartTV'nin, hangi arayüz işaretçisini kullandığınıza bağlı olarak hem Powerable hem de Connectable olarak nasıl ele alınabildiğine dikkat edin. Bu esneklik, birden fazla arayüz uygulamanın gücüdür; kodunuz, somut türü bilmesine gerek kalmadan, ihtiyaç duyduğu sözleşmeyi yerine getiren herhangi bir cihazla çalışabilir.
Kopya kağıdı
C++'ta bir arayüz (interface), tüm üye fonksiyonların saf sanal (pure virtual) olduğu soyut bir sınıftır. Arayüzler, herhangi bir uygulama (implementation) içermeden yalnızca hangi işlemlerin var olması gerektiğini tanımlar:
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Sınıflar, tüm saf sanal fonksiyonları geçersiz kılarak (overriding) arayüzleri uygularlar:
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; }
};Bir sınıf, çoklu kalıtım (multiple inheritance) kullanarak birden fazla arayüzü uygulayabilir:
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; }
};Arayüzler, kodun somut uygulamalar yerine soyutlamalara dayanmasını sağlayarak gevşek bağlılığı (loose coupling) teşvik eder.
Kendin dene
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// Girdileri oku
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Bir Lamp ve bir SmartTV nesnesi oluşturun
// TODO: Her iki cihazı da içeren bir Powerable* işaretçi dizisi oluşturun
// TODO: Dizi boyunca döngü kurun ve her cihazda powerOn() fonksiyonunu çağırın
// Her powerOn() işleminden sonra şunu yazdırın: Status: <powerStatus>
// TODO: Boş bir satır yazdırın
// TODO: SmartTV için bir Connectable* işaretçisi oluşturun
// Ağ adıyla connect() fonksiyonunu çağırın
// Yazdırın: Connection: <connectionInfo>
// TODO: Boş bir satır yazdırın
// TODO: Powerable dizisi boyunca döngü kurun ve her cihazda powerOff() fonksiyonunu çağırın
// Her powerOff() işleminden sonra şunu yazdırın: Status: <powerStatus>
return 0;
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner