Apakah Pola Pengunjung valid dalam skenario ini?

9

Tujuan dari tugas saya adalah merancang sistem kecil yang dapat menjalankan tugas yang dijadwalkan berulang. Tugas berulang adalah sesuatu seperti "mengirim email ke administrator setiap jam dari jam 8:00 hingga 5:00 sore, Senin hingga Jumat".

Saya memiliki kelas dasar yang disebut 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(){

    }
}

Dan saya memiliki beberapa kelas yang diwarisi dari RecurringTask . Salah satunya disebut SendEmailTask .

public class SendEmailTask : RecurringTask{
    private Email email;

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

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

Dan saya memiliki Layanan Email yang dapat membantu saya mengirim email.

Kelas terakhir adalah RecurringTaskScheduler , yang bertanggung jawab untuk memuat tugas dari cache atau database dan menjalankan tugas.

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();
            }
        }
    }
}

Inilah masalah saya: di mana saya harus meletakkan EmailService ?

Option1 : Suntikkan Layanan Email ke 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);
    }
}

Sudah ada beberapa diskusi tentang apakah kita harus menyuntikkan layanan ke entitas, dan kebanyakan orang setuju itu bukan praktik yang baik. Lihat artikel ini .

Option2: If ... Else in 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
                }
            }
        }
    }
}

Saya telah diberitahu Jika ... Lain dan pemain seperti di atas bukan OO, dan akan membawa lebih banyak masalah.

Opsi3: Ubah tanda tangan Jalankan dan buat ServiceBundle .

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

}

Suntikkan kelas ini ke dalam 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);
            }
        }
    }
}

The Run metode SendEmailTask akan

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

Saya tidak melihat masalah besar dengan pendekatan ini.

Opsi4 : Pola pengunjung.
Ide dasarnya adalah membuat pengunjung yang akan merangkum layanan seperti 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){
        //
    }
}

Dan kita juga perlu mengubah tanda tangan dari metode Run . The Run metode SendEmailTask adalah

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

Ini adalah implementasi khas Pola Pengunjung, dan pengunjung akan disuntikkan ke RecurringTaskScheduler .

Singkatnya: Di antara empat pendekatan ini, mana yang terbaik untuk skenario saya? Dan apakah ada perbedaan besar antara Option3 dan Option4 untuk masalah ini?

Atau apakah Anda memiliki ide yang lebih baik tentang masalah ini? Terima kasih!

Pembaruan 5/22/2015 : Saya pikir jawaban Andy merangkum niat saya dengan sangat baik; jika Anda masih bingung tentang masalah itu sendiri, saya sarankan membaca postingnya terlebih dahulu.

Saya baru tahu bahwa masalah saya sangat mirip dengan masalah Pengiriman Pesan , yang mengarah ke Option5.

Option5 : Konversikan masalah saya ke Pengiriman Pesan .
Ada pemetaan satu-ke-satu antara masalah saya dan masalah Pengiriman Pesan :

Message Dispatcher : Terima IMessage dan kirim sub-kelas IMessage ke penangannya. → RecurringTaskScheduler

IMessage : Antarmuka atau kelas abstrak. → RecurringTask

MessageA : Meluas dari IMessage , memiliki beberapa informasi tambahan. → SendEmailTask

MessageB : Subclass lain dari IMessage . → CleanDiskTask

MessageAHandler : Saat menerima MessageA , tangani → SendEmailTaskHandler, yang berisi EmailService, dan akan mengirimkan email ketika menerima MessageEandTask

MessageBHandler : Sama seperti MessageAHandler , tetapi menangani MessageB sebagai gantinya. → CleanDiskTaskHandler

Bagian tersulit adalah cara mengirim berbagai jenis IMessage ke penangan yang berbeda. Berikut ini tautan yang bermanfaat .

Saya sangat menyukai pendekatan ini, tidak mencemari entitas saya dengan layanan, dan tidak memiliki kelas Tuhan .

