क्या कथन या स्विच की लंबी श्रृंखला के अलावा ऐसा करने का एक अधिक बुद्धिमान तरीका है?


20

मैं एक आईआरसी बॉट लागू कर रहा हूं जो एक संदेश प्राप्त करता है और मैं उस संदेश की जांच कर रहा हूं ताकि यह निर्धारित किया जा सके कि किस कॉल को कॉल करना है। क्या ऐसा करने का अधिक चतुर तरीका है? ऐसा लगता है जैसे मैं 20 आदेशों को पसंद करने के बाद जल्दी से हाथ से निकल गया।

शायद यह अमूर्त करने का एक बेहतर तरीका है?

 public void onMessage(String channel, String sender, String login, String hostname, String message){

        if (message.equalsIgnoreCase(".np")){
//            TODO: Use Last.fm API to find the now playing
        } else if (message.toLowerCase().startsWith(".register")) {
                cmd.registerLastNick(channel, sender, message);
        } else if (message.toLowerCase().startsWith("give us a countdown")) {
                cmd.countdown(channel, message);
        } else if (message.toLowerCase().startsWith("remember am routine")) {
                cmd.updateAmRoutine(channel, message, sender);
        }
    }

14
क्या भाषा, इस तरह के विस्तार के इस स्तर पर महत्वपूर्ण है।
मटनज़

3
@mattnz जावा से परिचित कोई भी व्यक्ति उसे प्रदान किए गए कोड नमूने में इसे पहचान लेगा।
jwenting

6
@jwenting: यह मान्य सी # सिंटैक्स भी है, और मुझे यकीन है कि अधिक भाषाएँ हैं।
phresnel

4
@phresnel हाँ, लेकिन क्या स्ट्रिंग के लिए समान मानक API है?
jwenting

3
@jwenting: क्या यह प्रासंगिक है? लेकिन यहां तक ​​कि अगर: एक वैध उदाहरण का निर्माण कर सकता है, उदाहरण के लिए एक जावा / सी # -इंटरटॉप हेल्पर लाइब्रेरी, या जावा के लिए एक नज़र .net: ikvm.net। भाषा हमेशा प्रासंगिक होती है। प्रश्नकर्ता विशिष्ट भाषाओं की तलाश में नहीं हो सकता है, उसने / उसने वाक्यविन्यास त्रुटियों (गलती से जावा को सी #, जैसे) में परिवर्तित किया हो सकता है, नई भाषाएं उत्पन्न हो सकती हैं (या जंगल में, जहां ड्रेगन हो) को बाहर कर सकते हैं - संपादित करें : मेरी पिछली टिप्पणियाँ डिकी, क्षमा करें।
१३:१४

जवाबों:


45

प्रेषण तालिका का उपयोग करें । यह एक तालिका है जिसमें जोड़े ("संदेश भाग" pointer-to-function) हैं। प्रेषणकर्ता इस तरह दिखेगा (छद्म कोड में):

for each (row in dispatchTable)
{
    if(message.toLowerCase().startsWith(row.messagePart))
    {
         row.theFunction(message);
         break;
    }
}

( equalsIgnoreCaseपहले एक विशेष मामले के रूप में संभाला जा सकता है, या यदि आपके पास उन परीक्षणों में से कई हैं, तो दूसरे प्रेषण तालिका के साथ)।

बेशक, जो pointer-to-functionदिखना है वह आपकी प्रोग्रामिंग भाषा पर निर्भर करता है। यहाँ C या C ++ में एक उदाहरण दिया गया है। जावा या C # में आप शायद उस उद्देश्य के लिए लैम्ब्डा एक्सप्रेशन का उपयोग करेंगे, या आप कमांड पैटर्न का उपयोग करके "पॉइंटर-टू-फंक्शन" का अनुकरण करेंगे। मुफ्त ऑनलाइन पुस्तक " हायर ऑर्डर पर्ल " में पर्ल का उपयोग करके डिस्पैच टेबल के बारे में एक पूरा अध्याय है।


