سؤال

لدي هذه الواجهة:

public interface Command<T> {
    T execute(String... args);
}

إنه يعمل بشكل جيد لمعظم الاستخدامات. ولكن عندما أحاول نموذج أمر له آثار جانبية فقط (على سبيل المثال دون قيمة الإرجاع)، أتم إغراءك بالكتابة:

public class SideEffectCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
        return null; // null is fine?
    }
} 

هل هذه مشكلة شائعة؟ هل هناك أفضل الممارسات لنموذج Commands مع و بدون قيمة العودة؟

لقد حاولت مع هذا المحول، لكنني أعتقد أن هذا ليس مثاليا لعدة أسباب:

public abstract class VoidCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
       execute2(args);
       return null;
    }

    public abstract void execute2(String... args);
}
هل كانت مفيدة؟

المحلول

إليك تنفيذ أفضل من بين العديد من العالمين.

// Generic interface for when a client doesn't care
// about the return value of a command.
public interface Command {
    // The interfaces themselves take a String[] rather
    // than a String... argument, because otherwise the
    // implementation of AbstractCommand<T> would be
    // more complicated.
    public void execute(String[] arguments);
}

// Interface for clients that do need to use the
// return value of a command.
public interface ValuedCommand<T> extends Command {
    public T evaluate(String[] arguments);
}

// Optional, but useful if most of your commands are ValuedCommands.
public abstract class AbstractCommand<T> implements ValuedCommand<T> {
    public void execute(String[] arguments) {
        evaluate(arguments);
    }
}

// Singleton class with utility methods.
public class Commands {
    private Commands() {} // Singleton class.

    // These are useful if you like the vararg calling style.
    public static void execute(Command cmd, String... arguments) {
        cmd.execute(arguments);
    }

    public static <T> void execute(ValuedCommand<T> cmd, String... arguments) {
        return cmd.evaluate(arguments);
    }

    // Useful if you have code that requires a ValuedCommand<?>
    // but you only have a plain Command.
    public static ValuedCommand<?> asValuedCommand(Command cmd) {
        return new VoidCommand(cmd);
    }

    private static class VoidCommand extends AbstractCommand<Void> {
        private final Command cmd;

        public VoidCommand(Command actual) {
            cmd = actual;
        }

        public Void evaluate(String[] arguments) {
            cmd.execute(arguments);
            return null;
        }
    }
}

مع هذا التنفيذ، يمكن للعملاء التحدث عن Command إذا كانوا لا يهتمون بقيمة العودة، و ValuedCommand<T> إذا كانت الحاجة إلى أمر يعيد قيمة معينة.

حول السبب الوحيد لعدم الذهاب مع استخدام Void مستقيم يصل كل شيء القبيح return null; البيانات التي سوف تضطر إلى إدراجها.

نصائح أخرى

أود التمسك باستخدام Void صراحة. من السهل أن نرى ما يجري دون فئة أخرى تشارك. سيكون لطيفا إذا كنت تستطيع تجاوز Void العودة مع voidInteger مع int, ، إلخ)، ولكن هذه ليست أولوية.

يبدو جيدا في نظري. كما قال آخرون، Void تم تصميمه أصلا لآلية الانعكاس، لكنه يستخدم الآن في الأجداد في كثير من الأحيان لوصف المواقف مثل لك.

أفضل: جوجل، في إطار GWT الخاص بهم، استخدم نفس الفكرة في أمثلةهم للحصول على رد فعل إرجاع الفراغ (مثال هنا). أقول: إذا فعلت Google ذلك، يجب أن يكون على الأقل موافق .. :)

هذه ليست مشكلة شائعة. المشكلة التي تحتاجها لمعالجة هي توقع واجهةك. أنت تجمع بين سلوك واجهة تأثير غير جانبية مع واجهة تسمح بالتأثيرات الجانبية.

النظر في هذا:

public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();       
        reportResults(sec);     
    }   

    public static void reportResults(Command<?> cmd){

        final Object results = cmd.execute("arg");
        if (results != null ) {
            System.out.println(results.getClass());
        }
    }
}

لا حرج في استخدام <Void> كما نوع القالب ولكن السماح لها بالخلط مع تطبيقات "أمر"<T>"يعني أن بعض عملاء الواجهة قد لا يتوقعون نتيجة فراغ. دون تغيير الواجهة، سمحت بتنفيذ إنشاء نتيجة غير متوقعة.

