รูปแบบของผู้เข้าชมใช้ได้ในสถานการณ์นี้หรือไม่


9

เป้าหมายของงานของฉันคือการออกแบบระบบขนาดเล็กที่สามารถเรียกใช้งานที่เกิดขึ้นซ้ำตามกำหนดเวลา งานที่เกิดซ้ำเป็นเหมือน "ส่งอีเมลไปยังผู้ดูแลระบบทุกชั่วโมงตั้งแต่ 8:00 น. ถึง 17:00 น. วันจันทร์ถึงวันศุกร์"

ฉันได้เรียนฐานที่เรียกว่าRecurringTask

public abstract class RecurringTask{

    // I've already figured out this part
    public bool isOccuring(DateTime dateTime){
        // implementation
    }

    // run the task
    public abstract void Run(){

    }
}

และฉันมีหลายชั้นเรียนซึ่งได้รับมาจากRecurringTask หนึ่งในนั้นคือเรียกว่าSendEmailTask

public class SendEmailTask : RecurringTask{
    private Email email;

    public SendEmailTask(Email email){
        this.email = email;
    }

    public override void Run(){
        // need to send out email
    }
}

และฉันมีEmailServiceซึ่งสามารถช่วยให้ฉันส่งอีเมลได้

คลาสสุดท้ายคือRecurringTaskSchedulerรับผิดชอบการโหลดงานจากแคชหรือฐานข้อมูลและเรียกใช้งาน

public class RecurringTaskScheduler{

    public void RunTasks(){
        // Every minute, load all tasks from cache or database
        foreach(RecuringTask task : tasks){
            if(task.isOccuring(Datetime.UtcNow)){
                task.run();
            }
        }
    }
}

นี่คือปัญหาของฉัน: ฉันควรใส่EmailServiceที่ไหน?

ตัวเลือกที่ 1 : Inject EmailServiceลงในSendEmailTask

public class SendEmailTask : RecurringTask{
    private Email email;

    public EmailService EmailService{ get; set;}

    public SendEmailTask (Email email, EmailService emailService){
        this.email = email;
        this.EmailService = emailService;
    }

    public override void Run(){
        this.EmailService.send(this.email);
    }
}

มีการถกเถียงกันอยู่แล้วว่าเราควรอัดฉีดบริการเข้าสู่องค์กรหรือไม่และคนส่วนใหญ่เห็นด้วยว่ามันไม่ใช่วิธีปฏิบัติที่ดี ดูบทความนี้

ตัวเลือก 2:ถ้า ... อื่นในRecurringTaskScheduler

public class RecurringTaskScheduler{
    public EmailService EmailService{get;set;}

    public class RecurringTaskScheduler(EmailService emailService){
        this.EmailService = emailService;
    }

    public void RunTasks(){
        // load all tasks from cache or database
        foreach(RecuringTask task : tasks){
            if(task.isOccuring(Datetime.UtcNow)){
                if(task is SendEmailTask){
                    EmailService.send(task.email); // also need to make email public in SendEmailTask
                }
            }
        }
    }
}

ฉันได้รับแจ้งว่า ... ... อย่างอื่นและนักแสดงเหมือนด้านบนไม่ใช่ OO และจะทำให้เกิดปัญหามากขึ้น

Option3:เปลี่ยนลายเซ็นของRunและสร้างServiceBundle

public class ServiceBundle{
    public EmailService EmailService{get;set}
    public CleanDiskService CleanDiskService{get;set;}
    // and other services for other recurring tasks

}

ฉีดคลาสนี้ลงในRecurringTaskScheduler

public class RecurringTaskScheduler{
    public ServiceBundle ServiceBundle{get;set;}

    public class RecurringTaskScheduler(ServiceBundle serviceBundle){
        this.ServiceBundle = ServiceBundle;
    }

