มีวิธีที่สง่างามที่จะทำให้ทุกวิธีในชั้นเรียนเริ่มต้นด้วยรหัสบล็อกหนึ่ง ๆ หรือไม่?


143

ฉันมีชั้นเรียนที่ทุกวิธีเริ่มต้นด้วยวิธีเดียวกัน:

class Foo {
  public void bar() {
    if (!fooIsEnabled) return;
    //...
  }
  public void baz() {
    if (!fooIsEnabled) return;
    //...
  }
  public void bat() {
    if (!fooIsEnabled) return;
    //...
  }
}

มีวิธีที่ดีที่จะต้องการ (และหวังว่าจะไม่เขียนในแต่ละครั้ง) fooIsEnabledส่วนหนึ่งสำหรับวิธีการสาธารณะในชั้นเรียนหรือไม่?


45
ดูการเขียนโปรแกรมเชิงมุมมอง (โดยเฉพาะก่อนคำแนะนำ )
Sotirios Delimanolis

15
คุณมีวิธีการหลายวิธีในการใช้แผ่นฉนวนนี้หรือไม่? ก่อนที่คุณจะลงสู่เส้นทางของการแนะนำ AOP คุณอาจต้องการพิจารณาเพียงแค่มีรหัสซ้ำ ๆ บางครั้งการคัดลอกและวางเล็กน้อยเป็นวิธีที่ง่ายที่สุด
bhspencer

51
ฉันสงสัยว่าผู้ดูแลในอนาคตของคุณจะมีความสุขกับจานหม้อไอน้ำพิเศษมากกว่าต้องเรียนรู้กรอบ AOP
bhspencer

7
หากทุกวิธีของคลาสจะต้องทำสิ่งเดียวกันในรหัสบรรทัดแรกของพวกเขาแล้วเรามีการออกแบบที่ไม่ดี
Tulains Córdova

7
@ user1598390: คำถามไม่ได้อยู่นอกหัวข้อที่นี่และไม่มีอะไรเกี่ยวกับขอบเขตของโปรแกรมเมอร์ที่ทำให้คำถามมีความหมายโดยเฉพาะ
Robert Harvey

คำตอบ:


90

ฉันไม่รู้เกี่ยวกับความสง่างาม แต่นี่คือการใช้งานโดยใช้ Java ในตัวjava.lang.reflect.Proxyซึ่งบังคับให้การเรียกใช้เมธอดทั้งหมดFooเริ่มต้นโดยการตรวจสอบenabledสถานะ

main วิธี:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

Foo อินเตอร์เฟซ:

public interface Foo {
    boolean getEnabled();
    void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory ระดับ:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {
        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class &&
                !method.getName().equals("getEnabled") &&
                !method.getName().equals("setEnabled")) {

                if (!this.fooImpl.getEnabled()) {
                    return null;
                }
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

ตามที่คนอื่น ๆ ได้ชี้ให้เห็นมันดูเหมือนจะเกินความจำเป็นสำหรับสิ่งที่คุณต้องการหากคุณมีวิธีการเพียงไม่กี่อย่างที่ต้องกังวล

ที่กล่าวว่ามีประโยชน์อย่างแน่นอน:

  • การแยกความกังวลบางอย่างเกิดขึ้นได้เนื่องจากFooการใช้วิธีการไม่ต้องกังวลเกี่ยวกับการenabledตรวจสอบข้ามความกังวล แต่รหัสของวิธีการนั้นต้องกังวลว่าจุดประสงค์หลักของวิธีการนั้นคืออะไรไม่มีอะไรเพิ่มเติม
  • ไม่มีวิธีใดสำหรับนักพัฒนาผู้บริสุทธิ์ที่จะเพิ่มวิธีการใหม่ในFooชั้นเรียนและ "ลืม" ผิดพลาดเพื่อเพิ่มการenabledตรวจสอบ enabledพฤติกรรมการตรวจสอบคือการสืบทอดโดยอัตโนมัติด้วยวิธีการใด ๆ เพิ่มเข้ามาใหม่
  • หากคุณต้องการเพิ่มข้อกังวลข้ามข้อกังวลอื่น ๆ หรือหากคุณต้องการปรับปรุงการenabledตรวจสอบมันเป็นเรื่องง่ายมากที่จะทำอย่างปลอดภัยและในที่เดียว
  • เป็นเรื่องที่ดีที่คุณจะได้รับพฤติกรรมแบบ AOP นี้ด้วยฟังก์ชั่นจาวาในตัว คุณไม่ได้ถูกบังคับให้ต้องรวมกรอบอื่น ๆ เช่นSpringแม้ว่ามันจะเป็นตัวเลือกที่ดีเช่นกัน

เพื่อความยุติธรรมข้อเสียบางประการคือ:

  • รหัสการนำไปใช้บางอย่างที่จัดการกับการเรียกใช้พร็อกซีนั้นน่าเกลียด บางคนก็บอกว่ามีชั้นในเพื่อป้องกันการ instantiation ของFooImplคลาสนั้นน่าเกลียด
  • หากคุณต้องการเพิ่มวิธีการใหม่ Fooคุณต้องทำการเปลี่ยนแปลงใน 2 จุด: คลาสการใช้งานและอินเทอร์เฟซ ไม่ใช่เรื่องใหญ่ แต่ก็ยังทำงานได้อีกเล็กน้อย
  • การเรียกใช้พร็อกซีไม่ฟรี มีค่าใช้จ่ายประสิทธิภาพบางอย่าง สำหรับการใช้งานทั่วไปแม้ว่าจะไม่เห็นได้ชัด ดูที่นี่สำหรับข้อมูลเพิ่มเติม

แก้ไข:

ความคิดเห็นของ Fabian Streitel ทำให้ฉันคิดเกี่ยวกับการรบกวน 2 อย่างด้วยวิธีแก้ไขปัญหาข้างต้นของฉันซึ่งฉันจะยอมรับว่าฉันไม่มีความสุขกับตัวเอง:

  1. ตัวจัดการคำร้องขอใช้สตริงมายากลเพื่อข้าม "enabled-check" บนวิธี "getEnabled" และ "setEnabled" สิ่งนี้สามารถแตกหักได้ง่ายหากชื่อเมธอดถูก refactored
  2. หากมีกรณีที่ต้องเพิ่มวิธีการใหม่ที่ไม่ควรสืบทอดพฤติกรรม "เปิดใช้งานตรวจสอบ" แล้วมันอาจเป็นเรื่องง่ายสำหรับนักพัฒนาที่จะทำสิ่งนี้ผิดและอย่างน้อยที่สุดก็หมายถึงการเพิ่มเวทมนตร์มากขึ้น เงื่อนไข

ในการแก้ไขจุด # 1 และอย่างน้อยก็ช่วยให้ปัญหาง่ายขึ้นด้วยจุด # 2 ฉันจะสร้างคำอธิบายประกอบBypassCheck(หรือบางอย่างที่คล้ายกัน) ที่ฉันสามารถใช้เพื่อทำเครื่องหมายวิธีในFooอินเทอร์เฟซที่ฉันไม่ต้องการทำ " เปิดใช้งานการตรวจสอบ ". ด้วยวิธีนี้ฉันไม่ต้องการสายเวทเลยและมันจะง่ายขึ้นมากสำหรับนักพัฒนาในการเพิ่มวิธีการใหม่ในกรณีพิเศษนี้อย่างถูกต้อง

เมื่อใช้โซลูชันคำอธิบายประกอบโค้ดจะมีลักษณะดังนี้:

main วิธี:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

BypassCheck คำอธิบายประกอบ:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foo อินเตอร์เฟซ:

public interface Foo {
    @BypassCheck boolean getEnabled();
    @BypassCheck void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory ระดับ:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {

        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class
                    && !method.isAnnotationPresent(BypassCheck.class) // no magic strings
                    && !this.fooImpl.getEnabled()) {

                return null;
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

11
ฉันเข้าใจว่านี่เป็นคำตอบที่ฉลาด แต่คุณจะใช้สิ่งนี้จริงหรือ
bhspencer

1
เป็นวิธีแก้ปัญหาที่ดีคุณใช้รูปแบบพร็อกซีแบบไดนามิกในการตกแต่งวัตถุที่มีพฤติกรรมทั่วไปนี้ที่พบในตอนเริ่มต้นของแต่ละวิธี
Victor

11
@ bhspencer: คำถามที่ถูกกฎหมายมาก จริง ๆ แล้วฉันใช้มันหลายครั้งในการจัดการข้อยกเว้นการบันทึกการจัดการธุรกรรม ฯลฯ ฉันจะยอมรับว่าสำหรับชั้นเรียนขนาดเล็กดูเหมือนว่า overkill และอาจเป็นไปได้ แต่ถ้าฉันคาดหวังให้ชั้นเรียนมีความซับซ้อนเพิ่มมากขึ้นและต้องการให้มีพฤติกรรมที่สอดคล้องกันในทุกวิธีที่ฉันทำฉันไม่สนใจวิธีนี้
sstan

1
เพื่อไม่ให้เป็นส่วนหนึ่งของ 97% ของความชั่วร้ายที่นี่ แต่ความหมายของประสิทธิภาพของคลาสพร็อกซีนี้คืออะไร
corsiKa

5
@corsiKa: เป็นคำถามที่ดี ไม่ต้องสงสัยเลยว่าการใช้พรอกซีแบบไดนามิกช้ากว่าการเรียกเมธอดโดยตรง สำหรับการใช้งานทั่วไปแม้ว่าค่าใช้จ่ายในการปฏิบัติงานจะมองไม่เห็น เธรด SO ที่เกี่ยวข้องหากคุณสนใจ: ต้นทุนด้านประสิทธิภาพของ Java ไดนามิกพรอกซี
sstan

51

มีคำแนะนำที่ดีมากมาย .. สิ่งที่คุณสามารถทำได้เพื่อแก้ไขปัญหาของคุณคือคิดในรูปแบบของรัฐและนำไปใช้

ลองดูตัวอย่างโค้ดนี้ .. บางทีคุณอาจจะได้แนวคิด ในสถานการณ์สมมตินี้ดูเหมือนว่าคุณต้องการปรับเปลี่ยนวิธีการใช้งานทั้งหมดขึ้นอยู่กับสถานะภายในของวัตถุ โปรดจำไว้ว่าผลรวมของวิธีการในวัตถุนั้นรู้ว่าเป็นพฤติกรรม

public class Foo {

      private FooBehaviour currentBehaviour = new FooEnabledBehaviour (); // or disabled, or use a static factory method for getting the default behaviour

      public void bar() {
        currentBehaviour.bar();
      }
      public void baz() {
        currentBehaviour.baz();
      }
      public void bat() {
        currentBehaviour.bat();
      }

      public void setFooEnabled (boolean fooEnabled) { // when you set fooEnabel, you are changing at runtime what implementation will be called.
        if (fooEnabled) {
          currentBehaviour = new FooEnabledBehaviour ();
        } else {
          currentBehaviour = new FooDisabledBehaviour ();
        }
      }

      private interface FooBehaviour {
        public void bar();
        public void baz();
        public void bat();
      }

      // RENEMBER THAT instance method of inner classes can refer directly to instance members defined in its enclosing class
      private class FooEnabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is enabled
        }
        public void baz() {}
        public void bat() {}

      }

      private class FooDisabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is desibled
        }
        public void baz() {}
        public void bat() {}

      }
}

หวังว่าคุณจะชอบมัน!

PD: เป็นการดำเนินการตามรูปแบบของรัฐ (หรือที่เรียกว่ากลยุทธ์ขึ้นอยู่กับบริบท .. แต่หลักการก็เหมือนกัน)


