ถ้าอื่น ๆ - ตรรกะของรหัสซ้ำแล้วซ้ำอีก


15

เจ้านายของฉันให้โครงการฉันด้วยตรรกะเฉพาะ ฉันต้องพัฒนาเว็บเพจที่มีการนำทางผ่านหลาย ๆ กรณีจนกว่าเขา / เธอจะมาถึงผลิตภัณฑ์

นี่คือรูปแบบเส้นทางของการนำทางในไซต์:

เส้นทางของโครงการ

สำคัญ!

ในหน้าผลิตภัณฑ์เนวิเกเตอร์สามารถเลือกตัวกรองที่ต้องการ

  • ถ้า A เขา / เธอต้องผ่าน B (และแน่นอน C) หรือ C และถึงผลิตภัณฑ์
  • ถ้า B เขา / เธอจะต้องผ่าน C และเข้าถึงผลิตภัณฑ์
  • ถ้า C เขา / เธอเข้าถึงผลิตภัณฑ์โดยตรง

แน่นอนว่าถ้าฉันเริ่มต้นจาก AI กำลังติดตามเส้นทางที่ยาวที่สุดและเมื่อฉันไปถึงผลิตภัณฑ์ฉันมีตัวกรองที่ใช้งานอยู่ 3 ตัว

จนถึงตอนนี้ฉันพัฒนาโค้ดต่อไปนี้ซึ่งใช้งานได้ดี

if filter_A
  if filter_B
     filter_C()
     .. else ..
  else
     filter_C
    .. else ..
else
   if filter_B
      filter_C()
     .. else ..
   else
     filter_C()
     .. else ..

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

ฉันคิดถึงการแยกโค้ดทุกส่วนในฟังก์ชั่น แต่เป็นความคิดที่ดีในกรณีนี้หรือไม่?



แสดงให้เห็นว่าการควบคุมการไหลแผนภาพการควบคุมทั้งหมดจะผ่านแต่งบเงื่อนไขระบุว่าการควบคุมการไหลสามารถไปรอบ ๆfilter_C filter_Cเป็นfilter_Cตัวเลือกหรือไม่?
CurtisHx

@CurtisHx Filter C เป็นข้อบังคับ ใช่ขอโทษที่ฉันทำผิดพลาด
Kevin Cittadini

2
คำถามนี้จะเป็นผู้ไม่เชื่อเรื่องภาษาได้อย่างไร? วิธีการแก้ปัญหาเชิงสำนวนใน Java จะแตกต่างจากวิธีการแก้ปัญหาใน Haskell คุณยังไม่ได้ตัดสินใจภาษาสำหรับโครงการของคุณ?
200_success

คำตอบ:


20

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

วิธีที่ง่ายที่สุดในการใช้สิ่งที่คุณอธิบายไว้(แต่ให้แน่ใจว่าได้อ่านคำตอบครึ่งหลังด้านล่าง)นั้นคล้ายกับคำตอบอื่น ๆ แต่ฉันจะไม่ตรวจสอบบูลีนเลย FilterA, FilterB, FilterCฉันจะกำหนดอินเตอร์เฟซ: จากนั้นคุณสามารถมีสิ่งที่ชอบ (ฉันเป็นโปรแกรมเมอร์ Java ดังนั้นนี่จะเป็นไวยากรณ์ของ Java-esque):

class RequestFilters {
    FilterA filterA;
    FilterB filterB;
    FilterC filterC;
}

จากนั้นคุณสามารถมีลักษณะดังนี้ (ใช้รูปแบบ enum singletonจากEffective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

    public List<Item> applyFilter(List<Item> input) {
       return input;
    }
}

แต่ถ้าคุณต้องการกรองบางรายการคุณสามารถให้อินสแตนซ์ของการFilterAใช้งานที่ทำอะไรบางอย่างแทน วิธีการกรองของคุณจะง่ายมาก

List<Item> filterItems(List<Item> data, RequestFilters filters) {
    List<Item> returnedList = data;
    returnedList = filters.filterA.filter(data);
    returnedList = filters.filterB.filter(data);
    returnedList = filters.filterC.filter(data);
    return returnedList;
}

แต่ฉันเพิ่งเริ่มต้น

ฉันสงสัยว่าการapplyFilterโทรจะคล้ายกันจริงสำหรับตัวกรองทั้งสามประเภท หากเป็นกรณีนี้ฉันจะไม่ทำตามที่อธิบายไว้ข้างต้น คุณสามารถรับโค้ดที่สะอาดยิ่งขึ้นได้โดยมีเพียงอินเทอร์เฟซเดียวจากนั้นทำสิ่งนี้:

class ChainedFilter implements Filter {
     List<Filter> filterList;

     void addFilter(Filter filter) {
          filterList.add(filter);
     }