    public void RunTasks(){
        // load all tasks from cache or database
        foreach(RecuringTask task : tasks){
            if(task.isOccuring(Datetime.UtcNow)){
                task.run(serviceBundle);
            }
        }
    }
}

เรียกใช้วิธีการSendEmailTaskจะเป็น

public void Run(ServiceBundle serviceBundle){
    serviceBundle.EmailService.send(this.email);
}

ฉันไม่เห็นปัญหาใหญ่ ๆ กับวิธีการนี้

ตัวเลือก 4 : รูปแบบผู้เยี่ยมชม
ความคิดพื้นฐานคือการสร้างผู้เข้าชมซึ่งจะแค็ปซูลให้บริการเช่นเดียวกับServiceBundle

public class RunTaskVisitor : RecurringTaskVisitor{
    public EmailService EmailService{get;set;}
    public CleanDiskService CleanDiskService{get;set;}

    public void Visit(SendEmailTask task){
        EmailService.send(task.email);
    }

    public void Visit(ClearDiskTask task){
        //
    }
}

และเราจำเป็นต้องเปลี่ยนลายเซ็นของวิธีการเรียกใช้ เรียกใช้วิธีการSendEmailTaskคือ

public void Run(RecurringTaskVisitor visitor){
    visitor.visit(this);
}

มันเป็นเรื่องปกติของการดำเนินงานแบบผู้เข้าชมและผู้เข้าชมจะได้รับการฉีดเข้าไปในRecurringTaskScheduler

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

หรือคุณมีความคิดที่ดีเกี่ยวกับปัญหานี้? ขอบคุณ!

Update 5/22/2015 : ฉันคิดว่าคำตอบของ Andy สรุปความตั้งใจของฉันได้ดีจริงๆ หากคุณยังคงสับสนเกี่ยวกับปัญหาตัวเองฉันแนะนำให้อ่านโพสต์ของเขาก่อน

ฉันเพิ่งพบว่าปัญหาของฉันคล้ายกับปัญหาการส่งข้อความซึ่งนำไปสู่ ​​Option5

Option5 : แปลงปัญหาของฉันไปที่ข้อความส่ง
มีการแมปแบบหนึ่งต่อหนึ่งระหว่างปัญหาของฉันและปัญหาการส่งข้อความ :

Message Dispatcher : รับIMessageและส่งคลาสย่อยของIMessageไปยังตัวจัดการที่เกี่ยวข้อง → RecurringTaskScheduler

IMessage : อินเทอร์เฟซหรือคลาสนามธรรม → RecurringTask

MessageA : ขยายจากIMessageโดยมีข้อมูลเพิ่มเติมบางอย่าง → SendEmailTask

MessageB : subclass ของอีกIMessage → CleanDiskTask

MessageAHandler : เมื่อรับMessageAให้จัดการ→ SendEmailTaskHandler ซึ่งมี EmailService และจะส่งอีเมลเมื่อได้รับ SendEmailTask

MessageBHandler : เหมือนกับMessageAHandlerแต่จัดการMessageBแทน → CleanDiskTaskHandler

ส่วนที่ยากที่สุดคือวิธีส่งIMessage ประเภทต่างๆไปยังตัวจัดการที่แตกต่างกัน นี่คือประโยชน์เชื่อมโยง

ผมชอบวิธีการนี้จะไม่ก่อให้เกิดมลพิษนิติบุคคลของฉันกับการบริการและมันก็ไม่ได้มีพระเจ้าระดับ


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

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

ยุติธรรมพอฉันพยายามแนะนำให้ใช้รหัสซ้ำหากเป็นไปได้

1
SendEmailTaskดูเหมือนจะเป็นบริการมากกว่าเป็นนิติบุคคลสำหรับฉัน ฉันจะไปเลือก 1 โดยไม่ลังเล
Bart van Ingen Schenau

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

คำตอบ:


4

