Aşağıdaki kod snippet'inden çok / if-if dan kaçmanın en iyi yolu nedir?


14

Ben girdi olarak geçti "eylem" değerine dayalı görev yapan bir sunucu uygulaması yazmaya çalışıyorum.

İşte örneği

public class SampleClass extends HttpServlet {
     public static void action1() throws Exception{
          //Do some actions
     }
     public static void action2() throws Exception{
          //Do some actions
     }
     //And goes on till action9


     public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
          String action = req.getParameter("action");

          /**
           * I find it difficult in the following ways
           * 1. Too lengthy - was not comfortable to read
           * 2. Makes me fear that action1 would run quicker as it was in the top
           * and action9 would run with a bit delay - as it would cross check with all the above if & else if conditions
           */

          if("action1".equals(action)) {
               //do some 10 lines of action
          } else if("action2".equals(action)) {
               //do some action
          } else if("action3".equals(action)) {
               //do some action
          } else if("action4".equals(action)) {
               //do some action
          } else if("action5".equals(action)) {
               //do some action
          } else if("action6".equals(action)) {
               //do some action
          } else if("action7".equals(action)) {
               //do some action
          } else if("action8".equals(action)) {
               //do some action
          } else if("action9".equals(action)) {
               //do some action
          }

          /**
           * So, the next approach i tried it with switch
           * 1. Added each action as method and called those methods from the swith case statements
           */
          switch(action) {
          case "action1": action1();
               break;
          case "action2": action2();
               break;
          case "action3": action3();
               break;
          case "action4": action4();
               break;
          case "action5": action5();
               break;
          case "action6": action6();
               break;
          case "action7": action7();
               break;
          case "action8": action8();
               break;
          case "action9": action9();
               break;
          default:
               break;
          }

          /**
           * Still was not comfortable since i am doing un-necessary checks in one way or the other
           * So tried with [reflection][1] by invoking the action methods
           */
          Map<String, Method> methodMap = new HashMap<String, Method>();

        methodMap.put("action1", SampleClass.class.getMethod("action1"));
        methodMap.put("action2", SampleClass.class.getMethod("action2"));

        methodMap.get(action).invoke(null);  

       /**
        * But i am afraid of the following things while using reflection
        * 1. One is Security (Could any variable or methods despite its access specifier) - is reflection advised to use here?
        * 2. Reflection takes too much time than simple if else
        */

     }
    }

Tüm ihtiyacım çok fazla if / else-if daha iyi okunabilirlik ve daha iyi kod bakım için kodumu denetler kaçmak etmektir. Bu yüzden diğer alternatifler için denendi

1. durum değiştirme - yine de benim eylem yapmadan önce çok fazla kontrol yapar

2. yansıma

i] bir ana şey güvenlik - erişim belirtecine rağmen sınıftaki değişkenlere ve yöntemlere bile erişmeme izin veriyor - hava kodumda kullanabileceğimden emin değilim

ii] ve diğeri ise basit if / else-if kontrollerinden daha uzun zaman alıyor

Yukarıdaki kodun daha iyi bir şekilde düzenlenmesini önerebilecek daha iyi bir yaklaşım veya daha iyi bir tasarım var mı?

REDAKTE

Aşağıdaki cevabı dikkate alarak yukarıdaki pasaj için cevabı ekledim .

Ama yine de, aşağıdaki "ExecutorA" ve "ExecutorB" sınıfları sadece birkaç satır kod yapar. Bunları sınıf olarak eklemek, yöntem olarak eklemekten daha iyi bir uygulama mudur? Lütfen bu konuda bilgi verin.



2
Neden tek bir sunucu uygulamasını 9 farklı eylemle aşırı yüklüyorsunuz? Neden her bir işlemi farklı bir sunucu uygulamasıyla desteklenen farklı bir sayfaya eşlemiyorsunuz? Bu şekilde eylemin seçimi istemci tarafından yapılır ve sunucu kodunuz sadece istemcinin isteğini sunmaya odaklanır.
Maybe_Factor

Yanıtlar:


13

Önceki cevaba dayanarak, Java enumların özelliklere sahip olmasını sağlar, böylece bir strateji deseni tanımlayabilirsiniz, örneğin