4
हालाँकि, इसके साथ समस्या यह है कि आप मिलान तंत्र को नियंत्रित नहीं कर पाएंगे। ओपी के उदाहरण में, वह equalsIgnoreCase"अब खेल रहा है" लेकिन toLowerCase().startsWithदूसरों के लिए उपयोग करता है।
मर्जिंक

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

1
FYI करें - "पॉइंटर टू फंक्शन" एक C # भाषा फीचर है जिसे डेलिगेट्स कहा जाता है। लैम्ब्डा एक एक्सप्रेशन ऑब्जेक्ट है जो कि पास किया जा सकता है - आप एक लैम्बडा को "कॉल" नहीं कर सकते हैं जैसे आप एक प्रतिनिधि को कॉल कर सकते हैं।
यूजर 1068

1
toLowerCaseलूप से ऑपरेशन को ऊपर उठाएं ।
zwol

1
@HarrisonNguyen: मैं docs.oracle.com/javase/tutorial/java/javaOO/… सलाह देता हूं । लेकिन अगर आप जावा 8 का उपयोग नहीं कर रहे हैं, तो आप केवल "मैच" भाग के बिना मिंक की इंटरफ़ेस परिभाषा का उपयोग कर सकते हैं, यह 100% समतुल्य है (यही मेरा मतलब है "कमांड पैटर्न का उपयोग करके पॉइंटर-टू-फंक्शन अनुकरण करना") और user106868। टिप्पणी विभिन्न प्रोग्रामिंग भाषाओं में इसी अवधारणा के विभिन्न संस्करणों के लिए अलग-अलग शब्दों के बारे में सिर्फ एक टिप्पणी है।
डॉक ब्राउन

31

मैं शायद ऐसा कुछ करूँगा:

public interface Command {
  boolean matches(String message);

  void execute(String channel, String sender, String login,
               String hostname, String message);
}

तब आपके पास इस इंटरफ़ेस को लागू करने के लिए हर कमांड हो सकता है, और जब यह संदेश से मेल खाता है तो यह सच है।

List<Command> activeCommands = new ArrayList<>();
activeCommands.add(new LastFMCommand());
activeCommands.add(new RegisterLastNickCommand());
// etc.

for (Command command : activeCommands) {
    if (command.matches(message)) {
        command.execute(channel, sender, login, hostname, message);
        break; // handle the first matching command only
    }
}

यदि संदेशों को कहीं और पार्स करने की आवश्यकता नहीं है (मुझे लगता है कि मेरे जवाब में) यह वास्तव में मेरे समाधान के लिए Commandबेहतर है, अगर यह खुद को कहा जाए तो यह अधिक आत्म-निहित है। यह एक मामूली उपरि पैदा करता है अगर आज्ञाओं की सूची बहुत बड़ी है लेकिन यह नगण्य है।
JHR

1
यह पैटर्न कंसोल कमांड को पैराडाइम को अच्छी तरह से फिट करने के लिए जाता है, लेकिन केवल इसलिए कि यह बड़े पैमाने पर कमांड लॉजिक को इवेंट बस से अलग करता है और क्योंकि भविष्य में नए कमांड को जोड़े जाने की संभावना है। मुझे लगता है कि यह ध्यान दिया जाना चाहिए कि यह किसी भी परिस्थिति के लिए एक समाधान नहीं है जहां आपके पास एक लंबी श्रृंखला है ... और अधिक
नील

एक "प्रकाश" कमांड पैटर्न का उपयोग करने के लिए +1! यह ध्यान दिया जाना चाहिए कि जब आप गैर-स्ट्रिंग्स के साथ काम कर रहे होते हैं तो आप उदाहरण के लिए बुद्धिमान एनमों का उपयोग कर सकते हैं जो जानते हैं कि उनके तर्क को कैसे निष्पादित किया जाए। यह भी आप के लिए लूप बचाता है।
LastFreeNickname

3
लूप के बजाय, हम इसे एक मानचित्र के रूप में उपयोग कर सकते हैं यदि आप कमांड को एक अमूर्त वर्ग बनाते हैं जो ओवरराइड करता है equalsऔर hashCodeस्ट्रिंग के समान है जो कमांड का प्रतिनिधित्व करता है
क्रंचर

