तो, मैं सिर्फ विज़िटर पैटर्न के बारे में पढ़ रहा था और मुझे विज़िटर और तत्वों के बीच आगे और पीछे बहुत अजीब लगा!

मूल रूप से हम तत्व कहते हैं, हम इसे एक आगंतुक पास करते हैं और फिर तत्व खुद को आगंतुक के पास जाता है। और फिर आगंतुक तत्व को संचालित करता है। क्या? क्यों? यह बहुत अनावश्यक लगता है। मैं इसे "आगे और पीछे पागलपन" कहता हूं।

इसलिए, विज़िटर का इरादा तत्वों को उनके कार्यों से अलग करना है जब सभी तत्वों में समान क्रियाओं को लागू करने की आवश्यकता होती है। यह तब किया जाता है जब हमें अपने तत्वों को नए कार्यों के साथ विस्तारित करने की आवश्यकता होती है, हम उन सभी वर्गों में नहीं जाना चाहते हैं और कोड को संशोधित करना चाहते हैं जो पहले से ही स्थिर है। इसलिए हम यहां ओपन/क्लोज्ड सिद्धांत का पालन कर रहे हैं।

यह सब आगे-पीछे क्यों होता है और अगर हमारे पास यह नहीं है तो हम क्या खोते हैं?

उदाहरण के लिए, मैंने यह कोड बनाया है जो उस उद्देश्य को ध्यान में रखता है लेकिन विज़िटर पैटर्न के इंटरैक्शन पागलपन को छोड़ देता है। मूल रूप से मेरे पास ऐसे जानवर हैं जो कूदते और खाते हैं। मैं उन कार्यों को वस्तुओं से अलग करना चाहता था, इसलिए मैं क्रियाओं को विज़िटर के पास ले जाता हूं। खाने और कूदने से जानवरों का स्वास्थ्य बढ़ता है (मुझे पता है, यह एक बहुत ही मूर्खतापूर्ण उदाहरण है...)

public interface AnimalAction { // Abstract Visitor
    public void visit(Dog dog);
    public void visit(Cat cat);
}

public class EatVisitor implements AnimalAction { // ConcreteVisitor
    @Override
    public void visit(Dog dog) {
        // Eating increases the dog health by 100
        dog.increaseHealth(100);
    }

    @Override
    public void visit(Cat cat) {
        // Eating increases the cat health by 50
        cat.increaseHealth(50);
    }
}

public class JumpVisitor implements AnimalAction { // ConcreteVisitor
    public void visit(Dog dog) {
        // Jumping increases the dog health by 10
        dog.increaseHealth(10);
    }

    public void visit(Cat cat) {
        // Jumping increases the cat health by 20
        cat.increaseHealth(20);
    }
}

public class Cat { // ConcreteElement
    private int health;