public enum Action {
    A ( () -> { //Lambda Sintax
        // Do A
       } ), 
    B ( () -> executeB() ), // Lambda with static method
    C (new ExecutorC()) //External Class 

    public Action(Executor e)
        this.executor = e;
    }

    //OPTIONAL DELEGATED METHOD
    public foo execute() {
        return executor.execute();
    }

    // Action Static Method
    private static foo executeB(){
    // Do B
    }
}

O zaman Executor(Stratejiniz)

public interface Executor {
    foo execute();
}

public class ExecutorC implements Executor {
    public foo execute(){
        // Do C
    }
}

Ve doPostmetodunuzdaki tüm if / else yöntemleriniz

public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    String action = req.getParameter("action");
    Action.valueOf(action).execute();
}

Bu şekilde, enumdaki uygulayıcılar için lambdas bile kullanabilirsiniz.


iyi dedi .. Ama ben küçük bir açıklama gerekir .. Tüm eylemler action1 (), action2 () birkaç satır kod olurdu .. bir sınıf içinde paketi için iyi olacak mı?
Tom Taylor

4
Bu, sizi belirli sınıflar / nesneler yaratmaya ikna etmesi gereken satır sayısı değil, farklı davranışları temsil ettikleri gerçeğidir. 1 fikir / kavram = 1 mantıksal nesne.
mgoeminne

2
@RajasubaSubramanian Bir sınıfın çok ağır olduğunu düşünüyorsanız lambda veya yöntem başvurusu da kullanabilirsiniz. Executorişlevsel bir arabirimdir (veya olabilir).
Hulk

1
@ J.Pichardo Güncelleme için teşekkürler :) Hala java7'de olduğum için lambda ifadesini kullanamadım .. Yani, burada önerilen strateji modelinin numaralandırma uygulamasını takip ettim dzone.com/articles/strategy-pattern-implemented
Tom Taylor

1
@RajasubaSubramanian havalı, yeni bir şey öğrendim,
J. Pichardo

7

Yansımayı kullanmak yerine özel bir arayüz kullanın.

yani:

      /**
       * Still was not comfortable since i am doing un-necessary checks in one way or the other
       * So tried with [reflection][1] by invoking the action methods
       */
      Map<String, Method> methodMap = new HashMap<String, Method>();

    methodMap.put("action1", SampleClass.class.getMethod("action1"));
    methodMap.put("action2", SampleClass.class.getMethod("action2"));

    methodMap.get(action).invoke(null);  

kullanım

 public interface ProcessAction{
       public void process(...);
 }

Her eylem için her birini uygular ve sonra:

 // as attribute
Map<String, ProcessAction> methodMap = new HashMap<String, ProcessAction>();
// now you can add to the map you can either hardcode them in an init function
methodMap.put("action1",action1Process);

// but if you want some more flexibility you should isolate the map in a class dedicated :
// let's say ActionMapper and add them on init : 

public class Action1Manager{
    private static class ProcessAction1 implements ProcessAction{...}

    public Action1Manager(ActionMapper mapper){
       mapper.addNewAction("action1", new ProcessAction1());
    }
}

Tabii ki bu çözüm en hafif değil, bu yüzden bu uzunluğa gitmeniz gerekmeyebilir.


ProcessActionbunun yerine olması gerektiğini düşünüyorum ActionProcessyani ...?
Tom Taylor

1
Evet düzelttim.
Walfrat

1
Ve daha genel olarak, cevap "OOP mekanizmalarını kullanmak" tır. Yani, burada, "durumu" ve onunla ilişkili davranışı yeniden anlamalısınız. Başka bir deyişle, mantığınızı soyut bir nesne ile temsil eder ve daha sonra bu nesneyi alttaki somun ve cıvatalar yerine manipüle eder.
mgoeminne

Ayrıca, @Walfrat tarafından önerilen yaklaşımın doğal bir uzantısı, belirtilen String parametresine bağlı olarak doğru ProcessAction'ı oluşturan / döndüren bir (soyut) fabrika önermek olacaktır.
mgoeminne

