Ziyaretçi Deseni bu senaryoda geçerli mi?


9

Görevimin amacı, zamanlanan yinelenen görevleri çalıştırabilecek küçük bir sistem tasarlamaktır. Yinelenen bir görev, "Pazartesi-Cuma günleri 08: 00-17: 00 saatleri arasında yöneticiye her saat e-posta gönder" gibi bir şeydir.

RecurringTask adında bir temel sınıf var .

public abstract class RecurringTask{

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

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

    }
}

Ve RecurringTask'tan devralınan birkaç sınıfım var . Bunlardan birine SendEmailTask denir .

public class SendEmailTask : RecurringTask{
    private Email email;

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

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

Ve bir e-posta göndermeme yardımcı olabilecek bir EmailService'im var .

Son sınıf RecurringTaskScheduler , görevleri önbellekten veya veritabanından yüklemek ve görevi çalıştırmaktan sorumludur.

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

İşte benim sorunum: EmailService'i nereye koymalıyım ?

Seçenek1 : SendEmailTask içine EmailService enjekte

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

Bir işletmeye bir hizmet enjekte etmemiz gerekip gerekmediği konusunda bazı tartışmalar var ve çoğu insan bunun iyi bir uygulama olmadığına katılıyor. Bkz bu yazıyı .

Seçenek2: Eğer ... YinelenenTaskScheduler içinde başka

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

Bana söylendi Eğer ... Başka ve yukarıdaki gibi döküm OO değildir ve daha fazla sorun getirecektir.

Seçenek3: Çalıştır imzasını değiştirin ve ServiceBundle oluşturun .

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

}

Bu sınıfı RecurringTaskScheduler içine enjekte edin

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

Çalışma yöntemi SendEmailTask olurdu

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

Bu yaklaşımla ilgili büyük bir sorun görmüyorum.

Seçenek4 : Ziyaretçi kalıbı.
Temel fikir, ServiceBundle gibi hizmetleri kapsayacak bir ziyaretçi oluşturmaktır .

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){
        //
    }
}

Ayrıca Run yönteminin imzasını da değiştirmemiz gerekiyor . Çalışma yöntemi SendEmailTask olduğunu

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

Ziyaretçi Paterninin tipik bir uygulamasıdır ve ziyaretçi RecurringTaskScheduler'a enjekte edilecektir .

Özetle: Bu dört yaklaşım arasında, senaryom için hangisi en iyisidir? Ve bu sorun için Option3 ve Option4 arasında büyük bir fark var mı?

Yoksa bu sorun hakkında daha iyi bir fikriniz mi var? Teşekkürler!

Güncelleme 5/22/2015 : Bence Andy'nin cevabı niyetimi gerçekten iyi özetliyor; hala sorunun kendisiyle ilgili kafanız karıştıysa, önce onun yazısını okumanızı öneririm.

Sorunumun Option5'e yol açan Message Dispatch sorununa çok benzediğini öğrendim.

Seçenek5 : Sorunumu Message Dispatch'a dönüştür . Sorunum
ile Message Dispatch sorunu arasında bire bir eşleme var :

İleti Dispatcher : Alma IMessage ve sevk alt sınıfları IMessage bunlara karşılık gelen işleyicilerine. → Yinelenen GörevScheduler

IMessage : Bir arayüz veya soyut bir sınıf. → Yinelenen Görev

MessageA : Bazı ek bilgilere sahip olan IMessage'dan genişler . → SendEmailTask

MessageB : IMessage'ın başka bir alt sınıfı . → CleanDiskTask

MessageAHandler : aldığınızda MessageA , EmailService içeren SendEmailTaskHandler, → idare ve SendEmailTask aldığında bir e-posta gönderir

MessageBHandler : MessageAHandler ile aynı , ancak bunun yerine MessageB'yi işleyin . → CleanDiskTaskHandler

En zor yanı, farklı işleyicilere farklı türde IMessage nasıl gönderileceğidir . İşte faydalı bir bağlantı .