Sher10ck
sumber
Anda belum menandai bahasa atau platform, tetapi saya sarankan melihat ke cron . Platform Anda mungkin memiliki perpustakaan yang berfungsi serupa (mis. Jcron yang sepertinya tidak berfungsi). Menjadwalkan pekerjaan dan tugas sebagian besar merupakan masalah yang sudah terpecahkan: sudahkah Anda mencari opsi lain sebelum menggulirkannya sendiri? Apakah ada alasan untuk tidak menggunakannya?
@Snowman Kita dapat beralih ke perpustakaan yang sudah matang nanti. Itu semua tergantung pada manajer saya. Alasan saya memposting pertanyaan ini adalah saya ingin menemukan cara untuk menyelesaikan masalah 'semacam ini'. Saya telah melihat masalah semacam ini lebih dari sekali, dan tidak dapat menemukan solusi yang elegan. Jadi saya bertanya-tanya apakah saya melakukan sesuatu yang salah.
Sher10ck
Cukup adil, saya selalu mencoba merekomendasikan penggunaan kembali kode jika memungkinkan.
1
SendEmailTasksepertinya lebih seperti layanan daripada entitas bagi saya. Saya akan pergi untuk opsi 1 tanpa dan ragu-ragu.
Bart van Ingen Schenau
3
Apa yang hilang (bagi saya) untuk Pengunjung adalah struktur kelas yang menjadi acceptpengunjung. Motivasi untuk Pengunjung adalah bahwa Anda memiliki banyak jenis kelas dalam beberapa agregat yang perlu dikunjungi, dan tidak nyaman untuk memodifikasi kode mereka untuk setiap fungsionalitas baru (operasi). Saya masih tidak melihat objek agregat itu, dan berpikir bahwa Pengunjung tidak tepat. Jika demikian, Anda harus mengedit pertanyaan Anda (yang mengacu pada pengunjung).
Fuhrmanator

Jawaban:

4

Saya akan mengatakan Opsi 1 adalah rute terbaik untuk ditempuh. Alasan Anda tidak boleh mengabaikannya adalah karena SendEmailTaskitu bukan entitas. Entitas adalah objek yang berkaitan dengan penyimpanan data dan status. Kelas Anda sangat sedikit. Sebenarnya, itu bukan entitas, tetapi ia memegang entitas: Emailobjek yang Anda simpan. Itu berarti bahwa Emailtidak boleh mengambil layanan, atau memiliki #Sendmetode. Sebagai gantinya, Anda harus memiliki layanan yang mengambil entitas, seperti Anda EmailService. Jadi, Anda sudah mengikuti gagasan menjaga layanan dari entitas.

Karena SendEmailTaskbukan entitas, maka sangat baik untuk menyuntikkan email dan layanan ke dalamnya, dan itu harus dilakukan melalui konstruktor. Dengan melakukan injeksi konstruktor, kita dapat memastikan bahwa SendEmailTaskselalu siap untuk melakukan pekerjaannya.

Sekarang mari kita lihat mengapa tidak melakukan opsi lain (khususnya berkenaan dengan SOLID ).

pilihan 2

Anda telah diberi tahu dengan benar bahwa bercabang pada tipe seperti itu akan membawa lebih banyak sakit kepala di jalan. Mari kita lihat alasannya. Pertama, ifcenderung mengelompok dan tumbuh. Hari ini, adalah tugas untuk mengirim email, besok, setiap jenis kelas yang berbeda membutuhkan layanan yang berbeda atau perilaku lainnya. Mengelola ifpernyataan itu menjadi mimpi buruk. Karena kita bercabang pada tipe (dan dalam hal ini tipe eksplisit ), kita menumbangkan sistem tipe yang dibangun ke dalam bahasa kita.