    public Cat() {
        this.health = 50;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

public class Dog { // ConcreteElement

    private int health;

    public Dog() {
        this.health = 10;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();

        Dog dog = new Dog();
        Cat cat = new Cat();

        jumpAction.visit(dog); // NOTE HERE. NOT DOING THE BACK AND FORTH MADNESS.
        eatAction.visit(dog);
        System.out.println(dog.getHealth());

        jumpAction.visit(cat);
        eatAction.visit(cat);
        System.out.println(cat.getHealth());
    }
}

38
AFP_555 3 जून 2021, 07:54

4 जवाब

सबसे बढ़िया उत्तर

ओपी में कोड विज़िटर डिज़ाइन पैटर्न की एक प्रसिद्ध विविधता जैसा दिखता है जिसे आंतरिक विज़िटर के रूप में जाना जाता है (उदाहरण के लिए जनता के लिए एक्स्टेंसिबिलिटी देखें। ब्रूनो द्वारा ऑब्जेक्ट बीजगणित के साथ व्यावहारिक एक्स्टेंसिबिलिटी सी. डी. एस. ओलिवेरा और विलियम आर. कुक)। हालांकि, विज़िटर पैटर्न द्वारा संबोधित कुछ समस्याओं को हल करने के लिए यह भिन्नता जेनेरिक और रिटर्न वैल्यू (void के बजाय) का उपयोग करती है।

वह कौन सी समस्या है, और ओपी भिन्नता शायद अपर्याप्त क्यों है?

विज़िटर पैटर्न द्वारा संबोधित की जाने वाली मुख्य समस्या यह है कि जब आपके पास विषम वस्तुएं होती हैं जिनका आपको इलाज करने की आवश्यकता होती है। जैसा कि चारों का गिरोह, (डिजाइन पैटर्न के लेखक) कहते हैं, आप इसका उपयोग करते हैं पैटर्न जब

"एक वस्तु संरचना में विभिन्न इंटरफेस वाली वस्तुओं के कई वर्ग होते हैं, और आप इन वस्तुओं पर संचालन करना चाहते हैं जो उनके ठोस वर्गों पर निर्भर करते हैं।"

इस वाक्य से जो गायब है वह यह है कि जब आप "इन वस्तुओं पर संचालन करना चाहते हैं जो उनके ठोस वर्गों पर निर्भर करते हैं", तो आप उन ठोस वर्गों का इलाज करना चाहते हैं जैसे कि उनके पास एक बहुरूपी प्रकार है।

एक अवधि उदाहरण

पशु डोमेन का उपयोग करना शायद ही कभी उदाहरण के लिए होता है (मैं उस पर बाद में बात करूंगा), इसलिए यहां एक और यथार्थवादी उदाहरण दिया गया है। उदाहरण सी # में हैं - मुझे आशा है कि वे अभी भी आपके लिए उपयोगी हैं।

कल्पना कीजिए कि आप एक ऑनलाइन रेस्तरां आरक्षण प्रणाली विकसित कर रहे हैं। उस सिस्टम के हिस्से के रूप में, आपको उपयोगकर्ताओं को कैलेंडर दिखाने में सक्षम होना चाहिए। यह कैलेंडर प्रदर्शित कर सकता है कि किसी दिए गए दिन में कितनी शेष सीटें उपलब्ध हैं, या उस दिन सभी आरक्षणों को सूचीबद्ध कर सकता है।

कभी-कभी, आप एक दिन प्रदर्शित करना चाहते हैं, लेकिन कभी-कभी, आप पूरे महीने को एक कैलेंडर ऑब्जेक्ट के रूप में प्रदर्शित करना चाहते हैं। अच्छे उपाय के लिए पूरे एक साल में फेंको। इसका मतलब है कि आपके पास तीन अवधि: वर्ष, माह, और दिन हैं। प्रत्येक के अलग-अलग इंटरफेस हैं:

public Year(int year)

public Month(int year, int month)

public Day(int year, int month, int day)

संक्षिप्तता के लिए, ये केवल तीन अलग-अलग वर्गों के निर्माता हैं। बहुत से लोग इसे केवल एक वर्ग के रूप में अशक्त क्षेत्रों के साथ मॉडल कर सकते हैं, लेकिन यह आपको अशक्त क्षेत्रों, या एनम, या अन्य प्रकार की कुटिलता से निपटने के लिए मजबूर करता है।

उपरोक्त तीन वर्गों की संरचना अलग है क्योंकि उनमें अलग-अलग डेटा है, फिर भी आप उन्हें एक ही अवधारणा के रूप में मानना ​​चाहेंगे - एक अवधि

ऐसा करने के लिए, एक IPeriod इंटरफ़ेस परिभाषित करें:

internal interface IPeriod
{
    T Accept<T>(IPeriodVisitor<T> visitor);
}

और प्रत्येक वर्ग को इंटरफ़ेस लागू करें। ये रहा Month:

internal sealed class Month : IPeriod
{
    private readonly int year;
    private readonly int month;

    public Month(int year, int month)
    {
        this.year = year;
        this.month = month;
    }

    public T Accept<T>(IPeriodVisitor<T> visitor)
    {
        return visitor.VisitMonth(year, month);
    }
}

यह आपको तीन विषम वर्गों को एक ही प्रकार के रूप में मानने में सक्षम बनाता है, और इंटरफ़ेस को बदले बिना उस एकल प्रकार पर संचालन को परिभाषित करता है।

यहां, उदाहरण के लिए, एक कार्यान्वयन है जो पिछली अवधि की गणना करता है:

private class PreviousPeriodVisitor : IPeriodVisitor<IPeriod>
{
    public IPeriod VisitYear(int year)
    {
        var date = new DateTime(year, 1, 1);
        var previous = date.AddYears(-1);
        return Period.Year(previous.Year);
    }

    public IPeriod VisitMonth(int year, int month)
    {
        var date = new DateTime(year, month, 1);
        var previous = date.AddMonths(-1);
        return Period.Month(previous.Year, previous.Month);
    }

    public IPeriod VisitDay(int year, int month, int day)
    {
        var date = new DateTime(year, month, day);
        var previous = date.AddDays(-1);
        return Period.Day(previous.Year, previous.Month, previous.Day);
    }
}

यदि आपके पास Day है, तो आपको पिछला Day मिलेगा, लेकिन यदि आपके पास Month है, तो आपको पिछला Month मिलेगा, इत्यादि।

आप यह लेख, लेकिन यहां कोड की कुछ पंक्तियां हैं जहां उनका उपयोग किया जाता है:

var previous = period.Accept(new PreviousPeriodVisitor());
var next = period.Accept(new NextPeriodVisitor());

dto.Links = new[]
{
    url.LinkToPeriod(previous, "previous"),
    url.LinkToPeriod(next, "next")
};

यहां, period एक IPeriod ऑब्जेक्ट है, लेकिन कोड यह नहीं जानता कि यह Day, और Month, या Year है।

स्पष्ट होने के लिए, उपरोक्त उदाहरण आंतरिक विज़िटर भिन्नता का उपयोग करता है, जो कि आइसोमोर्फिक टू अ चर्च एन्कोडिंग

जानवरों

ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग को समझने के लिए जानवरों का उपयोग करना शायद ही कभी रोशन होता है। मुझे लगता है कि स्कूलों को उस उदाहरण का उपयोग करना बंद कर देना चाहिए, क्योंकि इससे मदद की तुलना में भ्रमित होने की अधिक संभावना है।

ओपी कोड उदाहरण उस समस्या से ग्रस्त नहीं है जिसे विज़िटर पैटर्न हल करता है, इसलिए उस संदर्भ में, यदि आप लाभ देखने में विफल रहते हैं तो यह आश्चर्य की बात नहीं है।

Cat और Dog वर्ग विषम नहीं हैं। उनका वर्ग क्षेत्र और व्यवहार समान है। केवल कंस्ट्रक्टर में अंतर है। आप उन दो वर्गों को एक Animal वर्ग में तुच्छ रूप से पुन: सक्रिय कर सकते हैं:

public class Animal {
    private int health;

    public Animal(int health) {
        this.health = health;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

फिर दो अलग health मानों का उपयोग करके, बिल्लियों और कुत्तों के लिए दो निर्माण विधियों को परिभाषित करें।

चूंकि अब आपके पास एक ही वर्ग है, इसलिए किसी आगंतुक की आवश्यकता नहीं है।

34
Silvio Mayolo 3 जून 2021, 20:21

विज़िटर पैटर्न ग्राफ़ संरचना के तत्वों पर फ़ंक्शन लागू करने की समस्या को हल करता है।

अधिक विशेष रूप से, यह किसी वस्तु V के संदर्भ में, कुछ ग्राफ संरचना में प्रत्येक नोड N पर जाने की समस्या को हल करता है, और प्रत्येक N के लिए, कुछ सामान्य फ़ंक्शन F (V, N) को लागू करता है। F का तरीका कार्यान्वयन V और N के प्रकार के आधार पर चुना जाता है।

कई प्रेषण वाली प्रोग्रामिंग भाषाओं में, विज़िटर पैटर्न लगभग गायब हो जाता है। यह ग्राफ ऑब्जेक्ट (जैसे रिकर्सिव ट्री डिसेंट) के चलने तक कम हो जाता है, जो प्रत्येक एन नोड के लिए एक साधारण एफ (वी, एन) कॉल करता है। किया हुआ!

उदाहरण के लिए आम लिस्प में। संक्षिप्तता के लिए, आइए कक्षाओं को परिभाषित भी न करें: integers और strings कक्षाएं हैं, तो चलिए उनका उपयोग करते हैं।

सबसे पहले, आइए एक पूर्णांक या स्ट्रिंग के प्रत्येक संयोजन के लिए एक पूर्णांक या स्ट्रिंग पर जाने के लिए, जेनेरिक फ़ंक्शन के चार तरीके लिखें। विधियां सिर्फ आउटपुट उत्पन्न करती हैं। हम जेनेरिक फ़ंक्शन को defgeneric के साथ परिभाषित नहीं करते हैं; लिस्प इसका अनुमान लगाता है और यह हमारे लिए परोक्ष रूप से करता है:

(defmethod visit ((visitor integer) (node string))
  (format t "integer ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor integer) (node integer))
  (format t "integer ~s visits integer ~s!~%" visitor node))

(defmethod visit ((visitor string) (node string))
  (format t "string ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor string) (node integer))
  (format t "string ~s visits integer ~s!~%" visitor node))

आइए अब विज़िटर द्वारा पुनरावृत्त होने के लिए हमारी संरचना के रूप में एक सूची का उपयोग करें, और उसके लिए एक रैपर फ़ंक्शन लिखें:

(defun visitor-pattern (visitor list)
  ;; map over the list, doing the visitation
  (mapc (lambda (item) (visit visitor item)) list)
  ;; return  nothing
  (values))

अंतःक्रियात्मक रूप से परीक्षण करें:

(visitor-pattern 42 '(1 "abc"))
integer 42 visits integer 1!
integer 42 visits string "abc"!

(visitor-pattern "foo" '(1 "abc"))
string "foo" visits integer 1!
string "foo" visits string "abc"!

ठीक है, तो यह विज़िटर पैटर्न है: संरचना में प्रत्येक तत्व का एक ट्रैवर्सल, एक विज़िटिंग संदर्भ ऑब्जेक्ट के साथ एक विधि के डबल प्रेषण के साथ।

"आगे और पीछे पागलपन" का संबंध ओओपी प्रणाली में दोहरे प्रेषण के अनुकरण के बॉयलर-प्लेट कोड से है, जिसमें केवल एकल प्रेषण होता है, और जिसमें विधियाँ सामान्य कार्यों की विशेषज्ञता होने के बजाय कक्षाओं से संबंधित होती हैं।

क्योंकि मुख्यधारा के एकल-प्रेषण OOP प्रणाली में, विधियों को कक्षाओं में समझाया जाता है, हमारे सामने पहली समस्या यह है कि visit विधि कहाँ रहती है? क्या यह आगंतुक या नोड पर है?

जवाब पता चलता है कि यह दोनों होना चाहिए। हमें दोनों प्रकारों पर कुछ भेजने की आवश्यकता होगी।

इसके बाद समस्या आती है कि OOP अभ्यास में, हमें अच्छे नामकरण की आवश्यकता होती है। हमारे पास visitor और visited ऑब्जेक्ट दोनों पर visit विधि नहीं हो सकती है। जब किसी विज़िट की गई वस्तु का दौरा किया जाता है, तो "विज़िट" क्रिया का उपयोग यह वर्णन करने के लिए नहीं किया जाता है कि वह वस्तु क्या कर रही है। यह एक आगंतुक को "स्वीकार करता है"। इसलिए हमें उस आधी क्रिया को accept कहना होगा।

हम एक संरचना बनाते हैं जिससे प्रत्येक नोड का दौरा किया जाता है जिसमें accept विधि होती है। यह विधि नोड के प्रकार पर भेजी जाती है, और एक Visitor तर्क लेती है। वास्तव में, नोड में कई accept विधियां हैं, जो स्थिर रूप से विभिन्न प्रकार के विज़िटर के लिए विशिष्ट हैं: IntegerVisitor, StringVisitor, FooVisitor। ध्यान दें कि हम केवल String का उपयोग नहीं कर सकते, भले ही हमारे पास भाषा में ऐसा कोई वर्ग हो, क्योंकि यह Visitor इंटरफ़ेस को visit विधि के साथ लागू नहीं करता है।

तो क्या होता है कि हम संरचना पर चलते हैं, प्रत्येक नोड N प्राप्त करते हैं, और फिर V.visit(N) पर कॉल करते हैं ताकि आगंतुक इसे देख सकें। हम V के सटीक प्रकार के बारे में नहीं जानते हैं; यह एक आधार संदर्भ है। प्रत्येक विज़िटर कार्यान्वयन को visit बॉयलर प्लेट के टुकड़े के रूप में लागू करना चाहिए (एक छद्म भाषा का उपयोग करना जो जावा या सी ++ नहीं है):

StringVisitor::visit(Visited obj)
{
  obj.Accept(self)
}

IntegerVisitor::visit(Visited obj)
{
  obj.Accept(self)
}

इसका कारण यह है कि self को Accept कॉल के लिए स्थिर रूप से टाइप किया जाना है, क्योंकि Visited ऑब्जेक्ट में संकलन समय पर चुने गए विभिन्न प्रकारों के लिए कई Accept कार्यान्वयन हैं:

IntegerNode::visit(StringVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}

IntegerNode::visit(IntegerVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}

उन सभी वर्गों और विधियों को कहीं घोषित करना होगा:

class VisitorBase {
  virtual void Visit(VisitedBase);
}

class IntegerVisitor;
class StringVisitor;

class VisitedBase {
  virtual void Accept(IntegerVisitor);
  virtual void Accept(StringVisitor);
}

class IntegerVisitor : inherit VisitorBase {
  Integer value;
  void Visit(VisitedBase);
}

class StringVisitor: inherit VisitorBase {
  String value;
  void Visit(VisitedBase);
}

class IntegerNode : inherit VisitedBase {
  Integer value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}

class StringNode : inherit VisitedBase {
  String value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}

तो यह सिंगल-डिस्पैच-विद-स्टेटिक-ओवरलोडिंग विज़िटर पैटर्न है: बॉयलर प्लेट का एक गुच्छा है, साथ ही यह सीमा है कि कक्षाओं में से एक, या तो विज़िटर या विज़िट किया गया है, अन्य सभी के स्थिर प्रकारों को जानना है समर्थित है, इसलिए यह उस पर स्थिर रूप से प्रेषण कर सकता है, और प्रत्येक स्थिर प्रकार के लिए, एक डमी विधि भी होगी।

1
Kaz 5 जून 2021, 01:16

विज़िटर में आगे-पीछे एक तरह के डबल प्रेषण तंत्र का अनुकरण करना है, जहां आप दो ऑब्जेक्ट के रन-टाइम प्रकार के आधार पर एक विधि कार्यान्वयन का चयन करें।

यह उपयोगी है यदि आपके पशु और विज़िटर के दोनों प्रकार अमूर्त (या बहुरूपी) हैं। इस मामले में आपके पास चुनने के लिए 2 x 2 = 4 विधि कार्यान्वयन की क्षमता है, a) आप किस प्रकार की क्रिया (विज़िट) करना चाहते हैं, और b) आप किस प्रकार के जानवर को यह क्रिया लागू करना चाहते हैं।

enter image description here enter image description here

यदि आप कंक्रीट और गैर-पॉलीमॉर्फिक प्रकारों का उपयोग कर रहे हैं, तो इस आगे-पीछे का हिस्सा वास्तव में अतिश्योक्तिपूर्ण है।

23
SDJ 3 जून 2021, 22:32

बैक-एंड-फोर्थ के साथ, क्या आपका यह मतलब है?

public class Dog implements Animal {

    //...

    @Override
    public void accept(AnimalAction action) {
        action.visit(this);
    }
}

इस कोड का उद्देश्य यह है कि आप ठोस प्रकार को जाने बिना इस प्रकार पर प्रेषण कर सकते हैं, जैसे यहां:

public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();


        Animal animal = aFunctionThatCouldReturnAnyAnimal();
        animal.accept(jumpAction);
        animal.accept(eatAction);
    }

    private static Animal aFunctionThatCouldReturnAnyAnimal() {
        return new Dog();
    }
}

तो आपको क्या मिलता है: आप किसी जानवर पर सही व्यक्तिगत कार्रवाई को केवल यह जानकर कह सकते हैं कि वह एक जानवर है।

यह विशेष रूप से उपयोगी है यदि आप एक समग्र पैटर्न को पार करते हैं, जहां लीफ नोड्स Animals हैं और आंतरिक नोड्स Animals के एग्रीगेशन (जैसे List) हैं। एक List<Animal> को आपके डिज़ाइन के साथ संसाधित नहीं किया जा सकता है।

7
Peter Mortensen 3 जून 2021, 19:25