Bu yaklaşımı gerçekten seviyorum, varlığımı hizmetle kirletmiyor ve hiçbir Tanrı sınıfı yok.


Bir dili veya platformu etiketlemediniz, ancak cron'a bakmanızı tavsiye ederim . Platformunuzda benzer şekilde çalışan bir kütüphane olabilir (örn. Geçersiz görünen jcron ). İşleri ve görevleri zamanlamak büyük ölçüde çözülmüş bir sorundur: kendinizinkini almadan önce diğer seçeneklere baktınız mı? Bunları kullanmamanın sebepleri var mıydı?

@Snowman Daha sonra olgun bir kütüphaneye geçebiliriz. Her şey müdürüme bağlı. Bu soruyu yayınlamamın nedeni, bu tür bir sorunu çözmek için bir yol bulmak istiyorum. Bu tür bir problemi bir kereden fazla gördüm ve zarif bir çözüm bulamadım. Bu yüzden yanlış bir şey yapıp yapmadığımı merak ediyorum.
Sher10ck

Yeterince adil, her zaman mümkünse kod yeniden kullanımını tavsiye etmeye çalışın.

1
SendEmailTaskBana göre bir varlıktan çok bir hizmet gibi görünüyor. Seçenek 1 olmadan ve tereddüt gitmek istiyorum.
Bart van Ingen Schenau

3
Ziyaretçi için eksik olan (bana göre) acceptziyaretçilerin sınıf yapısıdır . Ziyaretçinin motivasyonu, bazı toplamalarda ziyaret edilmesi gereken birçok sınıf türüne sahip olmanız ve her yeni işlevsellik (işlem) için kodlarını değiştirmenin uygun olmamasıdır. Hala bu toplu nesnelerin ne olduğunu görmüyorum ve Ziyaretçi'nin uygun olmadığını düşünüyorum. Durum buysa, sorunuzu düzenlemelisiniz (ziyaretçiyi ifade eder).
Fuhrmanator

Yanıtlar:


4

Seçenek 1'in alınacak en iyi yol olduğunu söyleyebilirim . İşten çıkarmamanızın nedeni SendEmailTask, bir varlık olmamasıdır . İşletme, veri ve durum tutma ile ilgilenen bir nesnedir. Sınıfınızda çok az şey var. Aslında, bir varlık değildir, ancak bir varlık tutar : Emaildepoladığınız nesne. Bu, Emailbir hizmet almaması veya bir #Sendyöntemi olması gerektiği anlamına gelir . Bunun yerine, sizin gibi varlıkları alan hizmetlere sahip olmalısınız EmailService. Böylece hizmetleri varlıkların dışında tutma fikrini zaten takip ediyorsunuz.

Yana SendEmailTaskbir varlık değil, içine e-posta ve hizmet enjekte etmek nedenle mükemmel para cezası olduğunu ve yapıcı yoluyla yapılmalıdır. Yapıcı enjeksiyonu yaparak, SendEmailTaskişini her zaman yapmaya hazır olduğundan emin olabiliriz .

Şimdi diğer seçenekleri neden yapmayacağınıza bakalım (özellikle SOLID ile ilgili olarak ).

seçenek 2

Size böyle bir dallamanın yolda daha fazla baş ağrısı getireceği söylendi. Neden olduğuna bakalım. İlk olarak, ifs kümelenme ve büyüme eğilimindedir. Bugün, yarın her sınıfın farklı bir hizmete veya başka bir davranışa ihtiyacı olan e-postalar göndermek bir görevdir. Bu ififadeyi yönetmek bir kabus haline gelir. Tip (ve bu durumda müstehcen tip ) üzerinde dalladığımız için, inşa edilen tip sistemini dilimize indiriyoruz.

