Yazılım geliştirmede modeller: ziyaretçi modeli

Adanali

Active member
Yazılım geliştirmede modeller: ziyaretçi modeli


  1. 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
çerçeve








Visitor

  • tanımlar visit-Nesnenin yapısı üzerinde işlem
ConcreteVisitor

  • uygulamak visit-Ameliyat
Element

  • tanımlar accept– Bir ziyaretçiyi argüman olarak alan operasyon
ConcreteElement

  • uygulamak accept-Ameliyat
Sanırım bu temsil çok basitti. Wikipedia’daki ziyaretçi modelinden alınan aşağıdaki resim, nesne hiyerarşisini ve işlem hiyerarşisini iyi bir şekilde göstermektedir.







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(n) { }

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.
Dezavantajları

  • 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.
Ziyaretçi modelinin karmaşıklığının ana nedeni çift gönderidir.

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
 
Üst