Opsi 2 bukanlah Tanggung Jawab Tunggal (SRP) karena yang sebelumnya dapat digunakan kembali RecurringTaskSchedulerharus mengetahui semua jenis tugas yang berbeda, dan tentang semua jenis layanan dan perilaku yang mungkin mereka butuhkan. Kelas itu jauh lebih sulit untuk digunakan kembali. Ini juga bukan Open / Closed (OCP). Karena perlu mengetahui tentang tugas semacam ini atau yang itu (atau layanan semacam ini atau yang itu), perubahan yang berbeda pada tugas atau layanan dapat memaksa perubahan di sini. Tambah tugas baru? Tambahkan layanan baru? Ubah cara email ditangani? Perubahan RecurringTaskScheduler. Karena jenis tugas penting, itu tidak mematuhi Substitusi Liskov (LSP). Itu tidak bisa hanya mendapatkan tugas dan dilakukan. Itu harus meminta jenis dan berdasarkan jenis melakukan ini atau melakukan itu. Daripada merangkum perbedaan ke dalam tugas, kami menarik semua itu ke dalam RecurringTaskScheduler.

Opsi 3

Opsi 3 memiliki beberapa masalah besar. Bahkan dalam artikel yang Anda tautkan , penulis tidak menyarankan hal ini:

  • Anda masih dapat menggunakan pencari lokasi statis ...
  • Saya menghindari pencari lokasi ketika saya bisa, terutama ketika pencari lokasi harus statis ...

Anda membuat pencari lokasi layanan dengan ServiceBundlekelas Anda . Dalam kasus ini, sepertinya tidak statis, tetapi masih memiliki banyak masalah yang melekat pada pencari layanan. Ketergantungan Anda sekarang disembunyikan di bawah ini ServiceBundle. Jika saya memberi Anda API tugas baru keren berikut:

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
    }
}

Apa layanan yang saya gunakan? Layanan apa yang perlu dipermainkan dalam ujian? Apa yang membuat saya berhenti menggunakan setiap layanan dalam sistem, hanya karena?

Jika saya ingin menggunakan sistem tugas Anda untuk menjalankan beberapa tugas, saya sekarang bergantung pada setiap layanan di sistem Anda, bahkan jika saya hanya menggunakan beberapa atau bahkan tidak sama sekali.

Ini ServiceBundletidak benar-benar SRP karena perlu tahu tentang setiap layanan di sistem Anda. Itu juga bukan OCP. Menambahkan layanan baru berarti perubahan ke ServiceBundle, dan perubahan ke ServiceBundledapat berarti perubahan yang berbeda untuk tugas di tempat lain. ServiceBundletidak Memisahkan Antarmuka (ISP) -nya. Ini memiliki antarmuka yang luas dari semua layanan ini, dan karena itu hanya penyedia untuk layanan tersebut, kami dapat mempertimbangkan antarmuka untuk mencakup antarmuka semua layanan yang disediakannya juga. Tugas tidak lagi mematuhi Dependency Inversion (DIP), karena ketergantungannya dikaburkan di belakang ServiceBundle. Ini juga tidak mematuhi Prinsip Least Knowledge (alias Hukum Demeter) karena hal-hal mengetahui lebih banyak hal daripada yang seharusnya.

Opsi 4

Sebelumnya, Anda memiliki banyak objek kecil yang dapat beroperasi secara mandiri. Opsi 4 mengambil semua objek ini dan menghancurkannya menjadi satu Visitorobjek. Objek ini bertindak sebagai objek dewa atas semua tugas Anda. Ini mengurangi RecurringTaskobjek Anda menjadi bayangan anemia yang hanya memanggil pengunjung. Semua perilaku berpindah ke Visitor. Perlu mengubah perilaku? Perlu menambahkan tugas baru? Perubahan Visitor.

