รูปแบบกลยุทธ์สามารถดำเนินการได้โดยไม่แตกสาขาหรือไม่


14

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

โรงงาน:

// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}

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

<strategies>
   <order type="NEW_ORDER">com.company.NewOrder</order>
   <order type="CANCELLATION">com.company.Cancellation</order>
   <order type="RETURN">com.company.Return</order>
</strategies>

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

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

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


Hmmmmm .... อาจเป็นไปได้ที่จะทำให้สิ่งต่าง ๆ ง่ายขึ้นด้วยเช่นสิ่งที่eval... อาจไม่ทำงานใน Java แต่อาจเป็นภาษาอื่น?
FrustratedWithFormsDesigner

1
@FrustratedWithFormsDesigner reflection เป็นคำวิเศษใน java
ratchet freak

2
คุณยังจะต้องมีเงื่อนไขเหล่านั้นอยู่ที่ไหนสักแห่ง ด้วยการผลักดันพวกเขาไปยังโรงงานคุณเพียงแค่ปฏิบัติตาม DRY เนื่องจากมิฉะนั้นคำสั่ง if หรือ switch อาจปรากฏในหลาย ๆ ที่
Daniel B

1
ข้อบกพร่องที่คุณพูดถึงมักจะเรียกว่าการละเมิดราคาเปิด
k3b

คำตอบที่ได้รับการยอมรับในคำถามที่เกี่ยวข้องแนะนำพจนานุกรม / แผนที่เป็นทางเลือกแทน if-else และ switch
gnat

คำตอบ:


16

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

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

บางคนใช้วิธีนี้เพื่อหมายความว่าสวิตช์ / เคสไม่ดี กรณีนี้ไม่ได้. แต่ควรจะอยู่ด้วยตัวเองถ้าเป็นไปได้


1
ฉันไม่เห็นว่ามันเป็นสิ่งเลวร้ายเช่นกัน อย่างไรก็ตามฉันมักจะมองหาวิธีลดจำนวนของรหัสที่จะคงไว้ซึ่งมันจะแตะซึ่งถามคำถาม
Michael K

สลับคำสั่ง (และหากบล็อกอื่น / ยาว) ไม่ดีและควรหลีกเลี่ยงหากเป็นไปได้ทั้งหมดเพื่อรักษารหัสของคุณไว้ ที่กล่าวว่า "หากเป็นไปได้" รับทราบว่ามีบางกรณีที่สวิตช์ต้องมีอยู่และในกรณีเหล่านั้นพยายามทำให้มันอยู่ในที่เดียวและที่ทำให้การบำรุงรักษาน้อยลง (มันง่ายมากที่จะ พลาด 1 ใน 5 สถานที่ในรหัสที่คุณต้องการในการซิงค์เมื่อคุณไม่ได้แยกอย่างถูกต้อง)
Shadow Man

10

รูปแบบกลยุทธ์สามารถดำเนินการได้โดยไม่แตกสาขาหรือไม่

ใช่โดยใช้ hashmap / พจนานุกรมที่การลงทะเบียนการใช้กลยุทธ์ทุกครั้งที่ วิธีการจากโรงงานจะกลายเป็นเหมือน

Class strategyType = allStrategies[orderType];
return runtime.create(strategyType);

การใช้กลยุทธ์ทุกครั้งจะต้องลงทะเบียนโรงงานด้วย orderType และข้อมูลบางอย่างเกี่ยวกับวิธีสร้างคลาส

factory.register(NEW_ORDER, NewOrder.class);

คุณสามารถใช้ตัวสร้างแบบคงที่สำหรับการลงทะเบียนหากภาษาของคุณรองรับสิ่งนี้

วิธีการลงทะเบียนไม่ได้ทำอะไรมากไปกว่าการเพิ่มค่าใหม่ให้กับ hashmap:

void register(OrderType orderType, Class class)
{
   allStrategies[orderType] = class;
}

[อัพเดต 2012-05-04]
โซลูชันนี้มีความซับซ้อนมากกว่า "โซลูชันสวิตช์" แบบเดิมซึ่งฉันต้องการใช้เวลาส่วนใหญ่

อย่างไรก็ตามในสภาพแวดล้อมที่กลยุทธ์เปลี่ยนแปลงบ่อยครั้ง (เช่นการคำนวณราคาขึ้นอยู่กับลูกค้า, เวลา, .... ) โซลูชัน hashmap นี้รวมกับ IoC-Container อาจเป็นทางออกที่ดี