1
OP ต้องการไม่ต้องทำซ้ำบรรทัดรหัสเดียวกันที่จุดเริ่มต้นของทุกวิธีและโซลูชันของคุณเกี่ยวข้องกับการทำซ้ำบรรทัดรหัสเดียวกันที่จุดเริ่มต้นของทุกวิธี
Tulains Córdova

2
@ user1598390 ไม่จำเป็นต้องทำการจำลองใหม่ภายใน FooEnabledBehaviour คุณกำลังสมมติว่าไคลเอนต์ของวัตถุนี้ได้ตั้งค่า fooEnabled เป็นจริงดังนั้นจึงไม่จำเป็นต้องทำการโกง เช่นเดียวกันกับคลาส FooDisabledBehaviour ตรวจสอบอีกครั้งรหัสในมัน
Victor

2
ขอบคุณ @ bayou.io รอจนกว่าคำตอบ OP ฉันคิดว่าชุมชนกำลังทำงานหนักที่นี่มีเคล็ดลับดี ๆ มากมายที่นี่!
Victor

2
เห็นด้วยกับ @dyesdyes ฉันนึกไม่ออกว่าจะทำอะไรเพื่ออะไรนอกจากชั้นเรียนที่ไม่สำคัญ มันเป็นปัญหามากเกินไปเมื่อพิจารณาว่าbar()ในFooEnabledBehaviorและbar()ในFooDisabledBehaviorอาจแบ่งปันรหัสเดียวกันจำนวนมากโดยอาจเป็นแม้แต่บรรทัดเดียวที่แตกต่างกันระหว่างทั้งสอง คุณสามารถทำได้ง่ายมากโดยเฉพาะอย่างยิ่งหากรหัสนี้ต้องได้รับการดูแลโดยผู้พัฒนาระบบรอง (เช่นตัวฉันเอง) ลงเอยด้วยเรื่องไร้สาระขนาดใหญ่ที่ไม่สามารถต้านทานได้และไม่สามารถทดสอบได้ ที่สามารถเกิดขึ้นได้กับรหัสใด ๆ แต่ดูเหมือนจะง่ายมากที่จะพลาด +1 แม้ว่าเนื่องจากคำแนะนำที่ดี
Chris Cirefice

1
อืมมมม ... ฉันไม่ใช่พวก .. แต่ก่อนอื่นขอบคุณสำหรับความคิดเห็น สำหรับฉันขนาดของรหัสไม่ใช่ปัญหาตราบใดที่มันเป็น "clean" และ "readable" ในความโปรดปรานของฉันฉันควรยืนยันว่าฉันไม่ได้ใช้คลาสภายนอกใด ๆ .. ที่ควรทำให้สิ่งต่างๆเข้าถึงได้มากขึ้น และถ้ามีพฤติกรรมบางอย่างร่วมกันลองสรุปใน CommonBehaviourClass และมอบหมายให้ตรงจุดที่มันต้องการ ในหนังสือเล่มขาด (ไม่ใช่หนังสือที่ชื่นชอบสำหรับการเรียนรู้ แต่มีสูตรดี) คุณพบว่าตัวอย่างนี้en.wikipedia.org/wiki/... เป็นสิ่งเดียวกับที่ฉันทำที่นี่มากหรือน้อย
วิกเตอร์

14

ใช่ แต่มันเป็นงานเล็กน้อยดังนั้นมันขึ้นอยู่กับความสำคัญของคุณ

คุณสามารถกำหนดคลาสเป็นอินเทอร์เฟซเขียนการใช้งานของผู้รับมอบสิทธิ์และจากนั้นใช้java.lang.reflect.Proxyเพื่อใช้อินเทอร์เฟซด้วยวิธีการที่ทำในส่วนที่ใช้ร่วมกันแล้วเรียกเงื่อนไขผู้รับมอบสิทธิ์

interface Foo {
    public void bar();
    public void baz();
    public void bat();
}

class FooImpl implements Foo {
    public void bar() {
      //... <-- your logic represented by this notation above
    }

    public void baz() {
      //... <-- your logic represented by this notation above
    }

    // and so forth
}

Foo underlying = new FooImpl();
InvocationHandler handler = new MyInvocationHandler(underlying);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
     new Class[] { Foo.class },
     handler);

คุณMyInvocationHandlerสามารถมีลักษณะเช่นนี้ (การจัดการข้อผิดพลาดและการยกเว้นระดับนั่งร้านสมมติว่าfooIsEnabledมีการกำหนดให้เข้าถึงได้):

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!fooIsEnabled) return null;
    return method.invoke(underlying, args);
}

มันไม่ได้สวยอย่างไม่น่าเชื่อ แต่ต่างจากนักวิจารณ์หลายคนฉันทำเพราะฉันคิดว่าการทำซ้ำเป็นความเสี่ยงที่สำคัญกว่าความหนาแน่นแบบนี้และคุณจะสามารถสร้าง "ความรู้สึก" ในชั้นเรียนจริงของคุณด้วยเสื้อคลุมที่ไม่น่าเชื่อถือนี้ มากในท้องถิ่นในเพียงไม่กี่บรรทัดของรหัส

ดูเอกสารประกอบ Javaสำหรับรายละเอียดเกี่ยวกับคลาสพร็อกซีแบบไดนามิก


14

คำถามนี้เกี่ยวข้องกับการเขียนโปรแกรมเชิงกว้าง AspectJ เป็นส่วนเสริมของ AOP ของ Java และคุณอาจลองดูว่าจะได้รับ ispiration หรือไม่

เท่าที่ฉันรู้ไม่มีการสนับสนุนโดยตรงสำหรับ AOP ใน Java มีรูปแบบ GOF บางรูปแบบที่เกี่ยวข้องกับมันเช่นวิธีแม่แบบและกลยุทธ์แต่มันจะไม่ช่วยคุณประหยัดบรรทัดของโค้ด

ใน Java และภาษาอื่น ๆ ส่วนใหญ่คุณสามารถกำหนดตรรกะการเกิดซ้ำที่คุณต้องการในฟังก์ชั่นและนำวิธีการเข้ารหัสที่เรียกว่าวินัยซึ่งคุณเรียกมันในเวลาที่เหมาะสม