Bagian yang lebih menantang adalah bahwa, karena semua perilaku yang berbeda semuanya dalam satu kelas, mengubah beberapa polimorfik menyeret semua perilaku lainnya. Misalnya, kami ingin memiliki dua cara berbeda untuk mengirim email (mungkin mereka harus menggunakan server yang berbeda?). Bagaimana kita melakukannya? Kami dapat membuat IVisitorantarmuka dan mengimplementasikannya, yang berpotensi menggandakan kode, seperti #Visit(ClearDiskTask)dari pengunjung asli kami. Kemudian jika kita menemukan cara baru untuk menghapus disk, kita harus mengimplementasikan dan menggandakan lagi. Lalu kami ingin kedua jenis perubahan. Terapkan dan duplikat lagi. Dua perilaku berbeda dan berbeda ini saling terkait erat.

Mungkin malah kita bisa subkelas saja Visitor? Subkelas dengan perilaku email baru, subkelas dengan perilaku disk baru. Tidak ada duplikasi sejauh ini! Subkelas dengan keduanya? Sekarang satu atau yang lain perlu digandakan (atau keduanya jika itu pilihan Anda).

Mari kita bandingkan dengan opsi 1: Kita memerlukan perilaku email baru. Kami dapat membuat yang baru RecurringTaskyang melakukan perilaku baru, menyuntikkan dependensinya, dan menambahkannya ke kumpulan tugas di RecurringTaskScheduler. Kami bahkan tidak perlu berbicara tentang membersihkan disk, karena tanggung jawab itu ada di tempat lain sepenuhnya. Kami juga masih memiliki beragam alat OO yang kami miliki. Kita bisa menghias tugas itu dengan logging, misalnya.

Opsi 1 akan memberi Anda paling sedikit rasa sakit, dan merupakan cara yang paling benar untuk menangani situasi ini.

cbojar
sumber
Analisis Anda pada Otion2,3,4 luar biasa! Ini sangat membantu saya. Tetapi untuk Option1, saya berpendapat bahwa * SendEmailTask ​​* adalah suatu entitas. Memiliki id, memiliki pola berulang, dan informasi berguna lainnya yang harus disimpan dalam db. Saya pikir Andy merangkum niat saya dengan baik. Mungkin nama seperti * EMailTaskDefinitions * lebih tepat. saya tidak ingin mencemari entitas saya dengan kode layanan saya. Euforia menyebutkan beberapa masalah jika saya menyuntikkan layanan ke entitas. Saya juga memperbarui pertanyaan saya dan memasukkan Option5, yang menurut saya merupakan solusi terbaik sejauh ini.
Sher10ck
@ Sher10ck Jika Anda menarik konfigurasi untuk Anda SendEmailTaskkeluar dari database, maka konfigurasi itu harus menjadi kelas konfigurasi terpisah yang juga harus disuntikkan ke Anda SendEmailTask. Jika Anda menghasilkan data dari Anda SendEmailTask, Anda harus membuat objek kenang-kenangan untuk menyimpan status dan memasukkannya ke dalam basis data Anda.
cbojar
Saya perlu menarik konfigurasi dari db, jadi apakah Anda menyarankan menyuntikkan keduanya EMailTaskDefinitionsdan EmailServiceke dalam SendEmailTask? Kemudian di RecurringTaskScheduler, saya perlu menyuntikkan sesuatu SendEmailTaskRepositoryyang tanggung jawabnya memuat definisi dan layanan dan menyuntikkannya SendEmailTask. Tapi saya berpendapat sekarang RecurringTaskSchedulerkebutuhan untuk mengetahui Repositori dari setiap tugas, seperti CleanDiskTaskRepository. Dan saya perlu mengubah RecurringTaskSchedulersetiap kali saya memiliki tugas baru (untuk menambahkan repositori ke Penjadwal).
Sher10ck
@ Sher10ck RecurringTaskSchedulerSeharusnya hanya mengetahui konsep repositori tugas umum dan a RecurringTask. Dengan melakukan ini, itu bisa bergantung pada abstraksi. Repositori tugas dapat disuntikkan ke dalam konstruktor RecurringTaskScheduler. Kemudian repositori yang berbeda hanya perlu diketahui di mana RecurringTaskSchedulerinstantiated (atau bisa disembunyikan di pabrik dan dipanggil dari sana). Karena itu hanya tergantung pada abstraksi, RecurringTaskSchedulertidak perlu berubah dengan setiap tugas baru. Itu adalah inti dari inversi ketergantungan.
cbojar
3