यह भयानक और समझने में आसान है। सलाह के लिये धन्यवाद। यह वास्तव में मैं क्या देख रहा था और आदेशों के भविष्य के अलावा के लिए प्रबंधनीय लगता है।
हैरिसन गुयेन

15

आप जावा का उपयोग कर रहे हैं - इसलिए इसे सुंदर बनाएं ;;;

मैं शायद एनोटेशन का उपयोग करके ऐसा करूंगा:

  1. एक कस्टम विधि एनोटेशन बनाएँ

    @IRCCommand( String command, boolean perfectmatch = false )
  2. कक्षा जैसे सभी प्रासंगिक विधियों में एनोटेशन जोड़ें

    @IRCCommand( command = ".np", perfectmatch = true )
    doNP( ... )
  3. अपने निर्माता में अपनी कक्षा के सभी एनोटेट तरीकों से तरीकों का एक हैशपॉप बनाने के लिए प्रतिबिंबों का उपयोग करें:

    ...
    for (Method m : getDeclaredMethods()) {
    if ( isAnnotationPresent... ) {
        commandList.put(m.getAnnotation(...), m);
        ...
  4. अपने onMessageतरीके में, बस commandListस्ट्रिंग को हर एक पर मिलान करने की कोशिश करते हुए और कॉल करें method.invoke()कि वह कहाँ फिट बैठता है।

    for ( @IRCCommand a : commanMap.keyList() ) {
        if ( cmd.equalsIgnoreCase( a.command )
             || ( cmd.startsWith( a.command ) && !a.perfectMatch ) {
            commandMap.get( a ).invoke( this, cmd );

यह स्पष्ट नहीं है कि वह जावा का उपयोग कर रहा है, हालांकि यह एक सुंदर समाधान है।
नील

आप सही कह रहे हैं - कोड सिर्फ इतना दिखता था जैसे ग्रहण-ऑटो-स्वरूपित जावा-कोड ... लेकिन आप C # के साथ भी ऐसा ही कर सकते थे और C ++ के साथ आप कुछ चतुर मैक्रों के साथ एनोटेशन का अनुकरण कर सकते थे
Falco

यह बहुत अच्छी तरह से जावा हो सकता है, हालांकि भविष्य में, मैं सुझाव देता हूं कि आप भाषा-विशिष्ट समाधानों से बचें यदि भाषा स्पष्ट रूप से इंगित नहीं की गई है। बस दोस्ताना सलाह।
नील

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

5

क्या होगा यदि आप एक इंटरफेस को परिभाषित, का कहना है कि IChatBehaviourजो एक विधि कहा जाता है Executeजो एक में ले जाता है messageऔर एक cmdवस्तु:

public Interface IChatBehaviour
{
    public void execute(String message, CMD cmd);
}

अपने कोड में, आप फिर इस इंटरफ़ेस को लागू करते हैं और अपने इच्छित व्यवहारों को परिभाषित करते हैं:

public class RegisterLastNick implements IChatBehaviour
{
    public void execute(String message, CMD cmd)
    {
        if (message.toLowerCase().startsWith(".register"))
        {
            cmd.registerLastNick(channel, sender, message);
        }
    }
}

और इतने पर बाकी के लिए।

आपके मुख्य वर्ग में, आपके पास तब व्यवहारों की एक सूची होती है ( List<IChatBehaviour>) जो आपकी IRC बॉट लागू करती है। फिर आप अपने ifबयानों को कुछ इस तरह से बदल सकते हैं :

for(IChatBehaviour behaviour : this.behaviours)
{
    behaviour.execute(message, cmd);
}

ऊपर आपके पास कोड की मात्रा कम होनी चाहिए। उपरोक्त दृष्टिकोण आपको बॉट क्लास को स्वयं (जैसे भी हो Strategy Design Pattern) संशोधित किए बिना आपके बॉट क्लास में अतिरिक्त व्यवहार की आपूर्ति करने की अनुमति देगा ।

यदि आप किसी एक समय में केवल एक व्यवहार करना चाहते हैं, तो आप executeउपज trueके व्यवहार के हस्ताक्षर को बदल सकते हैं (व्यवहार निकाल दिया गया है) या false(व्यवहार में आग नहीं लगी) और उपरोक्त लूप को इस तरह से बदल दें:

for(IChatBehaviour behaviour : this.behaviours)
{
    if(behaviour.execute(message, cmd))
    { 
         break;
    }
}

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


1
एस कहाँ चला गया if? यानी, आप कैसे तय करते हैं कि एक कमांड के लिए एक व्यवहार निष्पादित किया जाता है?
मर्जिंक

2
@ मर्जिंक: माफ़ करना मेरी बुर। व्यवहार को निष्पादित करने या न करने का निर्णय (मुझे गलती ifसे व्यवहार में भाग छोड़ दिया गया है)।
नपंती

मुझे लगता है कि मैं इस बारे में जांच पसंद करता हूं कि क्या कोई दिए IChatBehaviourगए कमांड को संभाल सकता है, क्योंकि यह कॉल करने वाले को इसके साथ और अधिक करने की अनुमति देता है, यदि कोई कमांड मैच नहीं करता है, तो प्रसंस्करण त्रुटियों की तरह, हालांकि यह वास्तव में केवल एक व्यक्तिगत प्राथमिकता है। यदि इसकी आवश्यकता नहीं है, तो कोई भी बिंदु अनावश्यक रूप से जटिल कोड नहीं है।
नील

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

1

"बुद्धिमान" तीन चीजें हो सकती हैं:

उच्च प्रदर्शन

डिस्पैच टेबल (और इसके समकक्ष) सुझाव एक अच्छा है। इस तरह की तालिका को "कैन्ट ऐड" के लिए पिछले वर्षों में "सीएडीईटी" कहा जाता था; हालांकि, उक्त तालिका को प्रबंधित करने के तरीके पर एक नौसिखिए अनुचर की सहायता के लिए एक टिप्पणी पर विचार करें।

रख-रखाव

"इसे सुंदर बनाओ" कोई निष्क्रिय नसीहत नहीं है।

और, अक्सर अनदेखी ...

लचीलाता

टॉलेरकेस के उपयोग में इस तरह की कमी है कि कुछ भाषाओं में कुछ पाठों को मैगीस्क्यू और मिनीस्कुल के बीच बदलते समय दर्दनाक पुनर्गठन से गुजरना होगा। दुर्भाग्य से, एक ही नुकसान toUpperCase के लिए मौजूद हैं। बस जागरूक रहिए।


0

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

public interface Command {
    public void execute(String channel, String message, String sender) throws Exception;
}

public class MessageParser {
    public Command parseCommandFromMessage(String message) {
        // TODO Put your if/switch or something more clever here
        // e.g. return new CountdownCommand();
    }
}

public class Whatever {
    public void onMessage(String channel, String sender, String login, String hostname, String message) {
        Command c = new MessageParser().parseCommandFromMessage(message);
        c.execute(channel, message, sender);
    }
}

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


मुझे लगता है कि यह डिकूपिंग और प्रोग्राम ऑर्गनाइजेशन के लिए अच्छा है, हालांकि मुझे नहीं लगता कि यह समस्या को बहुत अधिक होने के कारण सीधे संबोधित करता है।
नील

0

मैं यह क्या करूंगा:

  1. आपके पास जो कमांड हैं उन्हें ग्रुप्स में ग्रुप करें। (आपके पास अभी कम से कम 20 हैं)
  2. पहले स्तर पर, समूह द्वारा वर्गीकृत करें, इसलिए आपके पास उपयोगकर्ता नाम संबंधित कमांड, गीत कमांड, काउंट कमांड, आदि हैं।
  3. फिर आप प्रत्येक समूह की विधि में जाते हैं, इस बार आपको मूल आदेश मिलेगा।

यह इसे और अधिक प्रबंधनीय बना देगा। अधिक लाभ तब होता है जब 'और यदि' की संख्या बहुत अधिक बढ़ जाती है।

बेशक, कभी-कभी ये 'अगर और' एक बड़ी समस्या नहीं होगी। मुझे नहीं लगता कि 20 खराब है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.