2015-10-05 16 views
8

Eldeki görev, Java web uygulamamın bir parçasını oluşturmaktır; bu, küçük kod parçalarını kompozisyonsal bir şekilde kolayca yürütmeme olanak tanır. Eldeki görev, kullanıcının herhangi bir sırada "eylemleri" oluşturmasına izin vermektir. Benim mücadele ettiğim şey, parametreleri eylemlerime geçirmektir.Temiz programlama uygulamalarını korurken genel bir eylem sınıfı nasıl tasarlanır?

Tüm eylem arayüzü ile başlar:

eylem çözülene
public interface Action { 
    void resolve(Context context); 
} 

, bu kodu yürütüldüğünde bu. Kod herhangi bir şey olabilir: Java'da bir yöntemi çağırmak, bazı Javascript yürütmek ...

Burada, "bağlam" benim için sorun. Her eylem belirli bir bağlam içinde yürütülür. Buradaki fikir, eylemi oluşturan kullanıcının kavramdan hangi nesnenin alınacağını, örneğin mevcut eylemi çözen kullanıcının veya eylemin özel arayüzünde belirtilen diğer nesneleri belirtebilmesidir.

Örnek, hadi bu eylemin bakalım:

public final class ActionScript implements Action { 
    private final Parameters parameters; 
    private final String methodName; 
    private final ScriptsLibrary library; 

    public ActionScript(ScriptsLibrary library, String methodName, Parameters parameters) { 
     this.parameters = parameters; 
     this.library = library; 
     this.methodName = methodName;  
    } 

    @Override 
    public void resolve(Context context) { 
     try { 
      ((Invocable) library.getEngine()).invokeFunction(methodName, context); 
     } catch (ScriptException | NoSuchMethodException ex) { 
      throw new RuntimeException(ex); 
     } 
    } 
} 

O Gergedan kullanarak JavaScript bir eylem çağrısında basit sarıcı var. parametreler herhangi bir şey olabilir: Yani

public class DummyAction implements Action { 
    @Override 
    public void resolve(Context context) { 
     User userExecutingTheAction = context.get("ExecutingUser"); 
     System.out.println("User " + userExecutingTheAction); 
    } 
} 

: DB belirli kimlikleri ile nesneler, int/Dizi/boole değerleri bir işlemden kaynaklanan

kod şöyle olabilir ... kullanıcı tarafından yapılandırılan eylem çalışma zamanı parametrelerini (örn: eylemi yürüten kullanıcı ...) ve statik parametreleri (eylem yüklendiğinde oluşturulan - örneğin bir yapılandırma dosyasından) ve tüm bu kavramdan alabilir. Ayrıca, kullanıcı, örneğin çalışma zamanında enjekte edilecek parametrelerdeki nesneye referanslar belirleyebilir. Tam kompozisyona ulaşmak için iç içe/dekore edilebilir. Örneğin: Bu veriyor

public class DummyWrapperAction implements Action { 
    private final Action wrappedAction; 
    public DummyWrapperAction(Action wrappedAction) { 
     this.wrappedAction = wrappedAction; 
    } 

    @Override 
    public void resolve(Context context) { 
     System.out.println("Before"); 
     wrappedAction.resolve(context); 
     System.out.println("After"); 
    } 
} 

eylemleri kolaylıkla oluşturulacak:

// executes specific action 1 or specific action 2 based on a condition 
Action myAction = new LoggingAction(new ConditionalAction(new Condition(3), new SpecificAction1(), new SpecificAction2())); 