ฉันจะบอกว่าตัวเลือกที่ 1เป็นเส้นทางที่ดีที่สุดที่จะใช้ เหตุผลที่คุณไม่ควรยกเลิกมันก็คือว่าSendEmailTaskเป็นไม่ได้กิจการ เอนทิตีเป็นวัตถุที่เกี่ยวข้องกับการเก็บข้อมูลและสถานะ ชั้นเรียนของคุณมีน้อยมาก ในความเป็นจริงมันไม่ได้เป็นกิจการ แต่ก็ถือเป็นกิจการที่: Emailวัตถุที่คุณจะได้รับการจัดเก็บ นั่นหมายความว่าEmailไม่ควรรับบริการหรือมี#Sendวิธีการ EmailServiceแต่คุณควรจะมีการให้บริการที่ใช้หน่วยงานเช่นคุณ ดังนั้นคุณจึงได้ติดตามแนวคิดในการกำจัดบริการออกจากเอนทิตี

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

ทีนี้ลองมาดูกันว่าทำไมไม่ทำตัวเลือกอื่น ๆ (โดยเฉพาะเกี่ยวกับSOLID )

ตัวเลือก 2

คุณได้รับแจ้งอย่างถูกต้องว่าการแตกแขนงตามประเภทนั้นจะทำให้ปวดหัวมากขึ้นตามถนน ลองดูสาเหตุ ก่อนifs มักจะจัดกลุ่มและเติบโต วันนี้มันเป็นงานที่ต้องส่งอีเมลพรุ่งนี้ทุกประเภทต่างต้องการบริการที่แตกต่างหรือพฤติกรรมอื่น ๆ การจัดการifข้อความนั้นกลายเป็นฝันร้าย เนื่องจากเราแยกประเภทตามประเภท (และในกรณีนี้เป็นประเภทที่ชัดเจน ) เราจึงล้มล้างระบบประเภทที่สร้างเป็นภาษาของเรา

ตัวเลือกที่ 2 ไม่ใช่ Single Responsibility (SRP) เนื่องจากก่อนหน้านี้ reuseable RecurringTaskSchedulerตอนนี้ต้องรู้เกี่ยวกับงานประเภทต่าง ๆ เหล่านี้และเกี่ยวกับบริการและพฤติกรรมที่พวกเขาต้องการ ชั้นเรียนนั้นยากมากที่จะใช้ซ้ำ มันยังไม่ได้เปิด / ปิด (OCP) เนื่องจากต้องการทราบเกี่ยวกับงานประเภทนี้หรือบริการ (หรือบริการชนิดนี้หรืองานนั้น) การเปลี่ยนแปลงที่แตกต่างกันในงานหรือบริการอาจทำให้เกิดการเปลี่ยนแปลงที่นี่ เพิ่มงานใหม่หรือไม่? เพิ่มบริการใหม่หรือไม่ เปลี่ยนวิธีจัดการอีเมลไหม RecurringTaskSchedulerเปลี่ยนแปลง เนื่องจากประเภทของงานมีความสำคัญจึงไม่เป็นไปตามการทดแทน Liskov (LSP) มันไม่สามารถทำงานให้เสร็จได้ มันต้องขอประเภทและขึ้นอยู่กับประเภททำสิ่งนี้หรือทำเช่นนั้น RecurringTaskSchedulerแทนที่จะห่อหุ้มความแตกต่างในงานที่เราจะดึงทั้งหมดที่เข้ามาใน

ตัวเลือก 3

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

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

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

class MyCoolNewTask implements RecurringTask
{
    public bool isOccuring(DateTime dateTime) {
        return true; // It's always happenin' here!
    }

    public void Run(ServiceBundle bundle) {
        // yeah, some awesome stuff here
    }
}

ฉันใช้บริการอะไรบ้าง? บริการใดบ้างที่ต้องถูกเยาะเย้ยในการทดสอบ อะไรคือสิ่งที่ห้ามไม่ให้ฉันใช้บริการทุกอย่างในระบบเพียงเพราะอะไร

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

