이 시나리오에서 방문자 패턴이 유효합니까?


9

내 작업의 목표는 예약 된 되풀이 작업을 실행할 수있는 작은 시스템을 설계하는 것입니다. 되풀이되는 작업은 "월요일부터 금요일까지 오전 8 시부 터 오후 5 시까 지 관리자에게 이메일을 보내는 것"과 같습니다.

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를 어디에 두어야 합니까?

Option1 : EmailServiceSendEmailTask에 주입

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

위와 같이 말하면 OO가 아니며 더 많은 문제가 발생할 것입니다.

옵션 3 : 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);
}

이 방법에는 큰 문제가 없습니다.

Option4 : 방문자 패턴.
기본 아이디어는 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){
        //
    }
}

또한 Run 메소드 의 서명도 변경해야합니다 . SendEmailTaskRun 메소드 는

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

방문자 패턴의 일반적인 구현이며 방문자는 RecurringTaskScheduler에 주입됩니다 .

요약하자면,이 네 가지 접근법 중 어느 것이 내 시나리오에 가장 적합합니까? 그리고이 문제에 대해 Option3과 Option4 사이에 큰 차이가 있습니까?

아니면이 문제에 대해 더 잘 알고 있습니까? 감사!

2015 년 5 월 22 일 업데이트 : Andy의 답변에 내 의도가 잘 요약되어 있다고 생각합니다. 여전히 문제 자체에 대해 혼란 스러우면 먼저 그의 게시물을 읽는 것이 좋습니다.

방금 내 문제가 Message Dispatch 문제 와 매우 유사하다는 것을 알았습니다. 이는 Option5로 이어집니다.

Option5 : 내 문제를 Message Dispatch로 변환하십시오 .
내 문제와 Message Dispatch 문제 사이에 일대일 매핑이 있습니다.

메시지 Dispatcher는 : 수신 iMessage를 의 파견 하위 클래스 iMessage를을 자신의 해당 핸들러. → RecurringTaskScheduler

IMessage : 인터페이스 또는 추상 클래스. → 되풀이 작업

MessageA : 추가 정보가있는 IMessage 에서 확장됩니다 . → SendEmailTask

MessageB : IMessage의 또 다른 서브 클래스 . → CleanDiskTask

MessageAHandler는 :받을 때 MessageA을 , EmailService을 포함 SendEmailTaskHandler, →를 취급하고 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

당신은 그런 유형의 분기가 길을 더 많이 두통을 가져올 것이라고 올바르게 들었습니다. 이유를 봅시다. 첫째, if집단화하고 성장하는 경향이있다. 오늘, 내일, 모든 종류의 수업마다 다른 서비스 나 다른 행동이 필요합니다. 그 if진술을 관리하는 것은 악몽이됩니다. 우리는 유형 (이 경우 명시 적 유형 ) 으로 분기하기 때문에 언어에 내장 된 유형 시스템을 파괴합니다.

옵션 2는 SRP (Single Responsibility)가 아닙니다. 이전에 재사용 할 수 있었던 RecurringTaskScheduler모든 유형의 작업과 필요한 모든 종류의 서비스 및 동작에 대해 알아야하기 때문입니다. 그 수업은 재사용하기가 훨씬 어렵습니다. 또한 OCP (Open / Closed)가 아닙니다. 이런 종류의 작업이나 그 작업 (또는 이런 종류의 서비스 또는 해당 작업)에 대해 알아야하기 때문에 작업이나 서비스에 대한 이종 변경은 여기에서 변경을 강제 할 수 있습니다. 새 작업을 추가 하시겠습니까? 새로운 서비스를 추가 하시겠습니까? 이메일 처리 방식을 변경 하시겠습니까? 변경하십시오 RecurringTaskScheduler. 작업 유형이 중요하기 때문에 LSP (Liskov Substitution)를 준수하지 않습니다. 작업을 수행하고 완료 할 수 없습니다. 유형을 요구해야하며 유형에 따라이 작업을 수행하거나 수행하십시오. 작업에 차이점을 캡슐화하는 대신 모든 것을로 가져옵니다 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.