public void checkBalance() {
    checkSomePrecondition();
    ...
    checkSomePostcondition();
}

อย่างไรก็ตามสิ่งนี้จะไม่เหมาะกับกรณีของคุณเพราะคุณต้องการให้รหัสที่แยกออกจากกันสามารถกลับมาcheckBalanceได้ ในภาษาที่รองรับมาโคร (เช่น C / C ++) คุณสามารถกำหนดcheckSomePreconditionและcheckSomePostconditionเป็นมาโครและพวกมันจะถูกแทนที่ด้วย preprocessor ก่อนคอมไพเลอร์จะถูกเรียกใช้:

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Java ไม่มีสิ่งนี้อยู่นอกกรอบ สิ่งนี้อาจทำให้ใครบางคนขุ่นเคือง แต่ฉันใช้การสร้างรหัสอัตโนมัติและเครื่องมือเทมเพลตเพื่อทำภารกิจการเข้ารหัสซ้ำโดยอัตโนมัติในอดีต หากคุณประมวลผลไฟล์ Java ของคุณก่อนรวบรวมไฟล์ด้วยตัวประมวลผลล่วงหน้าที่เหมาะสมเช่น Jinja2 คุณสามารถทำสิ่งที่คล้ายกับสิ่งที่เป็นไปได้ใน C

แนวทางจาวาบริสุทธิ์

หากคุณกำลังมองหาโซลูชัน Java ที่แท้จริงสิ่งที่คุณอาจพบอาจไม่ชัดเจน แต่ก็ยังสามารถแยกส่วนที่พบบ่อยของโปรแกรมของคุณและหลีกเลี่ยงการทำซ้ำรหัสและข้อบกพร่อง คุณสามารถทำอะไรเช่นนี้ (มันเป็นรูปแบบกลยุทธ์ -แรงบันดาลใจบางอย่าง) โปรดทราบว่าใน C # และ Java 8 และในภาษาอื่น ๆ ที่ฟังก์ชั่นการจัดการง่ายขึ้นเล็กน้อยวิธีการนี้อาจดูดี

public interface Code {
    void execute();
}

...

public class Foo {
  private bool fooIsEnabled;

  private void protect(Code c) {
      if (!fooIsEnabled) return;
      c.execute();
  }

  public void bar() {
    protect(new Code {
      public void execute() {
        System.out.println("bar");
      }
    });
  }

  public void baz() {
    protect(new Code {
      public void execute() {
        System.out.println("baz");
      }
    });
  }

  public void bat() {
    protect(new Code {
      public void execute() {
        System.out.println("bat");
      }
    });
  }
}

ชนิดของสถานการณ์โลกแห่งความจริง

คุณกำลังพัฒนาคลาสเพื่อส่งเฟรมข้อมูลไปยังหุ่นยนต์อุตสาหกรรม หุ่นยนต์ต้องใช้เวลาในการทำคำสั่งให้สมบูรณ์ เมื่อคำสั่งเสร็จสิ้นจะส่งเฟรมควบคุมกลับมาให้คุณ หุ่นยนต์อาจได้รับความเสียหายหากได้รับคำสั่งใหม่ในขณะที่ก่อนหน้านี้ยังคงดำเนินการอยู่ โปรแกรมของคุณใช้DataLinkคลาสเพื่อส่งและรับเฟรมเข้าและออกจากโรบอต คุณต้องปกป้องการเข้าถึงDataLinkอินสแตนซ์

โทรติดต่อผู้ใช้ด้ายRobotController.left, right, upหรือdownเมื่อผู้ใช้คลิกปุ่ม แต่ยังเรียกร้องBaseController.tickในช่วงเวลาปกติในการสั่งซื้อเพื่อส่งต่อคำสั่ง reenable กับภาคเอกชนDataLinkเช่น

interface Code {
    void ready(DataLink dataLink);
}

class BaseController {
    private DataLink mDataLink;
    private boolean mReady = false;
    private Queue<Code> mEnqueued = new LinkedList<Code>();

    public BaseController(DataLink dl) {
        mDataLink = dl;
    }

    protected void protect(Code c) {
        if (mReady) {
            mReady = false;
            c.ready(mDataLink);
        }
        else {
            mEnqueue.add(c);
        }
    }

    public void tick() {
        byte[] frame = mDataLink.readWithTimeout(/* Not more than 50 ms */);

        if (frame != null && /* Check that it's an ACK frame */) {
          if (mEnqueued.isEmpty()) {
              mReady = true;
          }
          else {
              Code c = mEnqueued.remove();
              c.ready(mDataLink);
          }
        }
    }
}

class RobotController extends BaseController {
    public void left(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'left' by amount */);
        }});
    }

    public void right(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'right' by amount */);
        }});
    }

    public void up(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'up' by amount */);
        }});
    }

    public void down(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'down' by amount */);
        }});
    }
}