ServiceBundleไม่ได้จริงๆ SRP เพราะมันต้องการทราบเกี่ยวกับทุกบริการในระบบของคุณ มันไม่ใช่ OCP การเพิ่มบริการใหม่หมายถึงการเปลี่ยนแปลงในServiceBundleและการเปลี่ยนแปลงในServiceBundleอาจหมายถึงการเปลี่ยนแปลงที่แตกต่างกันไปที่งานอื่น ๆ ServiceBundleไม่แยกส่วนต่อประสาน (ISP) มีอินเทอร์เฟซที่แผ่กิ่งก้านสาขาของบริการเหล่านี้ทั้งหมดและเนื่องจากเป็นเพียงผู้ให้บริการสำหรับบริการเหล่านั้นเราจึงสามารถพิจารณาอินเทอร์เฟซเพื่อรวมอินเทอร์เฟซของบริการทั้งหมดที่มีให้ งานไม่เป็นไปตามการพึ่งพาผกผัน (DIP) ServiceBundleเพราะการอ้างอิงของพวกเขาก็ยุ่งเหยิงอยู่เบื้องหลัง สิ่งนี้ไม่ได้เป็นไปตามหลักการของความรู้น้อยที่สุด (กฎของ Demeter) เพราะสิ่งต่าง ๆ รู้เกี่ยวกับหลายสิ่งมากกว่าที่พวกเขาต้องทำ

ตัวเลือก 4

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

ส่วนที่ท้าทายยิ่งกว่านั้นคือเพราะพฤติกรรมที่แตกต่างทั้งหมดอยู่ในชั้นเรียนเดียวการเปลี่ยนpolymorphicly drags ตามพฤติกรรมอื่นทั้งหมด ตัวอย่างเช่นเราต้องการมีสองวิธีที่แตกต่างกันในการส่งอีเมล (ควรใช้เซิร์ฟเวอร์ที่แตกต่างกันหรือไม่?) เราจะทำอย่างไร เราสามารถสร้างIVisitorส่วนต่อประสานและนำรหัสนั้นมาใช้ซ้ำเช่น#Visit(ClearDiskTask)จากผู้เยี่ยมชมดั้งเดิมของเรา ถ้าเราหาวิธีใหม่ในการล้างดิสก์เราต้องนำไปใช้และทำซ้ำอีกครั้ง จากนั้นเราต้องการการเปลี่ยนแปลงทั้งสองแบบ นำไปใช้และทำซ้ำอีกครั้ง พฤติกรรมที่แตกต่างและแตกต่างกันสองอย่างนี้เชื่อมโยงกันอย่างแยกไม่ออก

บางทีแทนเราก็สามารถซับคลาสVisitor? คลาสย่อยที่มีพฤติกรรมอีเมลใหม่คลาสย่อยที่มีพฤติกรรมดิสก์ใหม่ ไม่มีการทำซ้ำ! ซับคลาสด้วยทั้งคู่? ตอนนี้จำเป็นต้องทำซ้ำอย่างใดอย่างหนึ่ง (หรือทั้งสองอย่างถ้านั่นคือการตั้งค่าของคุณ)

มาเปรียบเทียบกับตัวเลือกที่ 1: เราต้องการพฤติกรรมอีเมลใหม่ เราสามารถสร้างใหม่ที่ไม่พฤติกรรมใหม่ฉีดในการอ้างอิงและเพิ่มเข้าไปในคอลเลกชันของงานในส่วนRecurringTask RecurringTaskSchedulerเราไม่จำเป็นต้องพูดถึงเรื่องการล้างดิสก์เพราะความรับผิดชอบนั้นอยู่ที่อื่นทั้งหมด นอกจากนี้เรายังมีเครื่องมือ OO มากมายในการกำจัดของเรา เราสามารถตกแต่งงานนั้นด้วยการบันทึกตัวอย่างเช่น

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