더 어려운 부분은 서로 다른 모든 동작이 모두 단일 클래스에 있기 때문에 다른 모든 동작을 따라 다형성으로 드래그하는 것입니다. 예를 들어, 이메일을 보내는 두 가지 방법이 필요합니다 (다른 서버를 사용해야합니까?). 우리는 어떻게할까요? 우리는 IVisitor인터페이스를 만들어서 #Visit(ClearDiskTask)원래 방문자 와 같은 코드를 복제 할 수 있습니다 . 그런 다음 디스크를 지우는 새로운 방법이 나오면 다시 구현하고 복제해야합니다. 그런 다음 두 종류의 변경을 원합니다. 다시 구현하고 복제하십시오. 이 두 개의 서로 다른 이종 행동은 불가분의 관계가 있습니다.

어쩌면 우리는 단지 서브 클래스를 만들 수 Visitor있습니까? 새로운 이메일 동작이있는 서브 클래스, 새로운 디스크 동작이있는 서브 클래스. 지금까지 중복은 없습니다! 둘 다 서브 클래스? 이제 하나 또는 다른 것을 복제해야합니다 (또는 선호하는 경우 둘 다).

옵션 1과 비교해 보겠습니다. 새로운 이메일 동작이 필요합니다. RecurringTask새로운 동작을 수행 하는 새로운 것을 만들고, 의존성을 주입하고,에서 작업 모음에 추가 할 수 RecurringTaskScheduler있습니다. 디스크를 지우는 것에 대해 이야기 할 필요조차 없습니다. 책임은 전적으로 다른 곳이기 때문입니다. 우리는 여전히 우리가 사용할 수있는 모든 OO 도구를 보유하고 있습니다. 예를 들어 로깅으로 해당 작업을 꾸밀 수 있습니다.

옵션 1은 가장 적은 고통을 줄 것이며이 상황을 처리하는 가장 올바른 방법입니다.


Otion2,3,4에 대한 귀하의 분석은 환상적입니다! 정말 많은 도움이됩니다. 그러나 Option1의 경우 * SendEmailTask ​​*는 엔티티입니다. ID가 있고 반복 패턴이 있으며 db에 저장 해야하는 기타 유용한 정보가 있습니다. 앤디가 내 의도를 잘 요약했다고 생각합니다. 아마도 * EMailTaskDefinitions *와 같은 이름이 더 적합 할 것입니다. 서비스 코드로 엔티티를 오염시키고 싶지 않습니다. 서비스에 엔터티를 주입하면 몇 가지 문제가 언급됩니다. 나는 또한 내 질문을 업데이트하고 Option5를 포함시켰다.
Sher10ck

@ Sher10ck SendEmailTask데이터베이스에서 구성을 가져 오는 경우 해당 구성은에 구성해야하는 별도의 구성 클래스 여야합니다 SendEmailTask. 에서 데이터를 생성하는 경우 SendEmailTask상태를 저장하고 데이터베이스에 저장하는 memento 객체를 만들어야합니다.
cbojar

I는 DB의 구성을 당겨야하므로 U 모두 주입 하자는 EMailTaskDefinitionsEmailServiceSendEmailTask? 그런 다음 RecurringTaskScheduler, SendEmailTaskRepository정의 및 서비스를로드하는 책임이 있는 것과 같은 것을 주입하고 주입해야합니다 SendEmailTask. 그러나 이제는 RecurringTaskScheduler모든 작업의 ​​리포지토리를 알아야 한다고 주장합니다 CleanDiskTaskRepository. 그리고 RecurringTaskScheduler새로운 작업이있을 때마다 (스케줄러에 리포지토리를 추가 하기 위해) 변경해야합니다 .
Sher10ck