4
นี่ไม่ได้เป็นการเตะความสามารถลงบนถนน ซึ่งจะบอกว่าผู้ดูแลในอนาคตเคยจำได้ว่าถ้า (! fooIsEnabled) กลับมา; ที่จุดเริ่มต้นของทุกฟังก์ชั่นและตอนนี้พวกเขาต้องจำไว้ว่าต้องปกป้อง (รหัสใหม่ {... ที่จุดเริ่มต้นของทุกฟังก์ชั่นสิ่งนี้จะช่วยได้อย่างไร
bhspencer

ฉันชอบคุณวิเคราะห์และบริบทพื้นหลัง damix911 ... ฉันจะสร้างอินสแตนซ์รหัสใหม่ ณ เวลารวบรวม (ใช้สมาชิกคงที่ส่วนตัว) สมมติว่ารหัสจะไม่เปลี่ยนแปลงตลอดเวลาและเปลี่ยนชื่อการเปลี่ยนแปลงเพื่อ "execute If" ผ่านเป็นอาร์กิวเมนต์เงื่อนไข (เช่นคลาสเพรดิเคต) และรหัส แต่นั่นเป็นเรื่องส่วนตัวและรสนิยมที่มากกว่า
Victor

1
@ bhspencer ดูเหมือนจะค่อนข้างงุ่มง่ามใน Java และในกรณีส่วนใหญ่กลยุทธ์นี้เป็นรหัสที่ง่ายเกินไปสำหรับ overengineering มีโปรแกรมไม่มากที่จะได้ประโยชน์จากรูปแบบดังกล่าว สิ่งที่ยอดเยี่ยมคือเราสร้างสัญลักษณ์ใหม่protectซึ่งง่ายต่อการใช้ซ้ำและจัดทำเอกสาร หากคุณบอกผู้ดูแลในอนาคตว่ารหัสสำคัญควรได้รับการปกป้องprotectคุณกำลังบอกเขาว่าต้องทำอย่างไร หากกฎการป้องกันเปลี่ยนแปลงรหัสใหม่จะยังคงได้รับการป้องกัน นี่คือเหตุผลที่อยู่เบื้องหลังการกำหนดฟังก์ชั่น แต่ OP จำเป็นต้อง "ส่งคืนreturn" ซึ่งฟังก์ชั่นไม่สามารถทำได้
damix911

11

ฉันจะพิจารณาการปรับโครงสร้างใหม่ รูปแบบนี้ทำลายรูปแบบ DRY อย่างมาก (อย่าทำซ้ำตัวเอง) ฉันเชื่อว่าการแบ่งความรับผิดชอบระดับนี้ แต่สิ่งนี้ขึ้นอยู่กับการควบคุมรหัสของคุณ คำถามของคุณเปิดเผยมาก - คุณโทรไปที่Fooใด

ฉันคิดว่าคุณมีรหัสเช่น

foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

บางทีคุณควรเรียกมันว่าอย่างนี้:

if (fooEnabled) {
   foo.bat();
   foo.baz();
   ...
}

และรักษาความสะอาด ตัวอย่างเช่นการบันทึก:

this.logger.debug(createResourceExpensiveDump())

a logger ไม่ได้ถามตัวเองว่ามีการเปิดใช้งานการดีบักหรือไม่ มันแค่บันทึก

คลาสที่เรียกแทนต้องตรวจสอบสิ่งนี้:

if (this.logger.isDebugEnabled()) {
   this.logger.debug(createResourceExpensiveDump())
}

หากนี่เป็นห้องสมุดและคุณไม่สามารถควบคุมการโทรในชั้นนี้ได้ให้โยนรหัสเพื่อIllegalStateExceptionอธิบายว่าทำไมหากการโทรนี้ผิดกฎหมายและก่อให้เกิดปัญหา


6
มันง่ายกว่าและง่ายกว่าในสายตา แต่ถ้าเป้าหมายของ OP คือทำให้แน่ใจว่าเมื่อมีการเพิ่มวิธีการใหม่แล้วตรรกะที่เปิดใช้งานจะไม่ถูกข้ามไปดังนั้นการปรับโครงสร้างใหม่นี้จะไม่ทำให้ง่ายต่อการบังคับใช้
sstan

4
สำหรับตัวอย่างบันทึกของคุณฉันจะบอกว่าสิ่งนี้เกี่ยวข้องกับการทำซ้ำมากขึ้นทุกครั้งที่คุณต้องการเข้าสู่ระบบคุณต้องตรวจสอบว่าตัวบันทึกถูกเปิดใช้งาน ฉันมักจะเข้าสู่ระบบสายมากกว่าจำนวนของวิธีการในระดับใด ๆ ...
T. Kiley

4
สิ่งนี้จะทำลายความเป็นโมดูลเพราะตอนนี้ผู้โทรต้องรู้อะไรบางอย่างเกี่ยวกับ internals ของ foo (ในกรณีนี้ไม่ว่าจะเป็น fooEnabled) นี่เป็นตัวอย่างแบบคลาสสิกที่การปฏิบัติตามกฎการปฏิบัติที่ดีที่สุดจะไม่สามารถแก้ปัญหาได้เนื่องจากกฎขัดแย้งกัน (ฉันยังคงหวังว่าจะมีใครบางคนมาพร้อมกับ "ทำไมฉันไม่คิดอย่างนั้น?" ตอบว่า)
Ian Goldby

2
มันขึ้นอยู่กับบริบทอย่างมากว่ามันสมเหตุสมผลหรือไม่
เอียนโกลด์บาย

3
การบันทึกเป็นตัวอย่างที่ฉันไม่ต้องการให้ทำซ้ำในรหัสของฉัน ฉันแค่ต้องการเขียน LOG.debug (".... "); - และคนตัดไม้ควรตรวจสอบว่าฉันต้องการแก้จุดบกพร่องหรือไม่ - อีกตัวอย่างคือปิด / ล้างข้อมูล - ถ้าฉันใช้ AutoClosable ฉันไม่ต้องการข้อยกเว้นถ้ามันถูกปิดไปแล้วมันก็ไม่ควรทำอะไรเลย
Falco

6

IMHO ทางออกที่ดีที่สุดและมีประสิทธิภาพดีที่สุดสำหรับเรื่องนี้คือการมีการใช้งาน Foo มากกว่าหนึ่งรายการพร้อมกับวิธีการสร้างจากโรงงาน:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : new NopFoo();
  }
}

class NopFoo extends Foo {
  public void bar() {
    // Do nothing
  }
}

หรือการเปลี่ยนแปลง:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : NOP_FOO;
  }

  private static Foo NOP_FOO = new Foo() {
    public void bar() {
      // Do nothing
    }
  };
}

ในฐานะที่เป็น sstan ชี้ให้เห็นว่าดีกว่าจะใช้อินเทอร์เฟซ:

public interface Foo {
  void bar();

  static Foo getFoo() {
    return fooEnabled ? new FooImpl() : new NopFoo();
  }
}

class FooImpl implements Foo {
  FooImpl() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }
}