عندما نمر جميع البيانات باستخدام فئات المجموعة، وافق فريقي على أبدا إرجاع NULL على الرغم من أنه بخلي اليد. كانت المشكلة هي أن الفئات التي استخدمت قيم المرتجلة سيتعين على باستمرار التحقق من الفراغ لمنع NPE. باستخدام الكود أعلاه، سترى هذا في كل مكان:

    if (results != null ){

لأن هناك الآن طريقة لمعرفة ما إذا كان التنفيذ لديه بالفعل كائن أو لاغيا. للحصول على حالة معينة، تأكد من أنك تعرف لأنك على دراية بالتنفيذ. ولكن بمجرد أن تبدأ في إجمالهم أو نقلوا إلى ما وراء أفق الترميز الخاص بك (يستخدم كمكتبة وصيانة في المستقبل وما إلى ذلك) ستوفر المشكلة الفارغة نفسها.

بعد ذلك، حاولت هذا:

public class SideEffectCommand implements Command<String> {

    @Override
    public String execute(String... args) {
        return "Side Effect";
    }

}

public class NoSideEffectCommand implements Command<Void>{
    @Override
    public Void execute(String... args) {
        return null;
    }   
}
public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();
        Command<?> nsec = new NoSideEffectCommand();

        reportResults(sec.execute("args"));
        reportResults(nsec.execute("args")); //Problem Child
    }   

    public static void reportResults(Object results){
        System.out.println(results.getClass());
    }

    public static void reportResults(Void results){
        System.out.println("Got nothing for you.");
    }
}

لا يعمل التحميل الزائد لأن الدعوة الثانية إلى Reportresults لا تزال تسمى الإصدار الذي يتوقع كائن (بالطبع). لقد تفكرت في تغيير

public static void reportResults(String results){

ولكن هذا يوضح جذر المشكلة، يبدأ رمز العميل الخاص بك الاضطرار إلى معرفة تفاصيل التنفيذ. من المفترض أن تساعد الواجهات في عزل تبعيات التعليمات البرمجية عند الإمكان. لإضافتهم في هذه المرحلة يبدو أن التصميم السيئ.

سطر ما تحتاجه إلى استخدام تصميم يتضح عند توقع وجود أمر له تأثير جانبي والتفكير من خلال كيفية التعامل مع مجموعة من الأوامر، أي مجموعة من الأوامر غير المعروفة.

قد يكون هذا حالة التجريدات المتسربة.

احتفظ بالواجهة كما هي: هذا هو السبب في أنه باطلة في المكتبة القياسية. طالما أن كل ما يسمى الأمر تتوقع أن تعود Nulls Nulls.

ونعم، NULL هو القيمة الوحيدة التي يمكنك العودة إليها باطل.

تحديث 2017.

خلال السنوات القليلة الماضية لقد تجنبت Void كما أنواع العودة باستثناء المكان الذي يشعر انعكاس. لقد استخدمت نمطا مختلفا وأعتقد أنه أكثر وضوحا و يتجنب الخيال. وهذا هو، لدي نوع النجاح، الذي أسميه Ok الذي يتم إرجاعه لجميع الأوامر مثل OP. وقد عمل هذا بشكل جيد للغاية لفريقي ونشر أيضا في استخدام فرق أخرى.

public enum Ok { OK; }

public class SideEffectCommand implements Command<Ok> {
    @Override
    public Ok execute(String... args) {
        ...
        return Ok.OK; // I typically static import Ok.OK;
}

ما هو مثير للاهتمام في مثالك هو استخدام الأنواع المعلمة. عادة ما لديك

interface Command<T> {
    public T execute(T someObject);
}

في حالتك لديك فقط T كقيمة العودة. ومع ذلك، باستخدام الفراغ في هذه الحالة هو حل جيد. يجب أن تكون قيمة الإرجاع null.

الذي - التي مشكلة ليس الذي - التي شائع، ولكن لا ندرك ... أعتقد أنني رأيت مناقشة حول ذلك منذ بعض الوقت، حول القذيفة التي لا تعاد أي شيء.

وأنا أتفق مع الملصقات الأخرى أن هذا حل جيد، أفضل بكثير من استخدام الكائن أو بعض العنصر النائب الدمية الآخر.

ماذا لو بدلا من وجود واجهة مثل:

public interface Command<T> {
    T execute(String... args);
}

كنت بدلا من ذلك:

public interface Command<T> {
    void execute(String... args);
    T getResult();
    bool hasResult();
}

ثم سيفعل المتصلون:

public void doSomething(Command<?> cmd) {
    cmd.execute(args);
    if(cmd.hasResult()) {
        // ... do something with cmd.getResult() ...
    }
}

يمكنك أيضا إنشاء واجهة VoidCommmand التي تمتد الأمر، إذا أردت.

هذا يبدو وكأنه أنظف الحل بالنسبة لي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top