การวิเคราะห์ Otion2,3,4 ของคุณนั้นยอดเยี่ยมมาก! มันช่วยฉันได้มากจริงๆ แต่สำหรับ Option1 ฉันขอยืนยันว่า * SendEmailTask ​​* เป็นเอนทิตี มันมี id มันมีรูปแบบการเกิดซ้ำและข้อมูลที่เป็นประโยชน์อื่น ๆ ซึ่งควรเก็บไว้ใน db ฉันคิดว่าแอนดี้สรุปความตั้งใจของฉันได้ดี บางทีชื่อเช่น * EMailTaskDefinitions * จะเหมาะสมกว่า ฉันไม่ต้องการที่จะก่อให้เกิดมลพิษเอนทิตีของฉันด้วยรหัสบริการ ร่าเริงกล่าวถึงปัญหาบางอย่างถ้าฉันฉีดบริการลงในเอนทิตี้ ฉันยังอัปเดตคำถามของฉันและรวมถึงตัวเลือก 5 ซึ่งฉันคิดว่าเป็นทางออกที่ดีที่สุดจนถึงตอนนี้
Sher10ck

@ Sher10ck หากคุณกำลังดึงการกำหนดค่าSendEmailTaskออกจากฐานข้อมูลของคุณการกำหนดค่านั้นควรเป็นคลาสการกำหนดค่าแยกต่างหากที่ควรแทรกลงในของคุณSendEmailTaskด้วย หากคุณกำลังสร้างข้อมูลจากSendEmailTaskคุณควรสร้างวัตถุที่ระลึกเพื่อจัดเก็บสถานะและใส่ลงในฐานข้อมูลของคุณ
cbojar

ฉันจำเป็นต้องดึงการกำหนดค่าจาก db ดังนั้นคุณจะแนะนำให้ฉีดทั้งEMailTaskDefinitionsและEmailServiceเข้าSendEmailTask? จากนั้นในRecurringTaskSchedulerฉันต้องฉีดบางอย่างเช่นมีความรับผิดชอบคือความหมายการโหลดและการบริการและการฉีดพวกเขาลงSendEmailTaskRepository SendEmailTaskแต่ฉันจะยืนยันตอนนี้ความต้องการที่จะรู้ว่าพื้นที่เก็บข้อมูลของงานทุกเช่นRecurringTaskScheduler CleanDiskTaskRepositoryและฉันต้องเปลี่ยนRecurringTaskSchedulerทุกครั้งที่ฉันมีงานใหม่ (เพื่อเพิ่มที่เก็บลงใน Scheduler)
Sher10ck

@ Sher10ck เท่านั้นควรจะตระหนักถึงแนวคิดของพื้นที่เก็บข้อมูลงานทั่วไปและการRecurringTaskScheduler RecurringTaskโดยการทำเช่นนี้มันสามารถขึ้นอยู่กับ abstractions RecurringTaskSchedulerที่เก็บงานสามารถฉีดลงในตัวสร้างของ จากนั้นที่เก็บข้อมูลที่แตกต่างกันจะต้องทราบว่าRecurringTaskSchedulerมีการสร้างอินสแตนซ์ไว้ที่ใด (หรืออาจซ่อนอยู่ในโรงงานและเรียกจากที่นั่น) เพราะมันขึ้นอยู่กับนามธรรมเท่านั้นRecurringTaskSchedulerไม่จำเป็นต้องเปลี่ยนกับแต่ละงานใหม่ นั่นคือสาระสำคัญของการผกผันของการพึ่งพา
cbojar

3

คุณเคยดูห้องสมุดที่มีอยู่เช่นสปริงควอตซ์หรือสปริง (ฉันไม่แน่ใจว่าสิ่งที่คุณต้องการมากที่สุด)?

สำหรับคำถามของคุณ:

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

โซลูชันที่เสนอของฉัน:

ฉันจะแยก running- และข้อมูลส่วนหนึ่งของงานที่จะมีเช่นและTaskDefinition TaskRunnerTaskDefinition มีการอ้างอิงถึง TaskRunner หรือโรงงานที่สร้างขึ้นมาหนึ่งอัน (เช่นถ้าจำเป็นต้องมีการตั้งค่าบางอย่างเช่น smtp-host) โรงงานเป็นโรงงานเฉพาะ - สามารถจัดการEMailTaskDefinitions และส่งคืนอินสแตนซ์ของEMailTaskRunners เท่านั้น วิธีนี้จะเป็น OO มากขึ้นและเปลี่ยนความปลอดภัย - ถ้าคุณแนะนำประเภทงานใหม่คุณต้องแนะนำโรงงานเฉพาะใหม่ (หรือนำมาใช้ใหม่) หากคุณไม่สามารถรวบรวมได้

ด้วยวิธีนี้คุณจะต้องพึ่งพา: เอนทิตีเลเยอร์ -> เลเยอร์บริการและกลับมาอีกครั้งเนื่องจาก Runner ต้องการข้อมูลที่เก็บไว้ในเอนทิตีและอาจต้องการอัปเดตเป็นสถานะในฐานข้อมูล

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

PS ฉันสมมติว่า Java ที่นี่ ฉันคิดว่ามันคล้ายกันใน. net ปัญหาหลักที่นี่คือการผูกสองครั้ง

เพื่อรูปแบบผู้เข้าชม

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

ในกรณีของคุณคุณจะเลือกกลยุทธ์เฉพาะภารกิจ (เช่นอีเมล) และนำไปใช้กับงานทั้งหมดของคุณซึ่งผิดเพราะไม่ใช่ทั้งหมดนั้นเป็นงานอีเมล

ป.ล. ฉันไม่ได้ทดสอบ แต่ฉันคิดว่าตัวเลือกที่ 4 ของคุณจะใช้งานไม่ได้เพราะมันมีผลผูกพันสองครั้งอีกครั้ง


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

1

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

เมื่อ X ส่งเมลไปที่ Y

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

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


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

@ Andy หนึ่ง Sher10ck อ้างอิงในคำถามของเขา และฉันไม่เห็นว่ามันจะสร้างการแต่งงานที่แน่นหนาได้อย่างไร รหัสที่เขียนไม่ดีใด ๆ สามารถแนะนำการมีเพศสัมพันธ์อย่างแน่นหนา
ร่าเริง

1

นั่นเป็นคำถามที่ยอดเยี่ยมและเป็นปัญหาที่น่าสนใจ ฉันเสนอให้คุณใช้การผสมผสานของChain of ResponsibilityและรูปแบบDouble Dispatch (ตัวอย่างรูปแบบที่นี่ )

ก่อนอื่นให้กำหนดลำดับชั้นของงาน ขอให้สังเกตว่าขณะนี้มีหลายrunวิธีในการใช้การจัดส่งสองครั้ง

public abstract class RecurringTask {

    public abstract boolean isOccuring(Date date);

    public boolean run(EmailService emailService) {
        return false;
    }

    public boolean run(ExecuteService executeService) {
        return false;
    }
}

public class SendEmailTask extends RecurringTask {

    private String email;

    public SendEmailTask(String email) {
        this.email = email;
    }

    @Override
    public boolean isOccuring(Date date) {
        return true;
    }

    @Override
    public boolean run(EmailService emailService) {
        emailService.runTask(this);
        return true;
    }

    public String getEmail() {
        return email;
    }
}

public class ExecuteTask extends RecurringTask {

    private String program;

    public ExecuteTask(String program) {
        this.program = program;
    }

    @Override
    public boolean isOccuring(Date date) {
        return true;
    }

    public String getName() {
        return program;
    }

    @Override
    public boolean run(ExecuteService executeService) {
        executeService.runTask(this);
        return true;
    }
}