class NopFoo implements Foo {
  NopFoo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do nothing
  }
}

ปรับตัวให้เข้ากับสถานการณ์ที่เหลือของคุณ (คุณกำลังสร้าง Foo ใหม่ทุกครั้งหรือนำอินสแตนซ์เดียวกันมาใช้ซ้ำ)


1
นี่ไม่ใช่คำตอบของ Konrad ฉันชอบสิ่งนี้ แต่ฉันคิดว่ามันจะปลอดภัยกว่าหากคุณใช้อินเทอร์เฟซแทนคนอื่นได้แนะนำให้ใช้คำตอบของพวกเขาแทนการใช้การสืบทอดคลาส เหตุผลง่าย: มันง่ายเกินไปสำหรับนักพัฒนาที่จะเพิ่มวิธีการFooและลืมที่จะเพิ่มรุ่นที่ไม่มี op ของวิธีการในชั้นเรียนขยายจึงข้ามพฤติกรรมที่ต้องการ
sstan

2
@Sstan คุณพูดถูกนั่นจะดีกว่า ฉันทำแบบนี้เพื่อแก้ไขตัวอย่างดั้งเดิมของคริสตินาให้น้อยที่สุดเท่าที่จะทำได้เพื่อหลีกเลี่ยงสิ่งรบกวน แต่สิ่งนี้เกี่ยวข้อง ฉันจะเพิ่มข้อเสนอแนะของคุณในคำตอบของฉัน
Pepijn Schmitz

1
ฉันคิดว่าคุณพลาดจุด คุณตรวจสอบว่าisFooEnabledที่ instantiation Fooของ มันเช้าเกินไป. ในรหัสดั้งเดิมจะทำเมื่อมีการเรียกใช้เมธอด มูลค่าของisFooEnabledสามารถเปลี่ยนแปลงได้ในระหว่างนี้
Nicolas Barbulesco

1
@NicolasBarbulesco คุณไม่ทราบว่า fooIsEnabled อาจเป็นค่าคงที่ หรืออาจใช้วิธีที่ทำให้ค่าคงที่ได้อย่างมีประสิทธิภาพ หรืออาจตั้งไว้ในสถานที่ที่กำหนดไว้ไม่กี่แห่งเพื่อที่จะได้รับอินสแตนซ์ใหม่ของ Foo ทุกครั้ง หรืออาจเป็นที่ยอมรับได้ที่จะได้รับอินสแตนซ์ใหม่ของ Foo ทุกครั้งที่มีการใช้งาน คุณไม่รู้นั่นคือเหตุผลที่ฉันเขียนว่า: "ปรับให้เข้ากับสถานการณ์ส่วนที่เหลือของคุณ"
Pepijn Schmitz

@PepijnSchmitz - แน่นอนfooIsEnabled อาจคงที่ แต่ไม่มีอะไรบอกเราว่ามันคงที่ ดังนั้นฉันจึงพิจารณากรณีทั่วไป
Nicolas Barbulesco

5

ฉันมีวิธีอื่น: มี

interface Foo {
  public void bar();
  public void baz();
  public void bat();
}

class FooImpl implements Foo {
  public void bar() {
    //...
  }
  public void baz() {
    //...
  }
  public void bat() {
    //...
  }
}

class NullFoo implements Foo {
  static NullFoo DEFAULT = new NullFoo();
  public void bar() {}
  public void baz() {}
  public void bat() {}
}

}

จากนั้นคุณสามารถทำได้

(isFooEnabled ? foo : NullFoo.DEFAULT).bar();

บางทีคุณอาจจะยังสามารถแทนที่isFooEnabledด้วยFooตัวแปรซึ่งอาจถือที่จะใช้หรือFooImpl NullFoo.DEFAULTการโทรนั้นจะง่ายขึ้นอีกครั้ง:

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();

BTW นี้เรียกว่า "รูปแบบ Null"


วิธีการโดยรวมนั้นดี แต่การใช้นิพจน์(isFooEnabled ? foo : NullFoo.DEFAULT).bar();ดูเหมือนจะค่อนข้างงุ่มง่าม มีการนำไปใช้ครั้งที่สามที่มอบหมายให้กับหนึ่งในการนำไปใช้งานที่มีอยู่ แทนที่จะเปลี่ยนค่าของเขตข้อมูลisFooEnabledเป้าหมายของการมอบหมายอาจเปลี่ยนแปลงได้ สิ่งนี้จะช่วยลดจำนวนสาขาในรหัส
SpaceTrucker

1
แต่คุณกำลังเนรเทศการปรุงอาหารภายในห้องเรียนFooในรหัสการโทร! เราจะรู้ได้isFooEnabledอย่างไรว่า? นี่คือภายในFooสนามในชั้นเรียน
Nicolas Barbulesco

3

ในแนวทางการทำงานที่คล้ายกันกับคำตอบของ @ Colin ด้วยฟังก์ชั่นแลมบ์ดาของ Java 8เป็นไปได้ที่จะรวมคุณลักษณะที่มีเงื่อนไขเพื่อสลับเปิด / ปิดการใช้งานรหัสลงในวิธีการป้องกัน ( executeIfEnabled) ซึ่งยอมรับแลมบ์ดากระทำ ผ่านไป.

แม้ว่าในกรณีของคุณวิธีการนี้จะไม่บันทึกบรรทัดของโค้ดใด ๆ ด้วยการทำให้เป็นจริงขึ้นมาตอนนี้คุณมีตัวเลือกในการรวมศูนย์คุณลักษณะอื่น ๆ เพื่อสลับข้อกังวลรวมถึง AOP หรือข้อกังวลการดีบักเช่นการบันทึกการวินิจฉัยการทำโปรไฟล์

ข้อดีอย่างหนึ่งของการใช้ lambdas ที่นี่ก็คือการปิดสามารถใช้เพื่อหลีกเลี่ยงความจำเป็นที่จะต้องใช้executeIfEnabledวิธีการมากเกินไป

ตัวอย่างเช่น:

class Foo {
    private Boolean _fooIsEnabled;