tüm Bunu bilen bağlam sınıfını tasarlamak için en temiz kuyu nedir? Birkaç parçaya bölünmeli mi? Mücadele, çalışma zamanında sınıf için gereken her şeyi enjekte etmek ve potansiyel sarılmış eylemlerle çatışmamak için emin olun.

  • bir işlem tutma eylemi geliştiricilerin statik parametreler sağlayan ve kullanıcı kaynaşma
  • o erişim sağlayarak
  • zamanında DB'den herhangi bir nesne alma:

    Bağlam temel uygulama sorumludur çalışma zamanı ve statik kavram ve bu bir sarıcıda bile (ör: bir üst eylem bir alt eylemi çağırırsa, eylemi yürüten kullanıcısı hala bilinmelidir; dolayısıyla bağlam statik çocuk parametrelerini birleştirmeli ve çalıştırılmalıdır. gibi zaman parametreleri

Çok fazla uğraştığını hissediyorum.Tasarım, çok sorumluluklara sahip olan Kavram sınıflarından etkilenir ve bunların parçalanması gerekir (şu anda uygulamanın her parçası, konseptle bağlantılıdır). Ama nasıl ? Statik mekanizmaların

  • hiçbir kullanım
  • maksimum değişmezlik (parametrelerin reasonnable sayı ile, 1 yöntem max) eylem arayüzü basit tutulmalı

    • : Burada temiz kodlama başarmayı denemek ne

    Nesne yönelimli ve yöntem odaklı bir şekilde, bu belirli tasarım sorununu nasıl çözebilirim?

    düzenleme: arayüzünde yöntemde kamu Bildiricisi kaldırıldı

    düzenleme 2: Ben ilginç çözümlere bir sürü vardı, ama bana daha mantıklı birer eylem belirli bir ile parametrelenmişse edildiği biriydi Bağlam türü.

    • Eylem arayüzü eylem olduğunda eylemlerin
    • statik parametreler yüklenir, bunlar DB olmak ya da başka olabilir, bağlam ile harekete çözmek için, yine de yalnızca bir yöntemi vardır: Ben gibi şeyler yeniden
    • bağlam eylem
  • +1

    Biraz soru sormayı basitleştirir misiniz? İçeri girmesi biraz fazla olduğu için. –

    +0

    Elbette! Eylem arabirimi, Bağlam nesnesinin çok fazla olan nesnesine dayanır. İçerik, eylemi gerçekleştiren kullanıcı gibi nesneleri alabilen eylem tasarımcılarına bir hizmet sağlar. Bağlamı parçalara ayırmalı mıyım? Yoksa genel tasarımda özlediğim bir şey mi? – MMacphail

    +0

    Şimdi sorunuzu daha net görmeye başlıyorum. Sorunun 'Context' nesnesi ise, belki de [ChangeListener] (http://docs.oracle.com/javase/7/docs/api/javax/swing/event/ChangeListener.html) arabirimini uygulamak yerine 'Eylem' arayüzü. Kendi benzer 'İçerik 'nesnesine sahip ve' Eylem 'dinleyicinizle aynı şeyi yapıyor gibi görünüyor? –

    cevap

    2

    : o zaman

    public interface Action<C extends Context> { 
    
        void resolve(C context); // no need to use 'public' modifier here 
              // interface methods are always public 
    } 
    

    Ve Context bir işaretleyici arayüzü, bir sözleşme zorlar bir arayüz veya varsayılan yöntemi uygulamaları ile soyut bir sınıf ya olabilir:

    Sonra
    public interface Context { 
    
        User get(String user); 
    
        // other default methods here 
    } 
    

    , bunu yapabilirsiniz:

    public class LogUserContext implements Context { 
    
        @Override 
        public User get(String user) { 
         // materialize user here 
        } 
    } 
    

    Ve kullanıcı olabilir kaydeder bir Action:

    public interface WrapperContext extends Context { 
    
        Context getWrappedContext(); 
    } 
    

    Uygulama: Yani

    public class DummyWrappedContext implements WrapperContext { 
    
        private final Context wrappedContext; 
    
        public DummyWrapperContext(Context wrappedContext) { 
         this.wrappedContext = wrappedContext; 
        } 
    
        @Override 
        public Context getWrappedContext() { 
         return this.wrappedContext; 
        } 
    
        // TODO other methods from Context, etc. 
    } 
    

    sarılmış bir Action için

    public class LogUserAction implements Action<LogUserContext> { 
    
        @Override 
        public void resolve(LogUserContext context) { 
         User user = context.get("theUser"); 
         // log the user in 
        } 
    } 
    

    , ben WrapperContext bağlamı kullanırım şimdi DummyWrapperAction aşağıdaki gibi olabilir:

    public class DummyWrapperAction implements Action<WrapperContext> { 
    
        private final Action wrappedAction; 
    
        public DummyWrapperAction(Action wrappedAction) { 
         this.wrappedAction = wrappedAction; 
        } 
    
        @Override 
        public void resolve(WrapperContext context) { 
         System.out.println("Before"); 
         Context wrappedContext = context.getWrappedContext(); 
         wrappedAction.resolve(wrappedContext); 
         System.out.println("After"); 
        } 
    } 
    

    Buradaki fikir, bağlamları da sarmaktır. Bu tasarımı, bağlamları zincirlemenize veya süslemenize izin vererek ve tüm bağlamlarda ortak olan görevlerden sorumlu olacak soyut sınıfları kullanarak geliştirebilirsiniz.

    +1

    Çok güzel tasarım! İhtiyaçlarıma uyup uymadığını görmek için şu anda test ediyorum, ne olacağını bileceğim :). Anlayışlar için çok teşekkürler! – MMacphail

    +1

    Teşekkürler! Tasarımıma bir eldiven gibi uyuyor :) Sarılmış içeriği kullanmamıştım çünkü sonuçta benim bağlamlarım birbirine benziyordu! – MMacphail

    1

    Tercüman GoF desen kullanıcısı gibi çeşitli operasyonlar (işlem ...) + özel operasyonlar için sadece bir cephesi (kullanıcı oturum) oluşturulan, ağaç bazılarında externalized edilebilir yorumlanır yol. Herhangi bir düğümü süsleyebilir ve isteğinize güzelce yanıt verebilirsiniz. Bir şey daha: eylem arabirimi en azından temsilin dışsal olması durumunda (yani kullanıcı tarafından yapılmışsa) iki yönteme sahip olmalıdır: ağacın geçerliliğini kontrol etmek için (örneğin iki çocuğun olmasını gerektiren bir düğüm tam olarak bu çocuklara sahip olmalıdır) ve bir ağacı yürütmek.

    Düzenleme: Burada

    import java.util.*; 
    
    //context can be further complicated if you want visibility blocks (i.e.you need a stack of contexts) 
    
    class Context { 
    private Map<String, Object> variables; 
    
    Context() { 
        variables = new HashMap<java.lang.String, Object>(); 
    } 
    
    //this is to retrieve stuff from your context (variables) 
    Object getVariable(String name) { return variables.get(name); } 
    
    // put here the shared structs 
    void setVariable(String name, Object value) { variables.put(name, value); } 
    } 
    
    
    interface Action<T> { 
    
    void verify(Context ctx); 
    
    void execute(Context ctx); 
    
    T getData(); 
    
    void setData(T t); 
    
    List<Action<?>> getChildren(); 
    
    void addChild(Action<?> action); 
    } 
    
    // we offer some default impl, but we keep it abstract 
    // note on design: you can split (derive) this in Terminal/Non-terminal nodes (careful at the above interface) 
    // however, it's not in my objective to follow the pattern to the letter, but to explain how the things can be done 
    // plus, I need to kepp this short, it's long enough 
    abstract class BaseAction<T> implements Action<T> { 
    private List<Action<?>> children; 
    private T data; 
    
    public BaseAction() { 
        children = new LinkedList<Action<?>>(); 
    } 
    
    @Override 
    public void verify(Context ctx) { 
        for(Action<?> a : children) { 
         a.verify(ctx); 
        } 
    } 
    
    @Override 
    public void execute(Context ctx) { 
        for(Action<?> a : children) { 
         a.execute(ctx); 
        } 
    } 
    
    @Override 
    public T getData() { 
        return data; 
    } 
    
    @Override 
    public void setData(T t) { 
        this.data = t; 
    } 
    
    @Override 
    public List<Action<?>> getChildren() { 
        return Collections.unmodifiableList(children); 
    } 
    
    @Override 
    public void addChild(Action<?> action) { 
        children.add(action); 
    } 
    } 
    
    class BlockAction<T> extends BaseAction<T> {} //needs further refinement, including push/pop contexts if necessary 
    
    //let's implement your Action Script, some stuff left out. 
    // we suppose that the action produces some string 
    // that's a final node 
    final class ActionScript extends BaseAction<String>{ 
    private final String library; 
    private final String methodName; 
    private final String aParameter; 
    
    public ActionScript(final String library, final String methodName, final String aParameter) { 
        this.library = library; 
        this.methodName = methodName; 
        this.aParameter = aParameter; 
    } 
    
    @Override 
    public void verify(Context ctx) { 
        if(!getChildren().isEmpty()) { 
         throw new RuntimeException("Terminal node with children ?!?"); 
        } 
    } 
    
    @Override 
    public void execute(Context ctx) { 
        //do whatever here (your code) 
        String paramValue = (String) ctx.getVariable(aParameter); 
        setData(library + "." + methodName + "(" + paramValue + ")"); 
    } 
    } 
    
    // this can be further complicated, i.e. to have 2 subnodes, but for simplicity: 
    final class AssignmentAction<T> extends BaseAction<T> { 
    private String variableName; 
    
    public AssignmentAction(String variableName) { 
        this.variableName = variableName; 
    } 
    
    @Override 
    public void verify(Context ctx) { 
        if(getChildren().size() != 1) { 
         throw new RuntimeException(String.format("= node with %d children ?!?", getChildren().size())); 
        } 
        super.verify(ctx); 
    } 
    
    @Override 
    public void execute(Context ctx) { 
        @SuppressWarnings("unchecked") 
        Action<T> child = (Action<T>) getChildren().get(0); 
        child.execute(ctx); 
        ctx.setVariable(variableName, child.getData()); 
    } 
    } 
    
    public class IP { 
    public static void main(String []args) { 
        Context ctx = new Context(); 
        ctx.setVariable("inputVar", "Hello world!"); 
        Action<String> root = new BlockAction<String>(); 
        root.addChild(new AssignmentAction<String>("var")); 
        root.getChildren().get(0).addChild(new ActionScript("myLib", "foo", "inputVar")); 
    
        root.verify(ctx); 
    
        root.execute(ctx); 
    
        System.out.println(ctx.getVariable("var")); 
    } 
    } 
    

    artık açıktır Umut bir örnek.

    Ben onun Context üzerine jenerik Action arayüzünü yapmış
    +0

    Tercümana baktım; Örnekler bulunur, aritmetik gibi işlemler karmaşıktır, bu yüzden daha karmaşık örneklerden ne elde edebileceğimi görmek için daha fazla tomota bakacağım. Doğrulama hakkında, neden başka bir düğüme gerek var? Düğümün yapımında doğrulama yapılamaz mı? – MMacphail

    +0

    Yeap, tüm örneklerin aritmetik olduğunu biliyorum (kolay anlaşılır bir dilbilgisi sunuyorlar). Ancak gerçekten uzayabilir bir çözüm arıyorsanız, işte böyle. –

    +0

    Sonunda bir eylem ağacın olacak. Yukarıdaki ağacın doğrulaması, kullanıcı girişi sağlıyorsa, tüm yürütme sırasında büyük bir deneme yapma gibi bir şey oluşturmak istemediğiniz sürece, yürütme sırasında gerçekleşmemelidir. Doğrulama düğümde yapılmalıdır. –

    İlgili konular