ถัดไปให้กำหนดServiceลำดับชั้น เราจะใช้Services เพื่อสร้างเครือข่ายความรับผิดชอบ

public abstract class Service {

    private Service next;

    public Service(Service next) {
        this.next = next;
    }

    public void handleRecurringTask(RecurringTask req) {
        if (next != null) {
            next.handleRecurringTask(req);
        }
    }
}

public class ExecuteService extends Service {

    public ExecuteService(Service next) {
        super(next);
    }

    void runTask(ExecuteTask task) {
        System.out.println(String.format("%s running %s with content '%s'", this.getClass().getSimpleName(),
                task.getClass().getSimpleName(), task.getName()));
    }

    public void handleRecurringTask(RecurringTask req) {
        if (!req.run(this)) {
            super.handleRecurringTask(req);
        }
    }
}

public class EmailService extends Service {

    public EmailService(Service next) {
        super(next);
    }

    public void runTask(SendEmailTask task) {
        System.out.println(String.format("%s running %s with content '%s'", this.getClass().getSimpleName(),
                task.getClass().getSimpleName(), task.getEmail()));
    }

    public void handleRecurringTask(RecurringTask req) {
        if (!req.run(this)) {
            super.handleRecurringTask(req);
        }
    }
}

ชิ้นสุดท้ายคือการRecurringTaskSchedulerประสานกระบวนการโหลดและการรัน

public class RecurringTaskScheduler{

    private List<RecurringTask> tasks = new ArrayList<>();

    private Service chain;

    public RecurringTaskScheduler() {
        chain = new EmailService(new ExecuteService(null));
    }

    public void loadTasks() {
        tasks.add(new SendEmailTask("here comes the first email"));
        tasks.add(new SendEmailTask("here is the second email"));
        tasks.add(new ExecuteTask("/root/python"));
        tasks.add(new ExecuteTask("/bin/cat"));
        tasks.add(new SendEmailTask("here is the third email"));
        tasks.add(new ExecuteTask("/bin/grep"));
    }

    public void runTasks(){
        for (RecurringTask task : tasks) {
            if (task.isOccuring(new Date())) {
                chain.handleRecurringTask(task);
            }
        }
    }
}

ตอนนี้นี่คือตัวอย่างแอปพลิเคชันที่แสดงให้เห็นถึงระบบ

public class App {

    public static void main(String[] args) {
        RecurringTaskScheduler scheduler = new RecurringTaskScheduler();
        scheduler.loadTasks();
        scheduler.runTasks();
    }
}

การรันเอาต์พุตของแอ็พพลิเคชัน:

EmailService ทำงาน SendEmailTask ที่มีเนื้อหา 'นี่มาอีเมลแรก'
EmailService ทำงาน SendEmailTask ที่มีเนื้อหาที่นี่เป็นครั้งที่สองอีเมล '
ExecuteService ทำงาน ExecuteTask ที่มีเนื้อหา '/ ราก / หลาม'
ExecuteService ทำงาน ExecuteTask ที่มีเนื้อหา '/ bin / แมว'
EmailService ทำงาน SendEmailTask กับ เนื้อหา 'นี่คืออีเมลฉบับที่สาม'
ExecuteService ที่ใช้งาน ExecuteTask พร้อมเนื้อหา '/ bin / grep'


ฉันอาจมีภาระงานมากมาย เวลาที่ฉันเพิ่มใหม่ทุกงานผมต้องเปลี่ยนRecurringTaskและฉันยังต้องเปลี่ยนชั้นเรียนย่อยทั้งหมดของมันเพราะผมจำเป็นต้องเพิ่มฟังก์ชั่นใหม่ ๆ เช่น ประชาชนนามธรรมวิ่งบูล (OtherService otherService) ฉันคิดว่า Option4 รูปแบบผู้เยี่ยมชมซึ่งใช้การจัดส่งแบบคู่มีปัญหาเดียวกัน
Sher10ck

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