Seçenek 2 Tek Sorumluluk (SRP) değildir, çünkü eskiden yeniden kullanılabilir olan RecurringTaskSchedulerbu farklı görev türlerini ve ihtiyaç duyabilecekleri tüm farklı hizmet ve davranışları bilmek zorundadır. Bu sınıfı tekrar kullanmak çok daha zor. Ayrıca Açık / Kapalı (OCP) değildir. Bu tür bir görev ya da bu (ya da bu tür bir hizmet ya da o) hakkında bilgi sahibi olması gerektiğinden, görevlerde ya da hizmetlerde yapılan farklı değişiklikler burada değişiklikleri zorlayabilir. Yeni bir görev eklensin mi? Yeni bir hizmet eklensin mi? E-postanın işlenme biçimini değiştir? Değiştir RecurringTaskScheduler. Görev türü önemli olduğundan, Liskov Yerine Koyma'ya (LSP) uymaz. Sadece bir görev alamaz ve yapılamaz. Bu tip sormak zorundadır ve türüne göre bunu yapmak ya da bunu yapmak. Farklılıkları görevler içine almak yerine, bunların hepsini içine çekiyoruz RecurringTaskScheduler.

Seçenek 3

Seçenek 3'ün bazı büyük sorunları var. Bağlantı verdiğiniz makalede bile , yazar bunu yapmaktan vazgeçirir:

  • Hala statik servis bulucu kullanabilirsiniz ...
  • Yapabildiğim zaman servis bulucudan kaçınırım, özellikle servis bulucu statik olması gerektiğinde…

Sınıfınızla birlikte bir servis bulucu oluşturuyorsunuz ServiceBundle. Bu durumda, statik görünmüyor, ancak yine de bir servis bulucuda bulunan sorunların çoğuna sahip. Bağımlılıklarınız artık bunun altında gizleniyor ServiceBundle. Size harika yeni görevimin aşağıdaki API'sını verirsem:

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

Kullandığım hizmetler nelerdir? Bir testte hangi hizmetlerin atılması gerekir? Sistemdeki her servisi kullanmama engel olan ne ?

Görev sisteminizi bazı görevleri yürütmek için kullanmak istersem, artık yalnızca birkaç veya hatta hiç kullanmasam bile sisteminizdeki her hizmete bağımlıyım.

ServiceBundleO hakkında bilmek gerektiğinden gerçekten SRP değil her sisteminizde hizmet. Ayrıca OCP de değil. Yeni hizmetlerin eklenmesi, değişikliklerin yapılması anlamına gelir ServiceBundleve değişikliklerin yapılması, ServiceBundlebaşka yerlerdeki görevlerde farklı değişiklikler anlamına gelebilir. ServiceBundleArayüzünü (İSS) Ayırmaz. Tüm bu hizmetlerin geniş bir arayüzüne sahiptir ve sadece bu hizmetler için bir sağlayıcı olduğundan, arayüzünü sağladığı tüm hizmetlerin arayüzlerini de kapsayacak şekilde düşünebiliriz. Görevler artık Bağımlılık Tersine (DIP) bağlı değil, çünkü bağımlılıkları arkasından gizleniyor ServiceBundle. Bu aynı zamanda En Az Bilgi Prensibine (Demeter Yasası) da uymaz, çünkü işler yapmak zorunda olduklarından çok daha fazla şey bilir.

Seçenek 4

Önceden, bağımsız olarak çalışabilen birçok küçük nesneniz vardı. Seçenek 4, tüm bu nesneleri alır ve bunları tek bir Visitornesnede toplar . Bu nesne , tüm görevleriniz üzerinde bir tanrı nesnesi gibi davranır . RecurringTaskNesnelerinizi bir ziyaretçiye seslenen anemik gölgelere indirir . Tüm davranışlar Visitor. Davranışı değiştirmeniz mi gerekiyor? Yeni bir görev mi eklemeniz gerekiyor? Değiştir Visitor.

