Yazılım geliştirmede modeller: ziyaretçi modeli
Kalıplar, modern yazılım geliştirmede önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. “Design Patterns: Elements of Reusable Object-Oriented Software” kitabındaki ziyaretçi modeli iki nedenden dolayı efsanevidir. Birincisi, çok karmaşık olduğu için ve ikincisi, çifte boyun eğme adı verilen bir teknik nedeniyle.
Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.
Double Dispatch, işlevin nesnesine ve bağımsız değişkenlerine göre üye işlevi seçme sürecini açıklar. Elbette, ziyaretçi modelinin bu kadar karmaşık olmasının ana nedeni, çift gönderimin Eiffel’de olduğu gibi C++’ta yerel olarak desteklenmemesidir.
Single Dispatch ve Double Dispatch’e geçmeden önce ziyaretçi modeline değinmeme izin verin.
ziyaretçi modeli
amaç
kullanım durumu
Visitor
Ziyaretçi modeli UML diyagramı
(Resim: CC BY-SA 3.0)
Bu görüntü, ziyaretçi modeli hakkında çok iyi bir izlenim veriyor. Bu yüzden onu ziyaretçi modelinin yapısını ve dinamik davranışını açıklamak için kullanmak istiyorum.
Ziyaretçi modelinin iki tür hiyerarşisi vardır. Nesne hiyerarşisi (CarElement) ve operasyonel hiyerarşi (CarElementVisitor). Nesne hiyerarşisi sabittir, operasyon hiyerarşisi ise yeni operasyonları desteklemeyi amaçlar. her iki sınıf, CarElement Ve CarElementVisitor, arabirim görevi görür. Bu, arabanın her somut unsuru anlamına gelir Wheel, Engine, Body Ve Car üye işlevi accept(CarElementVisitor) uygulamak zorundadır. Sonuç olarak, her somut işlem CarElementDoVisitor Ve CarElementPrintVisitor dört aşırı yük visit(Wheel), visit(Engine)ziyaret(Vücut) e visit(Car) uygulamak.
Diyelim ki operasyon CarElementPrintVisitor nesne hiyerarşisine uygulanır. görevi CarElementPrintVisitor ziyaret edilen araba parçasının adını yazdırmak olabilir. İlk olarak, Motor gibi bir araba elemanı ziyaretçiyi kabul eder (accept(CarElementVisitor)) ve ziyaretçiyi işlem hiyerarşisine bir argüman olarak geri çağırmak için kullanır (visitor.visit(this)). Bu, visit(Engine)– aşırı yükleme CarElementPrintVisitor denir. Bir arabayı ziyaret etmek özel bir şeydir. Bir araba, farklı araba parçalarından oluşur. Sonuç olarak, delegeler accept-Üye işlevi car the accept-Bütün otomobil parçalarını ara.
Bu, ziyaretçinin can alıcı gözlemidir. Hangi işlemin gerçekleştirileceği iki nesneye bağlıdır: ziyaretçi ve ziyaret edilen nesne.
misal
Aşağıdaki örnek, yukarıdaki görüntüyü doğrudan koda çevirir.
// visitor.cpp
#include <iostream>
#include <string>
#include <vector>
class CarElementVisitor;
class CarElement { // (5)
public:
virtual void accept(CarElementVisitor& visitor) const = 0;
virtual ~CarElement() = default;
};
class Body;
class Car;
class Engine;
class Wheel;
class CarElementVisitor { // (6)
public:
virtual void visit(Body body) const = 0;
virtual void visit(Car car) const = 0;
virtual void visit(Engine engine) const = 0;
virtual void visit(Wheel wheel) const = 0;
virtual ~CarElementVisitor() = default;
};
class Wheel: public CarElement {
public:
Wheel(const std::string& n): name
{ }
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
std::string getName() const {
return name;
}
private:
std::string name;
};
class Body: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Engine: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Car: public CarElement {
public:
Car(std::initializer_list<CarElement*> carElements ):
elements{carElements} {}
void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
private:
std::vector<CarElement*> elements; // (7)
};
class CarElementDoVisitor: public CarElementVisitor {
void visit(Body body) const override {
std::cout << "Moving my body" << 'n';
}
void visit(Car car) const override {
std::cout << "Starting my car" << 'n';
}
void visit(Wheel wheel) const override {
std::cout << "Kicking my " << wheel.getName()
<< " wheel" << 'n';
}
void visit(Engine engine) const override {
std::cout << "Starting my engine" << 'n';
}
};
class CarElementPrintVisitor: public CarElementVisitor {
void visit(Body body) const override {
std::cout << "Visiting body" << 'n';
}
void visit(Car car) const override {
std::cout << "Visiting car" << 'n';
}
void visit(Wheel wheel) const override {
std::cout << "Visiting " << wheel.getName()
<< " wheel" << 'n';
}
void visit(Engine engine) const override {
std::cout << "Visiting engine" << 'n';
}
};
int main() {
std::cout << 'n';
Wheel wheelFrontLeft("front left");
Wheel wheelFrontRight("front right");
Wheel wheelBackLeft("back left");
Wheel wheelBackRight("back right");
Body body;
Engine engine;
Car car {&wheelFrontLeft, &wheelFrontRight,
&wheelBackLeft, &wheelBackRight,
&body, &engine};
CarElementPrintVisitor carElementPrintVisitor;
engine.accept(carElementPrintVisitor); // (1)
car.accept(carElementPrintVisitor); // (2)
std::cout << 'n';
CarElementDoVisitor carElementDoVisitor;
engine.accept(carElementDoVisitor); // (3)
car.accept(carElementDoVisitor); // (4)
std::cout << 'n';
}
Başlangıcında main-Fonksiyon, arabanın tüm bileşenlerini oluşturur. ondan sonra alırlar engine ve car the carElementPrintVisitor (1 ve 2)’ye. (3) ve (4) satırlarında her iki nesne de carElementDoVisitor iddia edildi. CarElement (5) ve CarElementVisitor (6), nesne hiyerarşisinin ve işlem hiyerarşisinin soyut temel sınıflarıdır. Şekle göre, arabanın ve ziyaretçilerin somut unsurları oluşturulmuştur. Araba en ilginç bileşendir çünkü bileşenlerini bir arada tutar. std::vector<Element*> (7).
İşte programın çıktısı:
İlgili modeller
Ziyaretçi deseni muhtemelen “Design Patterns: Elements of Reusable Object-Oriented Software” kitabından en yüksek desen yoğunluğuna sahip tasarım desenidir.
Avantajlar ve dezavantajlar
faydalar
Tek gönderme ve çift gönderme
Çift göndermeye başlamadan önce, tek gönderme veya sanal işlev çağrılarına bakmak istiyorum.
tek gönderme
Tek gönderimde, nesne hangi üye işlevin çağrılacağına karar verir. C++’da sanallığa ulaşmak iki bileşen gerektirir: işaretçi veya başvuru gibi dolaylı yönlendirme ve sanal üye işlevi.
// singleDispatch.cpp
#include <iostream>
class Ball {
public:
virtual std::string getName() const = 0;
virtual ~Ball() = default;
};
class HandBall: public Ball {
std::string getName() const override {
return "HandBall";
}
};
int main() {
std::cout << 'n';
HandBall hBall;
Ball* ballPointer = &hBall; // (1)
std::cout << "ballPointer->getName(): "
<< ballPointer->getName() << 'n';
Ball& ballReference = hBall; // (2)
std::cout << "ballReference.getName(): "
<< ballReference.getName() << 'n';
std::cout << 'n';
}
İfade Ball* ballPointer = &hBall (1) iki tipi vardır. Statik tip (Ball*) ve dinamik tip (Handball*), operatörün adresi ile değiştirilir & Iade edildi. Üye işlevinin sanallığı nedeniyle getName üye işlevinin çağrılması çalışma zamanında belirlenir. Sonuç olarak, dinamik bir çağrı ve üye işlevi gerçekleşir. getName denir. Benzer muhakeme (2)’de kullanılan referans için de geçerlidir.
Bu programın çıktısıdır:
Şimdi ziyaretçi modelinde kullanılan çift göndermeyi inceleyelim.
çift gönderme
Çift gönderimde, hangi işlemin gerçekleştirileceği iki nesneye bağlıdır.
Bu, programın özel durumunda visitor.cppziyaretçi ve ziyaret edilen nesne birlikte hangi üye fonksiyonun çağrıldığını belirler.
Daha açık olmak gerekirse: çağrı tarafından hangi eylemler tetiklenir? car.accept(carElementDoVisitor) (4) başladı mı? Kolaylık sağlamak için, işte üye işlevi accept.
void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
üye işlevi accept itibaren car tüm öğeleri ve çağrıları gözden geçirir elem->accept(visitor) bunun için: elem bir işaretçidir ve accept sanal bir işlev => dinamik gönderim
Son olarak, ziyaretçi arar visit ziyaret edilen öğeyi bağımsız değişken olarak kullanarak: visitor.visit(*this)Sonuç olarak, uygun ziyaretçi aşırı yüklemesine => statik gönderim denir
Ziyaretçi durumunda, Double Dispatch, Element (Öz-Öğe) ile Ziyaretçi arasında bir masa tenisi oyunudur. Otomatik öğe, dinamik dağıtım (geçersiz kılma) uygular ve ziyaretçi, statik dağıtım (aşırı yükleme) uygular.
Sıradaki ne?
Şablon yöntemi, bir algoritma için bir model tanımlayan davranış tabanlı bir tasarım modelidir. C++’da özel bir varyant kullanmayı seviyoruz: Sanal Olmayan Arayüz (NVI). Şablon yöntemini bir sonraki yazımda daha detaylı tanıtacağım.
(rm)
Haberin Sonu
Yazılım geliştirmede modeller: ziyaretçi modeli
Kalıplar, modern yazılım geliştirmede önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. “Design Patterns: Elements of Reusable Object-Oriented Software” kitabındaki ziyaretçi modeli iki nedenden dolayı efsanevidir. Birincisi, çok karmaşık olduğu için ve ikincisi, çifte boyun eğme adı verilen bir teknik nedeniyle.

Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.
Double Dispatch, işlevin nesnesine ve bağımsız değişkenlerine göre üye işlevi seçme sürecini açıklar. Elbette, ziyaretçi modelinin bu kadar karmaşık olmasının ana nedeni, çift gönderimin Eiffel’de olduğu gibi C++’ta yerel olarak desteklenmemesidir.