    public Foo(Boolean isEnabled) {
        _fooIsEnabled = isEnabled;
    }

    private void executeIfEnabled(java.util.function.Consumer someAction) {
        // Conditional toggle short circuit
        if (!_fooIsEnabled) return;

        // Invoke action
        someAction.accept(null);
    }

    // Wrap the conditionally executed code in a lambda
    public void bar() {
        executeIfEnabled((x) -> {
            System.out.println("Bar invoked");
        });
    }

    // Demo with closure arguments and locals
    public void baz(int y) {
        executeIfEnabled((x) -> {
            System.out.printf("Baz invoked %d \n", y);
        });
    }

    public void bat() {
        int z = 5;
        executeIfEnabled((x) -> {
            System.out.printf("Bat invoked %d \n", z);
        });
    }

ด้วยการทดสอบ:

public static void main(String args[]){
    Foo enabledFoo = new Foo(true);
    enabledFoo.bar();
    enabledFoo.baz(33);
    enabledFoo.bat();

    Foo disabledFoo = new Foo(false);
    disabledFoo.bar();
    disabledFoo.baz(66);
    disabledFoo.bat();
}

ก็คล้ายกับแนวทางของ Damix โดยไม่จำเป็นต้องมีอินเตอร์เฟสและการใช้งานคลาสแบบไม่ระบุชื่อพร้อมการแทนที่เมธอด
StuartLC

2

ดังที่อธิบายไว้ในคำตอบอื่น ๆรูปแบบการออกแบบกลยุทธ์เป็นรูปแบบการออกแบบที่เหมาะสมที่จะปฏิบัติตามเพื่อลดความซับซ้อนของรหัสนี้ ฉันได้แสดงไว้ที่นี่โดยใช้วิธีการอุทธรณ์ผ่านการสะท้อนกลับ แต่มีกลไกจำนวนหนึ่งที่คุณสามารถใช้เพื่อให้ได้ผลเหมือนกัน

class Foo {

  public static void main(String[] args) {
      Foo foo = new Foo();
      foo.fooIsEnabled = false;
      foo.execute("bar");
      foo.fooIsEnabled = true;
      foo.execute("baz");
  }

  boolean fooIsEnabled;

  public void execute(String method) {
    if(!fooIsEnabled) {return;}
    try {
       this.getClass().getDeclaredMethod(method, (Class<?>[])null).invoke(this, (Object[])null);
    }
    catch(Exception e) {
       // best to handle each exception type separately
       e.printStackTrace();
    }
  }

  // Changed methods to private to reinforce usage of execute method
  private void bar() {
    System.out.println("bar called");
    // bar stuff here...
  }
  private void baz() {
    System.out.println("baz called");
    // baz stuff here...
  }
  private void bat() {
    System.out.println("bat called");
    // bat stuff here...
  }
}

Proxyมีการจัดการกับภาพสะท้อนเป็นบิตที่น่าอึดอัดใจถ้ามีอยู่แล้วชั้นเรียนที่ทำเช่นนี้สำหรับคุณเช่นที่กล่าวมาแล้ว
SpaceTrucker

คุณจะทำfoo.fooIsEnabled ...อย่างไร ก่อนหน้านี่คือเขตข้อมูลภายในของวัตถุเราไม่สามารถและไม่ต้องการที่จะเห็นมันนอก
Nicolas Barbulesco

2

ถ้าเพียงจาวาก็ยังดีกว่าที่ใช้งานได้ มันคิดว่าทางออก OOO ที่สุดคือการสร้างคลาสที่ล้อมฟังก์ชั่นเดียวจึงเรียกว่าเมื่อเปิดใช้งาน foo เท่านั้น

abstract class FunctionWrapper {
    Foo owner;

    public FunctionWrapper(Foo f){
        this.owner = f;
    }

    public final void call(){
        if (!owner.isEnabled()){
            return;
        }
        innerCall();
    }

    protected abstract void innerCall();
}

แล้วใช้bar, bazและชั้นเรียนที่ไม่ระบุชื่อที่ขยายbatFunctionWrapper

class Foo {
    public boolean fooIsEnabled;

    public boolean isEnabled(){
        return fooIsEnabled;
    }

    public final FunctionWrapper bar = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    public final FunctionWrapper baz = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    // you can pass in parms like so 
    public final FunctionWrapper bat = new FunctionWrapper(this){
        // some parms:
        int x,y;
        // a way to set them
        public void setParms(int x,int y){
            this.x=x;
            this.y=y;
        }

        @Override
        protected void innerCall() {
            // do whatever using x and y
        }
    };
}

ความคิดอื่น

ใช้วิธีแก้ปัญหาที่เป็นโมฆะของ glglglแต่ทำFooImplและNullFooเรียนภายใน (กับตัวสร้างส่วนตัว) ของชั้นล่าง:

class FooGateKeeper {

    public boolean enabled;

    private Foo myFooImpl;
    private Foo myNullFoo;

    public FooGateKeeper(){
        myFooImpl= new FooImpl();
        myNullFoo= new NullFoo();
    }