Apakah Anda sudah melihat perpustakaan yang ada misalnya pegas kuarsa atau pegas (saya tidak yakin apa yang paling sesuai dengan kebutuhan Anda)?

Untuk pertanyaan Anda:

Saya berasumsi masalahnya adalah, bahwa Anda ingin bertahan beberapa metadata untuk tugas dengan cara polimorfik, sehingga tugas email memiliki alamat email yang ditetapkan, tugas log tingkat log, dan sebagainya. Anda dapat menyimpan daftar orang-orang dalam memori atau dalam database Anda tetapi untuk memisahkan masalah Anda tidak ingin entitas tercemar dengan kode layanan.

Solusi yang saya usulkan:

Saya akan memisahkan menjalankan - dan data - bagian dari tugas, untuk memiliki eg TaskDefinitiondan a TaskRunner. TaskDefinition memiliki referensi ke TaskRunner atau pabrik yang membuatnya (misalnya jika diperlukan beberapa pengaturan seperti smtp-host). Pabrik adalah pabrik yang spesifik - hanya dapat menangani EMailTaskDefinitiondan hanya mengembalikan instance dari EMailTaskRunners. Dengan cara ini lebih OO dan berubah aman - jika Anda memperkenalkan jenis tugas baru Anda harus memperkenalkan pabrik spesifik baru (atau menggunakan kembali), jika tidak, Anda tidak dapat mengkompilasi.

Dengan cara ini Anda akan berakhir dengan dependensi: lapisan entitas -> lapisan layanan dan kembali lagi, karena Pelari memerlukan informasi yang disimpan dalam entitas dan mungkin ingin membuat pembaruan ke statusnya di DB.

Anda bisa mematahkan lingkaran dengan menggunakan sebuah pabrik generik, yang mengambil sebuah TaskDefinition dan mengembalikan tertentu TaskRunner, tapi itu akan memerlukan banyak ifs. Anda dapat menggunakan refleksi untuk menemukan pelari yang juga disebut sebagai definisi Anda, tetapi berhati-hatilah dengan pendekatan ini yang dapat menghabiskan beberapa kinerja, dan dapat menyebabkan kesalahan runtime.

PS Saya mengasumsikan Jawa di sini. Saya pikir ini mirip dengan .net. Masalah utama di sini adalah pengikatan rangkap.

Untuk pola pengunjung

Saya pikir itu lebih dimaksudkan untuk digunakan untuk bertukar algoritma untuk berbagai jenis objek data saat runtime, daripada untuk tujuan pengikatan rangkap murni. Misalnya jika Anda memiliki berbagai jenis asuransi dan berbagai penghitungan, misalnya karena negara yang berbeda memerlukannya. Kemudian Anda memilih metode perhitungan tertentu dan menerapkannya pada beberapa asuransi.

Dalam kasus Anda, Anda akan memilih strategi tugas tertentu (misalnya email) dan menerapkannya pada semua tugas Anda, yang salah karena tidak semuanya merupakan tugas email.

PS Saya tidak mengujinya, tapi saya pikir Opsi 4 Anda tidak akan berfungsi baik, karena itu mengikat ganda lagi.

Andy
sumber
Anda meringkaskan niat saya dengan sangat baik, terima kasih! Saya ingin memutus lingkaran. Karena membiarkan TaskDefiniton memegang referensi ke TaskRunner atau pabrik memiliki masalah yang sama dengan Option1. Saya memperlakukan pabrik atau TaskRunner sebagai layanan. Jika kebutuhan TaskDefinition memegang referensi kepada mereka, Anda harus menyuntikkan layanan ke TaskDefinition , atau menggunakan beberapa metode statis, yang saya coba hindari.
Sher10ck
1