Daha zor olan kısım, farklı davranışların tümü tek bir sınıfta olduğu için, bazı davranışların değiştirilmesi tüm diğer davranışlar boyunca polimorfik olarak sürüklenir. Örneğin, e-posta göndermenin iki farklı yoluna sahip olmak istiyoruz (belki farklı sunucular mı kullanmalılar?). Bunu nasıl yapardık? Bir IVisitorarayüz oluşturabilir ve #Visit(ClearDiskTask)orijinal ziyaretçimizin yaptığı gibi bu kodu çoğaltabilecek şekilde uygulayabiliriz . O zaman bir diski temizlemenin yeni bir yolunu bulursak, tekrar uygulamamız ve çoğaltmamız gerekir. Sonra her iki değişikliği de istiyoruz. Uygulayın ve tekrarlayın. Bu iki farklı, farklı davranış ayrılmaz bir şekilde bağlantılıdır.

Belki onun yerine sadece alt sınıf yapabiliriz Visitor? Yeni e-posta davranışı ile alt sınıf, yeni disk davranışı ile alt sınıf. Şimdiye kadar çoğaltma yok! Her ikisiyle de alt sınıf? Şimdi biri ya da diğeri çoğaltılmalıdır (ya da tercihiniz buysa).

Seçenek 1 ile karşılaştıralım: Yeni bir e-posta davranışına ihtiyacımız var. Biz yeni oluşturabilir RecurringTaskiğne yapmak onun bağımlılıkları, yeni davranışı yaptığı ve görevlerin koleksiyonuna ekleme RecurringTaskScheduler. Diskleri temizleme hakkında konuşmamıza bile gerek yok, çünkü bu sorumluluk tamamen başka bir yerde. Yine de elimizde tam kapsamlı OO araçları var. Örneğin, bu görevi günlük kaydıyla dekore edebiliriz.

Seçenek 1 size en az acıyı verecektir ve bu durumu ele almanın en doğru yoludur.


Otion2,3,4 ile ilgili analiziniz harika! Gerçekten bana çok yardımcı oluyor. Ancak Option1 için * SendEmailTask ​​* bir varlık olduğunu iddia ediyorum. Kimliğine sahiptir, yinelenen paternine ve db'de saklanması gereken diğer faydalı bilgilere sahiptir. Bence Andy niyetimi iyi özetliyor. Belki * EMailTaskDefinitions * gibi bir isim daha uygundur. varlığımı servis kodumla kirletmek istemiyorum. Varlığa bir hizmet enjekte edersem, öforik bir problemden bahseder. Ben de sorum güncelleme ve şimdiye kadar en iyi çözüm olduğunu düşünüyorum Option5 dahil.
Sher10ck

@ Sher10ck Eğer SendEmailTaskveritabanınız için bir konfigürasyon çekiyorsanız , o konfigürasyon sizin de içine enjekte edilmesi gereken ayrı bir konfigürasyon sınıfı olmalıdır SendEmailTask. Verilerinizden veri SendEmailTaskoluşturuyorsanız, durumu depolamak ve bunu veritabanınıza koymak için bir hatıra nesnesi oluşturmanız gerekir.
cbojar

Ben db yapılandırmayı çekmek gerekir, bu yüzden u hem enjekte öneriyorsunuz EMailTaskDefinitionsve EmailServiceiçine SendEmailTask? Sonra , sorumluluğu tanım ve hizmeti yükleyen ve bunları içine enjekte eden RecurringTaskSchedulergibi bir şey enjekte etmem gerekiyor . Ama şimdi her görevin deposunu bilme ihtiyacını tartışırım . Ve her zaman yeni bir görev (zamanlayıcı içine depo eklemek için) değiştirmek gerekir . SendEmailTaskRepositorySendEmailTaskRecurringTaskSchedulerCleanDiskTaskRepositoryRecurringTaskScheduler
Sher10ck