     List<Item> applyFilter(List<Item> input) {
         List<Item> returnedList = input;
         for(Filter f : filterList) {
             returnedList = f.applyFilter(returnedList);
         }
         return returnedList;
     }
}

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

นอกจากนี้คุณสามารถเพิ่มบางอย่างเช่นNoOpFilterด้านบนหรือคุณไม่สามารถเพิ่มตัวกรองเฉพาะลงในรายการได้ทุกสิ่งที่ง่ายสำหรับโค้ดของคุณ


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

หากคุณมีของคุณFilterเป็นPredicateแล้วคุณสามารถใช้งานได้โดยตรงในStreamAPI หลายภาษามีโครงสร้างการทำงานที่คล้ายกัน
Boris the Spider

3
@BoristheSpider นั่นเป็นเพียงว่าเขาใช้ Java 8; เขาไม่ได้พูดภาษาที่เขาใช้ ภาษาอื่นมีโครงสร้างเช่นนี้ แต่ฉันไม่ต้องการที่จะพูดถึงรสชาติที่แตกต่างของวิธีการทำเช่นนั้น
durron597

3
ความเข้าใจ - เป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าเป็นหนทางในการสำรวจว่า OP ต้องการให้การดำเนินการที่เป็นไปได้สะอาดที่สุดหรือไม่ คุณมี +1 ของฉันสำหรับคำตอบที่ยอดเยี่ยมอยู่แล้ว
Boris the Spider

3

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

ApplyFilterA();
ApplyFilterB();
ApplyFilterC();

ในตัวอย่างรหัสโพสต์มี 3 booleans filter_A, และfilter_B filter_Cอย่างไรก็ตามจากแผนภาพให้filter_Cเรียกใช้เสมอเพื่อให้สามารถเปลี่ยนเป็นแบบไม่มีเงื่อนไข

หมายเหตุ:ฉันสมมติว่าแผนภาพการควบคุมการไหลถูกต้อง มีความแตกต่างระหว่างโค้ดตัวอย่างที่โพสต์และไดอะแกรมการควบคุมโฟลว์

ส่วนควบคุมรหัสแยกต่างหากซึ่งตัวกรองจะทำงาน

ApplyFilters(bool filter_A, bool filter_B)
{
    listOfProducts tmp;
    if (filter_A)
        ApplyFilterA();
    if (filter_B)
        ApplyFilterB();
    ApplyFilterC();
}

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


+1 ดูเหมือนว่าจะง่ายกว่าและแตกต่างจากคำตอบที่ยอมรับ
วิงค์เบรซ

2

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

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

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

optional_filters_array = (a, b, c, d, e, f, g, h, etc)

for current_filter in optional_filters_array
  do_filter(current_filter)

do_required_filter()

หรือ:

optional_filters_array = (a, b, c, d, e, f, g, h, etc)
required_filter = last_filter


for current_filter in optional_filters_array
  do_filter(current_filter)

do_filter(required_filter)

ของซอร์สคุณต้องกำหนดรูทีนย่อยการประมวลผลตัวกรอง


1

ฉันจะสมมติว่า filterA, filterB และ filterC จริง ๆ แล้วแก้ไขรายการผลิตภัณฑ์ มิฉะนั้นถ้าพวกเขาเป็นเพียงแค่การตรวจสอบแล้ว filterA และ filterB สามารถละเว้นได้เนื่องจากเส้นทางทั้งหมดนำไปสู่ที่สุด filterC คำอธิบายความต้องการของคุณดูเหมือนจะบอกเป็นนัยว่าตัวกรองแต่ละตัวจะลดรายการผลิตภัณฑ์

ดังนั้นสมมติว่าตัวกรองลดรายการสินค้าจริง ๆ นี่คือรหัสหลอก ...

class filter
    func check(item) returns boolean
endclass

func applyFilter(filter, productList) returns list
    newList is list
    foreach item in productList
        if filter.check(item) then
            add item to newList
        endif
    endfor 
    return newList
endfunc



filterA, filterB, filterC = subclasses of filter for each condition, chosen by the user
products = list of items to be filtered

if filterA then
    products = applyFilter(filterA, products)
endif

if filterB then
    products = applyFilter(filterB, products)
endif

if filterC then
    products = applyFilter(filterC, products)
endif

# use products...

ในความต้องการของคุณ filterC ไม่ได้ใช้โดยอัตโนมัติ แต่ในแผนภาพมันเป็น หากข้อกำหนดคืออย่างน้อย filterC ควรใช้ไม่ว่าอะไรคุณจะเรียกใช้ ApplyFilter (filterC ผลิตภัณฑ์) โดยไม่ตรวจสอบถ้าเลือก filterC

filterC = instance of filter, always chosen

...

# if filterC then
products = applyFilter(filterC, products)
# endif

0

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

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

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