    public Foo getFoo(){
        if (enabled){
            return myFooImpl;
        }
        return myNullFoo;
    }  
}

(isFooEnabled ? foo : NullFoo.DEFAULT)ด้วยวิธีนี้คุณไม่ต้องกังวลเกี่ยวกับความทรงจำกับการใช้งาน


สมมติว่าคุณมี: Foo foo = new Foo()โทรหาbarคุณจะเขียนfoo.bar.call()
โคลิน

1

ดูเหมือนว่าคลาสจะไม่ทำอะไรเลยเมื่อไม่ได้เปิดใช้งาน Foo ดังนั้นทำไมไม่แสดงสิ่งนี้ในระดับที่สูงกว่าที่คุณสร้างหรือรับอินสแตนซ์ของ Foo

class FooFactory
{
 static public Foo getFoo()
 {
   return isFooEnabled ? new Foo() : null;
 }
}
 ...
 Foo foo = FooFactory.getFoo();
 if(foo!=null)
 {
   foo.bar();
   ....
 }     

ใช้งานได้เฉพาะในกรณีที่ isFooEnabled เป็นค่าคงที่ ในกรณีทั่วไปคุณสามารถสร้างคำอธิบายประกอบของคุณเอง


Konrad คุณสามารถพัฒนาคำอธิบายประกอบได้หรือไม่
Nicolas Barbulesco

รหัสต้นฉบับจะกำหนดว่าfooIsEnabledเมื่อใดจะเรียกใช้เมธอด คุณทำสิ่งนี้ก่อนที่จะFooเริ่ม มันเช้าเกินไป. ค่าสามารถเปลี่ยนแปลงได้ในระหว่างนี้
Nicolas Barbulesco

ฉันคิดว่าคุณพลาดจุด นิรนัยisFooEnabledเป็นฟิลด์ตัวอย่างของFooวัตถุ
Nicolas Barbulesco

1

ฉันไม่คุ้นเคยกับไวยากรณ์ Java สมมติว่าใน Java มีความแตกต่าง, คุณสมบัติคงที่ระดับนามธรรมและวิธีการ:

    public static void main(String[] args) {
    Foo.fooIsEnabled = true; // static property, not particular to a specific instance  

    Foo foo = new bar();
    foo.mainMethod();

    foo = new baz();
    foo.mainMethod();

    foo = new bat();
    foo.mainMethod();
}

    public abstract class Foo{
      static boolean fooIsEnabled;

      public void mainMethod()
      {
          if(!fooIsEnabled)
              return;

          baMethod();
      }     
      protected abstract void baMethod();
    }
    public class bar extends Foo {
        protected override baMethod()
        {
            // bar implementation
        }
    }
    public class bat extends Foo {
        protected override baMethod()
        {
            // bat implementation
        }
    }
    public class baz extends Foo {
        protected override baMethod()
        {
            // baz implementation
        }
    }

ใครบอกว่าการเปิดใช้งานเป็นคุณสมบัติคงที่ของคลาส?
Nicolas Barbulesco

อะไรคือสิ่งที่new bar()ควรจะหมายความว่าอย่างไร
Nicolas Barbulesco

ใน Java เราเขียนคลาสNameด้วยอักษรตัวใหญ่
Nicolas Barbulesco

สิ่งนี้ต้องการเปลี่ยนวิธีการเรียกรหัสมากเกินไป เราเรียกวิธีการตามปกติ: bar()หากคุณต้องการแก้ไขคุณจะถึงวาระ
Nicolas Barbulesco

1

โดยทั่วไปคุณมีการตั้งค่าสถานะว่าถ้าตั้งค่าการเรียกใช้ฟังก์ชันควรถูกข้ามไป ดังนั้นฉันคิดว่าวิธีแก้ปัญหาของฉันจะโง่ แต่นี่คือ

Foo foo = new Foo();

if (foo.isEnabled())
{
    foo.doSomething();
}

ต่อไปนี้เป็นการนำพร็อกซีแบบธรรมดามาใช้ในกรณีที่คุณต้องการรันโค้ดบางอย่างก่อนที่จะเรียกใช้ฟังก์ชัน

class Proxy<T>
{
    private T obj;
    private Method<T> proxy;

    Proxy(Method<T> proxy)
    {
        this.ojb = new T();
        this.proxy = proxy;
    }

    Proxy(T obj, Method<T> proxy)
    {
        this.obj = obj;
        this.proxy = proxy;
    }

    public T object ()
    {
        this.proxy(this.obj);
        return this.obj;
    }
}

class Test
{
    public static void func (Foo foo)
    {
        // ..
    }

    public static void main (String [] args)
    {
        Proxy<Foo> p = new Proxy(Test.func);

        // how to use
        p.object().doSomething();
    }
}

class Foo
{
    public void doSomething ()
    {
        // ..
    }
}

isEnabled()สำหรับบล็อกแรกของรหัสที่คุณจำเป็นต้องมีวิธีการมองเห็น การเปิดใช้งานนิรนัยคือการทำอาหารภายในของFooไม่ถูกเปิดเผย
Nicolas Barbulesco

รหัสการโทรไม่สามารถและไม่ต้องการทราบว่าเปิดใช้งานวัตถุหรือไม่
Nicolas Barbulesco

0

มีวิธีแก้ไขปัญหาอื่นโดยใช้ผู้รับมอบสิทธิ์ (ตัวชี้ไปยังฟังก์ชั่น) คุณสามารถมีวิธีการเฉพาะที่แรกทำการตรวจสอบแล้วโทรไปยังวิธีการที่เกี่ยวข้องตามฟังก์ชัน (พารามิเตอร์) ที่จะเรียก รหัส C #:

internal delegate void InvokeBaxxxDelegate();

class Test
{
    private bool fooIsEnabled;

    public Test(bool fooIsEnabled)
    {
        this.fooIsEnabled = fooIsEnabled;
    }

    public void Bar()
    {
        InvokeBaxxx(InvokeBar);
    }

    public void Baz()
    {
        InvokeBaxxx(InvokeBaz);
    }

    public void Bat()
    {
        InvokeBaxxx(InvokeBat);
    }

    private void InvokeBaxxx(InvokeBaxxxDelegate invoker)
    {
        if (!fooIsEnabled) return;
        invoker();
    }

    private void InvokeBar()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bar");
    }

    private void InvokeBaz()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Baz");
    }

    private void InvokeBat()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bat");
    }
}

2
ถูกต้องมันถูกทำเครื่องหมายว่า Java และนี่คือสาเหตุที่ฉันเน้นและเขียน "Code in C #" เนื่องจากฉันไม่รู้ Java เนื่องจากเป็นคำถามของรูปแบบการออกแบบดังนั้นภาษาจึงไม่สำคัญ
ehh

Oh! ฉันเข้าใจขอโทษด้วยที่พยายามช่วยและหาทางแก้ไข ขอบคุณ
เอ๊ะ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.