@ Sher10ck RecurringTaskSchedulerSadece genelleştirilmiş bir görev deposu ve a RecurringTask. Bunu yaparak soyutlamalara bağlı olabilir. Görev havuzları yapıcısına enjekte edilebilir RecurringTaskScheduler. Daha sonra, farklı depoların yalnızca RecurringTaskSchedulersomutlaştırıldığı yerde bilinmesi gerekir (veya bir fabrikada gizlenebilir ve oradan çağrılabilir). Sadece soyutlamalara bağlı olduğu için, RecurringTaskSchedulerher yeni görevle değişmesi gerekmez. Bu, bağımlılığın tersine çevrilmesinin özüdür.
cbojar

3

Bahar kuvars veya bahar partisi gibi mevcut kütüphanelere bir göz attınız mı (ihtiyaçlarınıza en uygun olandan emin değilim)?

Sorunuz için:

Sorunun, göreve bazı meta verileri polimorfik bir şekilde sürdürmek istediğinizi varsayalım, bu nedenle bir e-posta görevine atanmış e-posta adresleri, bir günlük görevi bir günlük düzeyi vb. Bunların bir listesini bellekte veya veritabanınızda saklayabilirsiniz, ancak endişeleri ayrı tutmak için varlığın hizmet kodu ile kirletilmesini istemezsiniz.

Benim önerdiğim çözüm:

Örneğin TaskDefinitionve a olmak üzere görevin çalışmasını ve veri kısmını ayırırdım TaskRunner. TaskDefinition bir TaskRunner veya bir fabrika oluşturan bir referansa sahiptir (örn. Smtp-host gibi bazı kurulumlar gerekiyorsa). Fabrika belirli bir fabrika - sadece EMailTaskDefinitions ile başa çıkabilir ve sadece EMailTaskRunners örneklerini döndürür . Bu şekilde daha fazla OO'dur ve güvenlidir - yeni bir görev türü eklerseniz, yeni bir özel fabrika tanıtmanız gerekir (veya birini yeniden kullanın), eğer yapamazsanız derleyemezsiniz.

Bu şekilde bir bağımlılıkla karşılaşırsınız: varlık katmanı -> hizmet katmanı ve geri, çünkü Runner öğede depolanan bilgilere ihtiyaç duyar ve muhtemelen DB'deki durumuyla ilgili bir güncelleme yapmak ister.

Bir TaskDefinition alıp belirli bir TaskRunner döndüren genel bir fabrika kullanarak daireyi kırabilirsiniz, ancak bu çok sayıda ifs gerektirecektir. Sen olabilir benzer tanımına olarak adlandırılan bir koşucu bulmak için yansıma kullanın, ancak bu yaklaşım bazı performans mal olabilir dikkatli olun ve çalışma zamanı hataları neden olabilir.

PS: Burada Java olduğunu varsayıyorum. Sanırım .net'te de benzer. Buradaki asıl sorun çift bağlanmadır.

Ziyaretçi kalıbına

Ben saf çift bağlama amaçları yerine, çalışma zamanında farklı veri nesneleri için bir algoritma alışverişi için kullanılmak üzere tasarlanmış olduğunu düşünüyorum. Örneğin, farklı türden sigortalarınız varsa ve bunları hesaplarken farklı türleriniz varsa, örneğin farklı ülkeler bunu gerektirdiğinden. Sonra belirli bir hesaplama yöntemi seçip birkaç sigortaya uygularsınız.

Sizin durumunuzda belirli bir görev stratejisi (örn. E-posta) seçer ve bunu tüm görevlerinize uygularsınız, bu yanlıştır çünkü hepsi e-posta görevleri değildir.

PS Ben test etmedim, ama bence Seçenek 4 de işe yaramaz, çünkü tekrar çift bağlayıcıdır.


Niyetimi gerçekten iyi özetlersiniz, teşekkürler! Daireyi kırmak istiyorum. İcar Çünkü TaskDefiniton bir başvuru içerir TaskRunner veya fabrika OPTION1 aynı sorun var. Ben tedavi fabrika veya TaskRunner hizmeti olarak. Eğer TaskDefinition ihtiyacı onlara bir başvuru içerir, içine hizmet iğne yapmak ya sahip TaskDefinition , ya da ben önlemek için çalışıyorum bazı statik yöntemini kullanın.
Sher10ck