Single Dispatch ve Double Dispatch’e geçmeden önce ziyaretçi modeline değinmeme izin verin.
ziyaretçi modeli
amaç
- Bir nesne hiyerarşisinde gerçekleştirilen bir işlemi bir nesnede kapsüller
- Nesne hiyerarşisini değiştirmeden yeni işlemler tanımlamanıza izin verir
kullanım durumu
- İşlemler bir nesneler hiyerarşisinde gerçekleştirilmelidir
- İşlemler sık sık değişir
- Nesne hiyerarşisi sabittir

Visitor
- tanımlar visit-Nesnenin yapısı üzerinde işlem
- uygulamak visit-Ameliyat
- tanımlar accept– Bir ziyaretçiyi argüman olarak alan operasyon
- uygulamak accept-Ameliyat

Ziyaretçi modeli UML diyagramı
(Resim: CC BY-SA 3.0)
Bu görüntü, ziyaretçi modeli hakkında çok iyi bir izlenim veriyor. Bu yüzden onu ziyaretçi modelinin yapısını ve dinamik davranışını açıklamak için kullanmak istiyorum.
Ziyaretçi modelinin iki tür hiyerarşisi vardır. Nesne hiyerarşisi (CarElement) ve operasyonel hiyerarşi (CarElementVisitor). Nesne hiyerarşisi sabittir, operasyon hiyerarşisi ise yeni operasyonları desteklemeyi amaçlar. her iki sınıf, CarElement Ve CarElementVisitor, arabirim görevi görür. Bu, arabanın her somut unsuru anlamına gelir Wheel, Engine, Body Ve Car üye işlevi accept(CarElementVisitor) uygulamak zorundadır. Sonuç olarak, her somut işlem CarElementDoVisitor Ve CarElementPrintVisitor dört aşırı yük visit(Wheel), visit(Engine)ziyaret(Vücut) e visit(Car) uygulamak.
Diyelim ki operasyon CarElementPrintVisitor nesne hiyerarşisine uygulanır. görevi CarElementPrintVisitor ziyaret edilen araba parçasının adını yazdırmak olabilir. İlk olarak, Motor gibi bir araba elemanı ziyaretçiyi kabul eder (accept(CarElementVisitor)) ve ziyaretçiyi işlem hiyerarşisine bir argüman olarak geri çağırmak için kullanır (visitor.visit(this)). Bu, visit(Engine)– aşırı yükleme CarElementPrintVisitor denir. Bir arabayı ziyaret etmek özel bir şeydir. Bir araba, farklı araba parçalarından oluşur. Sonuç olarak, delegeler accept-Üye işlevi car the accept-Bütün otomobil parçalarını ara.
Bu, ziyaretçinin can alıcı gözlemidir. Hangi işlemin gerçekleştirileceği iki nesneye bağlıdır: ziyaretçi ve ziyaret edilen nesne.
misal
Aşağıdaki örnek, yukarıdaki görüntüyü doğrudan koda çevirir.
// visitor.cpp
#include <iostream>
#include <string>
#include <vector>
class CarElementVisitor;
class CarElement { // (5)
public:
virtual void accept(CarElementVisitor& visitor) const = 0;
virtual ~CarElement() = default;
};
class Body;
class Car;
class Engine;
class Wheel;
class CarElementVisitor { // (6)
public:
virtual void visit(Body body) const = 0;
virtual void visit(Car car) const = 0;
virtual void visit(Engine engine) const = 0;
virtual void visit(Wheel wheel) const = 0;
virtual ~CarElementVisitor() = default;
};
class Wheel: public CarElement {
public:
Wheel(const std::string& n): name
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
std::string getName() const {
return name;
}
private:
std::string name;
};
class Body: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Engine: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Car: public CarElement {
public:
Car(std::initializer_list<CarElement*> carElements ):
elements{carElements} {}
void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
private:
std::vector<CarElement*> elements; // (7)
};
class CarElementDoVisitor: public CarElementVisitor {
void visit(Body body) const override {
std::cout << "Moving my body" << 'n';
}
void visit(Car car) const override {
std::cout << "Starting my car" << 'n';
}
void visit(Wheel wheel) const override {
std::cout << "Kicking my " << wheel.getName()
<< " wheel" << 'n';
}
void visit(Engine engine) const override {
std::cout << "Starting my engine" << 'n';
}
};
class CarElementPrintVisitor: public CarElementVisitor {
void visit(Body body) const override {
std::cout << "Visiting body" << 'n';
}
void visit(Car car) const override {
std::cout << "Visiting car" << 'n';
}
void visit(Wheel wheel) const override {
std::cout << "Visiting " << wheel.getName()
<< " wheel" << 'n';
}
void visit(Engine engine) const override {
std::cout << "Visiting engine" << 'n';
}
};
int main() {
std::cout << 'n';
Wheel wheelFrontLeft("front left");
Wheel wheelFrontRight("front right");
Wheel wheelBackLeft("back left");
Wheel wheelBackRight("back right");
Body body;
Engine engine;
Car car {&wheelFrontLeft, &wheelFrontRight,
&wheelBackLeft, &wheelBackRight,
&body, &engine};
CarElementPrintVisitor carElementPrintVisitor;
engine.accept(carElementPrintVisitor); // (1)
car.accept(carElementPrintVisitor); // (2)
std::cout << 'n';
CarElementDoVisitor carElementDoVisitor;
engine.accept(carElementDoVisitor); // (3)
car.accept(carElementDoVisitor); // (4)
std::cout << 'n';
}
Başlangıcında main-Fonksiyon, arabanın tüm bileşenlerini oluşturur. ondan sonra alırlar engine ve car the carElementPrintVisitor (1 ve 2)’ye. (3) ve (4) satırlarında her iki nesne de carElementDoVisitor iddia edildi. CarElement (5) ve CarElementVisitor (6), nesne hiyerarşisinin ve işlem hiyerarşisinin soyut temel sınıflarıdır. Şekle göre, arabanın ve ziyaretçilerin somut unsurları oluşturulmuştur. Araba en ilginç bileşendir çünkü bileşenlerini bir arada tutar. std::vector<Element*> (7).
İşte programın çıktısı:

İlgili modeller
Ziyaretçi deseni muhtemelen “Design Patterns: Elements of Reusable Object-Oriented Software” kitabından en yüksek desen yoğunluğuna sahip tasarım desenidir.
Avantajlar ve dezavantajlar
faydalar
- Operasyon hiyerarşisine kolayca yeni bir operasyon (ziyaretçi) eklenebilir,
- bir işlem bir ziyaretçi e’de kapsüllenir
- nesne hiyerarşisi geçilirken bir durum oluşturulabilir.
- Nesne hiyerarşisini yenisiyle değiştirme VisitedObject Bu pahalı,
- ziyaret edilen nesne VisitedObject nesne hiyerarşisinde kaldırılmalı veya eklenmelidir e
- ziyaretçi arayüzünün genişletilmesi ve çalışır durumda olması gerekiyor visit(VisitObject)üye işlevi her belirli ziyaretçi için eklenebilir veya kaldırılabilir.
Tek gönderme ve çift gönderme
Çift göndermeye başlamadan önce, tek gönderme veya sanal işlev çağrılarına bakmak istiyorum.
tek gönderme
Tek gönderimde, nesne hangi üye işlevin çağrılacağına karar verir. C++’da sanallığa ulaşmak iki bileşen gerektirir: işaretçi veya başvuru gibi dolaylı yönlendirme ve sanal üye işlevi.
// singleDispatch.cpp
#include <iostream>
class Ball {
public:
virtual std::string getName() const = 0;
virtual ~Ball() = default;
};
class HandBall: public Ball {
std::string getName() const override {
return "HandBall";
}
};
int main() {
std::cout << 'n';
HandBall hBall;
Ball* ballPointer = &hBall; // (1)
std::cout << "ballPointer->getName(): "
<< ballPointer->getName() << 'n';
Ball& ballReference = hBall; // (2)
std::cout << "ballReference.getName(): "
<< ballReference.getName() << 'n';
std::cout << 'n';
}
İfade Ball* ballPointer = &hBall (1) iki tipi vardır. Statik tip (Ball*) ve dinamik tip (Handball*), operatörün adresi ile değiştirilir & Iade edildi. Üye işlevinin sanallığı nedeniyle getName üye işlevinin çağrılması çalışma zamanında belirlenir. Sonuç olarak, dinamik bir çağrı ve üye işlevi gerçekleşir. getName denir. Benzer muhakeme (2)’de kullanılan referans için de geçerlidir.
Bu programın çıktısıdır:

Şimdi ziyaretçi modelinde kullanılan çift göndermeyi inceleyelim.
çift gönderme
Çift gönderimde, hangi işlemin gerçekleştirileceği iki nesneye bağlıdır.
Bu, programın özel durumunda visitor.cppziyaretçi ve ziyaret edilen nesne birlikte hangi üye fonksiyonun çağrıldığını belirler.
Daha açık olmak gerekirse: çağrı tarafından hangi eylemler tetiklenir? car.accept(carElementDoVisitor) (4) başladı mı? Kolaylık sağlamak için, işte üye işlevi accept.
void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
üye işlevi accept itibaren car tüm öğeleri ve çağrıları gözden geçirir elem->accept(visitor) bunun için: elem bir işaretçidir ve accept sanal bir işlev => dinamik gönderim
Son olarak, ziyaretçi arar visit ziyaret edilen öğeyi bağımsız değişken olarak kullanarak: visitor.visit(*this)Sonuç olarak, uygun ziyaretçi aşırı yüklemesine => statik gönderim denir
Ziyaretçi durumunda, Double Dispatch, Element (Öz-Öğe) ile Ziyaretçi arasında bir masa tenisi oyunudur. Otomatik öğe, dinamik dağıtım (geçersiz kılma) uygular ve ziyaretçi, statik dağıtım (aşırı yükleme) uygular.
Sıradaki ne?
Şablon yöntemi, bir algoritma için bir model tanımlayan davranış tabanlı bir tasarım modelidir. C++’da özel bir varyant kullanmayı seviyoruz: Sanal Olmayan Arayüz (NVI). Şablon yöntemini bir sonraki yazımda daha detaylı tanıtacağım.
(rm)
Haberin Sonu