Menu
Coddy logo textTech

Saf Sanal Fonksiyonlar

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 59 / 104.

Bir saf sanal fonksiyon (pure virtual function), temel sınıfta herhangi bir gerçeklemesi bulunmayan sanal bir fonksiyondur. Fonksiyon bildirimine = 0 atanarak bildirilir. Bu, derleyiciye türetilmiş sınıfların kendi gerçeklemelerini sağlamasının zorunlu olduğunu söyler.

class Shape {
public:
    virtual double area() = 0;  // Saf sanal fonksiyon
    virtual ~Shape() = default;
};

Varsayılan bir davranış sağlayan normal sanal fonksiyonların aksine, saf sanal fonksiyonlar bir sözleşme tanımlar: Herhangi bir somut türetilmiş sınıfın örneklenebilir olması için bu fonksiyonu uygulaması gerekir. Temel sınıf sadece ne yapılması gerektiğini bildirir, nasıl yapılacağını değil.

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
};

Hem Circle hem de Rectangle, area() fonksiyonunu uygulamalıdır çünkü bu Shape içinde saf sanal (pure virtual) bir fonksiyondur. Eğer türetilmiş bir sınıf tüm saf sanal fonksiyonları uygulayamazsa, o da soyut (abstract) hale gelir ve örneklendirilemez.

Taban sınıfın anlamlı bir varsayılan uygulama sağlayamadığı durumlarda saf sanal fonksiyonlar (pure virtual functions) temeldir. Her şeklin bir alanı vardır, ancak belirli şekil türünü bilmeden "genel şekil alanı" hesaplamanın mantıklı bir yolu yoktur.

challenge icon

Görev

Kolay

Saf sanal fonksiyonların (pure virtual functions) farklı ödeme yöntemleri arasında bir sözleşmeyi nasıl zorunlu kıldığını gösteren bir ödeme işleme sistemi kuralım. Her ödeme işlemcisinin ne yapması gerektiğini tanımlayan soyut bir temel sınıf oluşturacak, ardından bu sözleşmeyi yerine getiren somut ödeme türlerini uygulayacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • PaymentProcessor.h: Tüm ödeme yöntemleri için bir taslak görevi gören soyut bir PaymentProcessor sınıfı tanımlayın. Bu sınıf şunlara sahip olmalıdır:
    • Bir korumalı (protected) std::string accountId üyesi
    • Hesap kimliğini (account ID) başlatan bir yapıcı (constructor)
    • Bir saf sanal (pure virtual) processPayment(double amount) metodu — her ödeme türü bunu farklı şekilde uygulamalıdır
    • std::string döndüren bir saf sanal getProcessorName() metodu
    • Bir sanal yıkıcı (virtual destructor)
  • PaymentMethods.h: PaymentProcessor sınıfından miras alan iki somut ödeme işlemcisi uygulayın:

    CreditCardProcessor:

    • Bir özel (private) double feePercentage üyesi (işlem ücreti oranı)
    • Hesap kimliği ve ücret yüzdesini alan bir yapıcı
    • Ücreti hesaplamak (amount * feePercentage / 100) ve ardından şunu yazdırmak için processPayment() metodunu uygulayın:
      Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;fee>)
    • "CreditCard" döndürmek için getProcessorName() metodunu uygulayın

    BankTransferProcessor:

    • Bir özel (private) std::string bankName üyesi
    • Hesap kimliği ve banka adını alan bir yapıcı
    • Şunu yazdırmak için processPayment() metodunu uygulayın:
      Bank Transfer [<accountId>] via <bankName>: Transferred Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount>
    • "BankTransfer" döndürmek için getProcessorName() metodunu uygulayın
  • main.cpp: Dört girişi (her biri ayrı bir satırda) okuyun:
    1. Kredi kartı hesap kimliği
    2. Kredi kartı ücret yüzdesi (double)
    3. Banka hesap kimliği
    4. Banka adı

    Her iki ödeme işlemcisini de oluşturun ve bunları bir PaymentProcessor* işaretçi dizisinde saklayın. Her işlemci üzerinden 100.0 tutarında bir ödeme gerçekleştirin ve her işlemden önce işlemci adını görüntüleyin:

    Processing with <processorName>:
    <processPayment output>

    İşlemciler arasına boş bir satır yazdırın. İşiniz bittiğinde dinamik olarak ayrılmış nesnelerinizi temizleyin.

Örneğin, CC-4521, 2.5, BA-7890 ve National Bank girişleri ile:

Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)

Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100

PaymentProcessor saf sanal fonksiyonlara sahip olduğu için, onu doğrudan örnekleyemezsiniz — yalnızca sözleşmeyi yerine getiren somut uygulamalar oluşturulabilir. Bu, her ödeme yönteminin kendi özel işleme mantığını sağlamasını garanti eder.

Kopya kağıdı

Bir saf sanal fonksiyon (pure virtual function), bir sanal fonksiyona = 0 atanarak bildirilir ve bu, temel sınıfta bir uygulaması olmadığını gösterir:

class Shape {
public:
    virtual double area() = 0;  // Pure virtual function
    virtual ~Shape() = default;
};

Saf sanal fonksiyonlar, türetilmiş sınıfların mutlaka uygulaması gereken bir sözleşme oluşturur. Temel sınıf, nasıl yapılacağını değil, ne yapılması gerektiğini tanımlar.

Türetilmiş sınıfların örneklenebilir (instantiable) olması için tüm saf sanal fonksiyonları geçersiz kılması (override) gerekir:

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

Eğer türetilmiş bir sınıf tüm saf sanal fonksiyonları uygulamazsa, o da soyut (abstract) hale gelir ve örneklenemez.

Temel sınıfın anlamlı bir varsayılan uygulama sağlayamadığı durumlarda saf sanal fonksiyonları kullanın.

Kendin dene

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // Girdileri oku
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: 2 elemanlı bir PaymentProcessor* işaretçi dizisi oluşturun
    
    // TODO: CreditCardProcessor ve BankTransferProcessor nesneleri oluşturun
    // ve bunları diziye kaydedin
    
    // TODO: Dizi üzerinde döngü kurun ve her işlemci için:
    // 1. "Processing with <processorName>:" yazdırın
    // 2. 100.0 tutarı ile processPayment fonksiyonunu çağırın
    // 3. İşlemciler arasında boş bir satır yazdırın (sonuncusundan sonra değil)
    
    // TODO: Dinamik olarak ayrılmış nesneleri temizleyin
    
    return 0;
}
quiz iconKendini test et

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