3
ดังนั้นตอนนี้คุณมี Hashmap ของกลยุทธ์ทั้งหมดเพื่อหลีกเลี่ยงการสลับหรือกรณี? แถวของตัวfactory.register(NEW_ORDER, NewOrder.class);ทำความสะอาดหรือการละเมิด OCP น้อยกว่าแถวของcase NEW_ORDER: return new NewOrder();อย่างไร
สาธารณรัฐประชาธิปไตยประชาชนลาว

5
มันไม่ได้ทำความสะอาดเลย มันตอบโต้ได้ง่าย มันเสียสละหลักการ Keep-it-stimple-stupid เพื่อปรับปรุง open-closed-priciple เช่นเดียวกับการควบคุมการผกผันและการพึ่งพาการฉีด: สิ่งเหล่านี้ยากต่อการเข้าใจมากกว่าวิธีง่ายๆ คำถามก็คือ "การดำเนินการ ... โดยไม่ต้องแยกทางอย่างมีนัยสำคัญ" และไม่ "วิธีการสร้าง soltuion ง่ายขึ้น"
K3B

1
ฉันไม่เห็นว่าคุณหลีกเลี่ยงการแตกแขนงเช่นกัน คุณเพิ่งเปลี่ยนไวยากรณ์
สาธารณรัฐประชาธิปไตยประชาชนลาว

1
ไม่มีปัญหากับวิธีแก้ปัญหานี้ในภาษาที่ไม่โหลดคลาสจนกว่าพวกเขาจะต้องการ? รหัสคงที่จะไม่ทำงานจนกว่าจะโหลดคลาสและคลาสจะไม่สามารถโหลดได้เนื่องจากไม่มีคลาสอื่นที่อ้างถึง
วินไคลน์

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

2

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


ฉันไม่เห็นด้วยกับคำสั่งของคุณอีกครั้ง สามารถสลับกลยุทธ์ที่รันไทม์ ตัวอย่างเช่นหลังจากล้มเหลวในการประมวลผลคำขอโดยใช้ ProcessingStrategy มาตรฐาน VerboseProcessingStrategy สามารถเลือกได้และทำการประมวลผลอีกครั้ง
dmux

@dmux: แน่นอนแก้ไขคำตอบของฉันตาม
Doc Brown

1

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

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


1

ในการพูดคุยกับนักพัฒนาคนอื่น ๆ ที่ฉันทำงานอยู่ฉันพบโซลูชันที่น่าสนใจอีกตัว - เฉพาะสำหรับ Java แต่ฉันมั่นใจว่าแนวคิดนี้ทำงานได้ในภาษาอื่น สร้าง enum (หรือแผนที่มันไม่สำคัญมากนัก) โดยใช้การอ้างอิงชั้นเรียนและสร้างอินสแตนซ์โดยใช้การสะท้อนกลับ

enum FactoryType {
   Type1(Type1.class),
   Type2(Type2.class);

   private Class<? extends Type> clazz;

   private FactoryType(Class<? extends Type> clazz) {
      this.clazz = clazz;
   }

   public Class<? extends Type> getTypeClass() {
      return clazz;
   }
}

สิ่งนี้จะลดรหัสโรงงานลงอย่างมาก:

public Type create(FactoryType type) throws Exception {
   return type.getTypeClass().newInstance();
}

(โปรดเพิกเฉยต่อการจัดการข้อผิดพลาดที่ไม่ดี - รหัสตัวอย่าง :))

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


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

1

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

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

public interface IOrderStrategy
{
    OrderType OrderType { get; }
}

public class NewOrder : IOrderStrategy
{
    public OrderType OrderType { get; } = OrderType.NewOrder;
}

public class OrderFactory
{
    private IEnumerable<IOrderStrategy> _strategies;

    public OrderFactory(IEnumerable<IOrderStrategy> strategies) // Injected by IoC container
    {
        _strategies = strategies;
    }

    public IOrderStrategy Create(OrderType orderType)
    {
        IOrderStrategy strategy = _strategies.FirstOrDefault(s => s.OrderType == orderType);

        if (strategy == null)
            throw new ArgumentException("Invalid order type.", nameof(orderType));

        return strategy;
    }
}

1

ฉันคิดว่าควรมีการ จำกัด จำนวนสาขาที่ยอมรับได้

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

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

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