@ Sher10ck RecurringTaskScheduler일반화 된 작업 저장소와의 개념 만 알고 있어야합니다 RecurringTask. 이렇게함으로써 추상화에 의존 할 수 있습니다. 작업 저장소는의 생성자에 주입 될 수 있습니다 RecurringTaskScheduler. 그런 다음 다른 리포지토리 RecurringTaskScheduler는 인스턴스화 되는 위치 만 알면됩니다 (또는 공장에서 숨겨져 호출 될 수 있음). 추상화에만 의존하기 때문에 RecurringTaskScheduler각각의 새로운 작업으로 변경할 필요가 없습니다. 이것이 의존성 역전의 본질입니다.
cbojar 2016 년

3

스프링 쿼츠 또는 스프링 배치와 같은 기존 라이브러리를 살펴 보셨습니까 (필요한 것이 무엇인지 가장 잘 모르겠습니다)?

귀하의 질문에 :

문제는 일부 메타 데이터를 다형성으로 작업에 유지하기 위해 전자 메일 작업에 전자 메일 주소가 할당되고 로그 작업이 로그 수준 등으로 유지된다는 것입니다. 메모리 나 데이터베이스에 이들의 목록을 저장할 수 있지만 서비스 코드로 개체를 오염시키지 않으려는 우려를 분리하기 위해.

내 제안 솔루션 :

나는 예를 들어 가지고는 running- 및 작업의 데이터 부분을 분리 할 TaskDefinition과를 TaskRunner. TaskDefinition에는 TaskRunner 또는 팩토리를 작성하는 팩토리에 대한 참조가 있습니다 (예 : smtp-host와 같은 일부 설정이 필요한 경우). 팩토리는 특정 팩토리입니다. EMailTaskDefinitions 만 처리하고 s 인스턴스 만 리턴 할 수 있습니다 EMailTaskRunner. 이런 식으로 OO가 더 크고 안전합니다. 새로운 작업 유형을 도입 할 경우 컴파일 할 수없는 경우 새로운 특정 팩토리를 도입하거나 재사용해야합니다.

이 방법으로 엔터티에 저장된 정보가 필요하고 아마도 DB의 상태를 업데이트하려고하기 때문에 엔터티 계층-> 서비스 계층 및 종속성과 같은 종속성이 생길 수 있습니다.

당신은 걸리는 일반 공장, 사용하여 원을 깰 수 TaskDefinition에를하고 반환 특정 TaskRunner을,하지만 IFS를 많이 필요로한다. 당신은 할 수 마찬가지로 당신의 정의로라는 이름의 러너를 찾기 위해 반사를 사용하지만,이 방법은 약간의 성능 비용을 수주의 및 런타임 오류가 발생할 수 있습니다.

추신 : 나는 여기서 Java를 가정합니다. .net과 비슷하다고 생각합니다. 여기서 주요 문제는 이중 바인딩입니다.

손님 패턴에

나는 순수한 이중 바인딩 목적보다는 런타임에 다른 종류의 데이터 객체에 대한 알고리즘을 교환하는 데 사용되었다고 생각합니다. 예를 들어, 다른 종류의 보험이 있고 다른 종류의 보험이있는 경우 (예 : 다른 국가에서 요구하기 때문에) 그런 다음 특정 계산 방법을 선택하여 여러 보험에 적용하십시오.

귀하의 경우 특정 작업 전략 (예 : 이메일)을 선택하여 모든 작업에 적용하십시오. 모든 작업이 전자 메일 작업이 아니기 때문에 잘못되었습니다.

추신 : 나는 그것을 테스트하지는 않았지만 옵션 4는 다시 이중 바인딩이기 때문에 작동하지 않을 것이라고 생각합니다.


내 의도를 잘 요약 했어! 원을 나누고 싶습니다. 시키기 때문에 TaskDefiniton가 에 대한 참조 보유 TaskRunner을 하거나 공장은 옵션 1과 동일한 문제가 있습니다. 팩토리 또는 TaskRunner 를 서비스로 취급 합니다. TaskDefinition에 대한 참조가 필요한 경우 서비스를 TaskDefinition에 삽입 하거나 일부 정적 메소드를 사용하여 피하려고합니다.
Sher10ck