Saya sepenuhnya tidak setuju dengan artikel itu. Layanan (secara konkret "API" mereka) adalah pihak penting dari Domain Bisnis dan dengan demikian akan ada dalam Model Domain. Dan tidak ada masalah dengan entitas dalam domain bisnis yang mereferensikan hal lain dalam domain bisnis yang sama.

Ketika X mengirim email ke Y.

Apakah aturan bisnis. Dan untuk melakukan itu, layanan yang mengirim surat diperlukan. Dan entitas yang menangani When Xharus tahu tentang layanan ini.

Tetapi ada beberapa masalah dengan implementasi. Harus transparan kepada pengguna entitas, bahwa entitas menggunakan layanan. Jadi menambahkan layanan di konstruktor bukanlah hal yang baik. Ini juga merupakan masalah, ketika Anda deserializing entitas dari database, karena Anda perlu mengatur data entitas dan contoh layanan. Solusi terbaik yang dapat saya pikirkan adalah menggunakan injeksi properti setelah entitas dibuat. Mungkin memaksa setiap instance yang baru dibuat dari entitas apa pun untuk melalui metode "inisialisasi" yang menyuntikkan semua entitas yang dibutuhkan entitas.

Euforia
sumber
Artikel apa yang Anda maksud dengan yang tidak Anda setujui? Namun, sudut pandang yang menarik pada model domain. Mungkin Anda bisa melihatnya seperti itu, meskipun, orang biasanya menghindari layanan pencampuran ke dalam entitas, karena itu akan membuat kopling ketat segera.
Andy
@Andy Yang Sher10ck referensi dalam pertanyaannya. Dan saya tidak melihat bagaimana itu akan membuat kopling ketat. Setiap kode yang ditulis dengan buruk dapat menyebabkan kopling ketat.
Euforia
1

Itu adalah pertanyaan yang bagus dan masalah yang menarik. Saya mengusulkan agar Anda menggunakan kombinasi pola Rantai Tanggung Jawab dan Pengiriman Ganda (contoh pola di sini ).

Pertama mari kita tentukan hierarki tugas. Perhatikan bahwa sekarang ada beberapa runmetode untuk mengimplementasikan Pengiriman Ganda.

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;
    }
}

Selanjutnya mari kita mendefinisikan Servicehierarki. Kami akan menggunakan Services untuk membentuk Rantai Tanggung Jawab.

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);
        }
    }
}

Bagian terakhir adalah RecurringTaskScheduleryang mengatur proses pemuatan dan pengoperasian.

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);
            }
        }
    }
}

Sekarang, inilah contoh aplikasi yang menunjukkan sistem.

public class App {

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

Menjalankan output aplikasi:

EmailService menjalankan SendEmailTask ​​dengan konten 'inilah email pertama'
EmailService menjalankan SendEmailTask ​​dengan konten 'di sini adalah email kedua'
ExecuteService menjalankan ExecuteTask dengan konten '/ root / python'
ExecuteService menjalankan ExecuteTask dengan konten '/ bin / cat'
EmailService menjalankan SendEmailTask ​​dengan konten '/ bin / cat' konten 'di sini adalah email ketiga'
ExecuteService menjalankan ExecuteTask dengan konten '/ nampan / grep'

iluwatar
sumber
Saya mungkin punya banyak Tugas . Setiap kali saya menambahkan Tugas baru , saya perlu mengubah RecurringTask dan saya juga perlu mengubah semua sub kelasnya, karena saya perlu menambahkan fungsi baru seperti boolean abstrak publik (OtherService otherService) . Saya pikir Option4, pola pengunjung yang juga menerapkan pengiriman ganda memiliki masalah yang sama.
Sher10ck
Poin yang bagus. Saya mengedit jawaban saya sehingga menjalankan metode (layanan) didefinisikan dalam RecurringTask dan mengembalikan false secara default. Dengan cara ini, ketika Anda perlu menambahkan kelas tugas lain Anda tidak perlu menyentuh tugas saudara.
iluwatar