1

Bu makaleye tamamen katılmıyorum. Hizmetler (somut olarak "API'ları"), İşletme Alanının önemli taraflarıdır ve bu nedenle Alan Modeli içinde yer alacaktır. Ayrıca, işletme alanındaki varlıklarla aynı işletme alanındaki başka bir şeye referansta bulunan bir sorun yoktur.

X, Y'ye posta gönderdiğinde.

Bir iş kuralıdır. Bunun için posta gönderen servise ihtiyaç vardır. Ve işleyen varlık When Xbu hizmet hakkında bilmek gerekir.

Ancak uygulama ile ilgili bazı sorunlar var. Varlığın kullanıcı tarafından, servis kullanması şeffaf olmalıdır. Yani hizmeti yapıcıya eklemek iyi bir şey değildir. Hem varlığın verilerini hem de hizmet örneklerini ayarlamanız gerektiğinden, bu, varlığı veritabanından serileştirdiğinizde de bir sorundur. Aklıma gelen en iyi çözüm, varlık oluşturulduktan sonra özellik enjeksiyonunu kullanmaktır. Belki de herhangi bir varlığın yeni oluşturulan her örneğini, varlığın ihtiyacı olan tüm varlıkları enjekte eden "initialize" yönteminden geçmeye zorlar.


Hangi makaleye katılmıyorsunuz? Ancak, etki alanı modeli ilginç bir bakış açısı. Muhtemelen böyle görebilirsiniz, ancak, insanlar genellikle hizmetleri hizmetlere karıştırmaktan kaçınırlar, çünkü çok yakında sıkı bir bağlantı oluşturacaktır.
Andy,

@Andy Sher10ck sorusunda referansta bulundu. Ve nasıl sıkı bir bağlantı oluşturacağını görmüyorum. Kötü yazılmış herhangi bir kod sıkı bağlantı kurabilir.
Euphoric

1

Bu harika bir soru ve ilginç bir sorundur. Sorumluluk Zinciri ve Çift Sevkıyat kalıplarının bir kombinasyonunu kullanmanızı öneriyorum ( burada örnek örnekler ).

Önce görev hiyerarşisini tanımlayalım. Şimdi runDouble Dispatch'ı uygulamak için birden fazla yöntem olduğuna dikkat edin .

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

Sonra Servicehiyerarşiyi tanımlayalım . ServiceSorumluluk Zincirini oluşturmak için s'yi kullanacağız .

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

Son parça, RecurringTaskScheduleryükleme ve çalıştırma işlemini düzenleyen parça .

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

Şimdi, sistemi gösteren örnek uygulama.

public class App {

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

Uygulama çıktılarını çalıştırma:

E-postaService içeriği ile SendEmailTask ​​çalıştıran 'burada ilk e-posta geliyor' E-
postaService içeriği ile SendEmailTask ​​çalıştıran 'burada ikinci e-posta'
ExecuteService içerik ile ExecuteTask çalışan '/ root / python'
ExecuteService içerik ile
ExecuteTask çalışan content 'burada üçüncü e-posta'
ExecuteService '/ bin / grep' içerikli ExecuteTask çalıştırıyor


Çok fazla görevim olabilir . Yeni eklediğimiz her zaman Görevi , ben değiştirmeniz gerekir RecurringTask ve ben gibi yeni bir işlev eklemek gerekir, çünkü ben de, tüm alt sınıflarını değiştirmek gerekir kamu soyut boolean vadede (OtherService otherService) . Bence Option4, çift sevk de uygulayan ziyaretçi kalıp aynı sorunu vardır.
Sher10ck

İyi bir nokta. Cevabımı düzenledim, böylece run (hizmet) yöntemleri RecurringTask'ta tanımlanır ve varsayılan olarak false döndürür. Bu şekilde, başka bir görev sınıfı eklemeniz gerektiğinde kardeş görevlerine dokunmanız gerekmez.
iluwatar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.