1

나는 그 기사에 전적으로 동의하지 않습니다. 서비스 (구체적으로 "API")는 비즈니스 도메인의 중요한 당사자이므로 도메인 모델 내에 존재합니다. 또한 동일한 비즈니스 도메인에서 다른 것을 참조하는 비즈니스 도메인의 엔터티에는 문제가 없습니다.

X가 Y에게 메일을 보낼 때

비즈니스 규칙입니다. 그리고이를 위해서는 메일을 보내는 서비스가 필요합니다. 그리고 처리하는 엔티티 When X는이 서비스에 대해 알아야합니다.

그러나 구현에는 몇 가지 문제가 있습니다. 엔티티가 서비스를 사용하고 있다는 것은 엔티티 사용자에게 투명해야합니다. 따라서 생성자에 서비스를 추가하는 것은 좋지 않습니다. 엔터티의 데이터와 서비스 인스턴스를 모두 설정해야하기 때문에 데이터베이스에서 엔터티를 역 직렬화하는 경우에도 문제가됩니다. 내가 생각할 수있는 가장 좋은 해결책은 엔티티가 생성 된 후 속성 주입을 사용하는 것입니다. 엔터티에 새로 생성 된 각 인스턴스가 엔터티에 필요한 모든 엔터티를 주입하는 "초기화"방법을 강요하도록 할 수 있습니다.


당신이 동의하지 않는 기사 는 무엇입니까 ? 그러나 도메인 모델에 대한 흥미로운 관점. 아마도 사람들은 일반적으로 서비스가 엔티티에 혼합되는 것을 피할 수 있습니다. 왜냐하면 곧 결합이 빡빡하기 때문입니다.
Andy

@Andy Sher10ck가 그의 질문에서 언급 한 것입니다. 그리고 그것이 타이트한 커플 링을 만드는 방법을 보지 못했습니다. 잘못 작성된 코드는 밀접한 결합을 유발할 수 있습니다.
Euphoric

1

좋은 질문이자 흥미로운 문제입니다. 책임 체인이중 디스패치 패턴 (패턴 예제는 여기 )을 조합하여 사용할 것을 제안합니다 .

먼저 작업 계층을 정의합니다. runDouble Dispatch를 구현하는 여러 가지 방법이 있습니다.

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계층을 정의 할 수 있습니다 . 우리는 Service책임 사슬을 형성하기 위해 s를 사용할 것 입니다.

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

응용 프로그램 출력 실행

컨텐츠가 '여기서는 첫 번째 이메일
임 '으로 SendEmailTask를 실행하는 EmailService 컨텐츠가 '여기에는 두 번째 이메일입니다'로 SendEmailTask를 실행하는 EmailService
컨텐츠가 '/ root / python'으로
ExecuteService를 실행하는 ExecuteService 컨텐츠가 '/ bin / cat'을 포함하는
ExecuteService를 실행합니다. content '
/ bin / grep'컨텐츠로 ExecuteTask를 실행하는 ' ExecuteService '라는 세 번째 이메일이 있습니다.


나는 많은 작업 이있을 수 있습니다 . 나는 새로운 추가 할 때마다 작업은 , 내가 변경해야 RecurringTask을 내가 같은 새로운 기능을 추가 할 필요가 있기 때문에 나는 또한, 모든 하위 클래스를 변경해야하는 공공 추상 부울 실행 (OtherService otherService) . 이중 발송을 구현하는 방문자 패턴 인 Option4도 같은 문제가 있다고 생각합니다.
Sher10ck

좋은 지적. run (service) 메소드가 RecurringTask에 정의되어 기본적으로 false를 반환하도록 답변을 편집했습니다. 이렇게하면 다른 작업 클래스를 추가해야 할 때 형제 작업을 건드릴 필요가 없습니다.
iluwatar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.