@mgoeminne Sağdaki ses
J. Pichardo

2

Komut Deseni'ni kullanın , bunun için aşağıdaki gibi bir Komut Arayüzü gerekir:

interface CommandInterface {
    CommandInterface execute();
}

Eğer Actionsbir yapı için hafif ve ucuz ardından Fabrika Yöntemini kullanın. Sınıf adlarını eşleyen bir özellikler dosyasından yükleyin ve actionName=classNameyürütme eylemlerini oluşturmak için basit bir fabrika yöntemi kullanın.

    public Invoker execute(final String targetActionName) {
        final String className = this.properties.getProperty(targetAction);
        final AbstractCommand targetAction = (AbstractCommand) Class.forName(className).newInstance();
        targetAction.execute();
    return this;
}

Eylemler oluşturmak pahalıysa , HashMap gibi bir havuz kullanın ; ancak çoğu durumda bunun , pahalı unsuru komutların kendilerinden ziyade önceden oluşturulmuş ortak kaynak havuzuna devrederken Tek Sorumluluk İlkesi kapsamında önlenebileceğini öneririm .

    public class CommandMap extends HashMap<String, AbstractAction> { ... }

Bunlar daha sonra

    public Invoker execute(final String targetActionName) {
        commandMap.get(targetActionName).execute();
        return this;
}

Bu, SOLID ilkelerinin SRP, LSP ve ISP'sini uygulayan çok sağlam ve ayrıştırılmış bir yaklaşımdır . Yeni komutlar, komut eşleyici kodunu değiştirmez. Komutların uygulanması kolaydır. Bunlar sadece proje ve özellikler dosyasına eklenebilir. Komutlar yeniden girilmeli ve bu da onu çok başarılı kılar.


1

Dize değerlerini hardCoding gereksinimini azaltmak için numaralandırma tabanlı nesneyi kullanabilirsiniz. Size biraz zaman kazandıracak ve kodu gelecekte okumak ve genişletmek için çok düzgün hale getirir.

 public static enum actionTypes {
      action1, action2, action3....
  }

  public void doPost {
      ...
      switch (actionTypes.valueOf(action)) {
          case action1: do-action1(); break;
          case action2: do-action2(); break;
          case action3: do-action3(); break;
      }
  }

1

Fabrika Metodu deseni, ölçeklenebilir ve daha az bakım yapılabilir tasarım arıyorsanız, göründüğüm şeydir.

Fabrika Metodu deseni, bir nesne oluşturmak için bir arabirim tanımlar, ancak alt sınıfın hangi sınıfın örnekleneceğine karar vermesine izin verin. Fabrika Yöntemi, bir sınıfın örneklemeyi alt sınıfa ertelemesini sağlar.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ action doStuff yöntemi ile somut uygulama yapılacak şeyleri uygulamak.

Sadece ara

    action.doStuff(actionN)

Gelecekte daha fazla eylem ortaya çıkarsa, sadece somut sınıf eklemeniz gerekir.


typo abstarct -> ilk kod satırında özet. Lütfen düzenleyin. Ayrıca, OP'nin sorusunu nasıl yanıtladığını daha doğrudan göstermek için bu örneği temizlemek için biraz daha kod ekleyebilir misiniz?
Jay Elston

0

@J referansıyla. Pichardo cevap yukarıdaki pasajı aşağıdaki gibi yazıyorum

public class SampleClass extends HttpServlet {

public enum Action {
    A (new ExecutorA()),
    B (new ExecutorB())

    Executor executor;

    public Action(Executor e)
        this.executor = e;
    }

    //The delegate method
    public void execute() {
        return executor.execute();
    }
}

public foo Executor {
    foo execute();
}

public class ExecutorA implements Executor{
   public void execute() {
      //Do some action
   }
}

public class ExecutorB implements Executor{
   public void execute() {
      //Do some action
   }
}

public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {

  String action = req.getParameter("action"); 
  Action.valueOf(action).execute();
  }
}

Çok fazla eylem varsa çok fazla sınıf oluşturmuyor musunuz? Daha iyi bir uygulama var mı?
Vaibhav Sharma
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.