Mẫu khách truy cập có hợp lệ trong kịch bản này không?


9

Mục tiêu của nhiệm vụ của tôi là thiết kế một hệ thống nhỏ có thể chạy các nhiệm vụ định kỳ theo lịch trình. Một nhiệm vụ định kỳ là một cái gì đó như "gửi email cho quản trị viên mỗi giờ từ 8:00 sáng đến 5:00 chiều, từ thứ Hai đến thứ Sáu".

Tôi có một lớp cơ sở gọi là 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(){

    }
}

Và tôi có một vài lớp được kế thừa từ RecurringTask . Một trong số đó được gọi là SendEmailTask .

public class SendEmailTask : RecurringTask{
    private Email email;

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

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

Và tôi có một EmailService có thể giúp tôi gửi email.

Lớp cuối cùng là RecurringTaskScheduler , nó chịu trách nhiệm tải các tác vụ từ bộ đệm hoặc cơ sở dữ liệu và chạy tác vụ.

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

Đây là vấn đề của tôi: tôi nên đặt EmailService ở đâu?

Option1 : Tiêm EmailService vào 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);
    }
}

Đã có một số cuộc thảo luận về việc chúng ta có nên tiêm dịch vụ vào một thực thể hay không và hầu hết mọi người đều đồng ý rằng đó không phải là một thực tiễn tốt. Xem bài viết này .

Tùy chọn 2: Nếu ... Khác trong 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
                }
            }
        }
    }
}

Tôi đã được thông báo Nếu ... Khác và diễn viên như trên không phải là OO, và sẽ mang lại nhiều vấn đề hơn.

Tùy chọn 3: Thay đổi chữ ký của Run và tạo ServiceBundle .

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

}

Tiêm lớp này vào 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);
            }
        }
    }
}

Các Run phương pháp SendEmailTask sẽ

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

Tôi không thấy bất kỳ vấn đề lớn với phương pháp này.

Tùy chọn4 : Mẫu khách truy cập.
Ý tưởng cơ bản là tạo một khách truy cập sẽ đóng gói các dịch vụ giống như 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){
        //
    }
}

Và chúng ta cũng cần thay đổi chữ ký của phương thức Run . Các Run phương pháp SendEmailTask

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

Đây là một triển khai điển hình của Mẫu khách truy cập và khách truy cập sẽ được đưa vào RecurringTaskScheduler .

Tóm lại: Trong số bốn cách tiếp cận này, cách nào là tốt nhất cho kịch bản của tôi? Và có sự khác biệt lớn nào giữa Option3 và Option4 cho vấn đề này không?

Hay bạn có ý tưởng tốt hơn về vấn đề này? Cảm ơn!

Cập nhật 22/05/2015 : Tôi nghĩ câu trả lời của Andy tóm tắt ý định của tôi thực sự tốt; Nếu bạn vẫn còn bối rối về vấn đề này, tôi khuyên bạn nên đọc bài viết của mình trước.

Tôi mới phát hiện ra vấn đề của mình rất giống với sự cố Message Message , dẫn đến Option5.

Tùy chọn5 : Chuyển đổi vấn đề của tôi sang Công văn tin nhắn .
Có một ánh xạ một-một giữa vấn đề của tôi và vấn đề Gửi thư :

Bộ điều phối thư : Nhận IMessage và gửi các lớp phụ của IMessage đến các bộ xử lý tương ứng của chúng. → Định kỳTaskScheduler

IMessage : Một giao diện hoặc một lớp trừu tượng. → Định kỳ

MessageA : Mở rộng từ IMessage , có thêm một số thông tin. → SendEmailTask

MessageB : Một lớp con khác của IMessage . → CleanDiskTask

MessageAHandler : Khi nhận MessageA , hãy xử lý nó → SendEmailTaskHandler, có chứa EmailService và sẽ gửi email khi nhận được SendEmailTask

MessageBHandler : Tương tự MessageAHandler , nhưng xử lý MessageB thay thế. → CleanDiskTaskHandler

Phần khó nhất là làm thế nào để gửi loại IMessage khác nhau đến những người xử lý khác nhau. Đây là một liên kết hữu ích .

Tôi thực sự thích cách tiếp cận này, nó không gây ô nhiễm thực thể của tôi với dịch vụ và nó không có bất kỳ lớp Chúa nào .


Bạn chưa gắn thẻ ngôn ngữ hoặc nền tảng, nhưng tôi khuyên bạn nên tìm hiểu về cron . Nền tảng của bạn có thể có một thư viện hoạt động tương tự (ví dụ jcron có vẻ như không còn tồn tại). Lập kế hoạch công việc và nhiệm vụ phần lớn là một vấn đề được giải quyết: bạn đã xem xét các lựa chọn khác trước khi tự mình thực hiện chưa? Có lý do cho việc không sử dụng chúng?

@Snowman Chúng tôi có thể chuyển sang một thư viện trưởng thành sau này. Tất cả phụ thuộc vào người quản lý của tôi. Lý do tôi đăng câu hỏi này là tôi muốn tìm cách giải quyết 'loại' vấn đề này. Tôi đã thấy loại vấn đề này hơn một lần và không thể tìm ra một giải pháp tao nhã. Vì vậy, tôi tự hỏi liệu tôi đã làm điều gì sai.
Sher10ck

Đủ công bằng, tôi luôn cố gắng đề nghị tái sử dụng mã nếu có thể.

1
SendEmailTaskcó vẻ giống như một dịch vụ hơn là một thực thể đối với tôi. Tôi sẽ đi cho tùy chọn 1 mà không và do dự.
Bart van Ingen Schenau

3
Điều còn thiếu (với tôi) đối với Khách truy cập là cấu trúc lớp mà acceptkhách truy cập. Động lực cho Khách truy cập là bạn có nhiều loại lớp trong một số tổng hợp cần truy cập và không thuận tiện để sửa đổi mã của họ cho mỗi chức năng (hoạt động) mới. Tôi vẫn không thấy những đối tượng tổng hợp đó là gì và nghĩ rằng Khách truy cập không phù hợp. Nếu đó là trường hợp, bạn nên chỉnh sửa câu hỏi của bạn (trong đó đề cập đến khách truy cập).
Fuhrmanator

Câu trả lời:


4

Tôi muốn nói Phương án 1 là con đường tốt nhất để đi. Lý do bạn không nên bỏ qua nó là SendEmailTaskkhông một tổ chức. Một thực thể là một đối tượng liên quan đến việc giữ dữ liệu và trạng thái. Lớp học của bạn có rất ít điều đó. Trong thực tế, nó không phải là một thực thể, nhưng nó chứa một thực thể: Emailđối tượng bạn đang lưu trữ. Điều đó có nghĩa là Emailkhông nên sử dụng dịch vụ hoặc có #Sendphương pháp. Thay vào đó, bạn nên có các dịch vụ lấy các thực thể, chẳng hạn như của bạn EmailService. Vì vậy, bạn đã theo đuổi ý tưởng giữ dịch vụ ra khỏi các thực thể.

SendEmailTaskkhông phải là một thực thể, do đó, việc đưa email và dịch vụ vào đó là hoàn toàn tốt, và điều đó nên được thực hiện thông qua hàm tạo. Bằng cách thực hiện tiêm constructor, chúng tôi có thể chắc chắn rằng SendEmailTaskluôn sẵn sàng để thực hiện công việc của mình.

Bây giờ hãy xem tại sao không thực hiện các tùy chọn khác (cụ thể là liên quan đến RẮN ).

Lựa chọn 2

Bạn đã được nói một cách đúng đắn rằng phân nhánh theo kiểu như thế sẽ mang lại nhiều đau đầu hơn trên đường. Hãy xem tại sao. Đầu tiên, ifs có xu hướng co cụm và phát triển. Hôm nay, nhiệm vụ là gửi email, ngày mai, mỗi loại lớp khác nhau cần một dịch vụ khác hoặc hành vi khác. Quản lý iftuyên bố đó trở thành một cơn ác mộng. Vì chúng tôi đang phân nhánh theo loại (và trong trường hợp này là loại rõ ràng ), chúng tôi đang lật đổ hệ thống loại được tích hợp vào ngôn ngữ của chúng tôi.

Tùy chọn 2 không phải là Trách nhiệm đơn lẻ (SRP) vì trước đây có thể sử dụng lại RecurringTaskSchedulerphải biết về tất cả các loại nhiệm vụ khác nhau này và về tất cả các loại dịch vụ và hành vi khác nhau mà chúng có thể cần. Lớp học đó khó sử dụng hơn nhiều. Nó cũng không mở / Đóng (OCP). Bởi vì nó cần biết về loại nhiệm vụ này hoặc một (hoặc loại dịch vụ này hoặc loại dịch vụ đó), các thay đổi khác nhau đối với các nhiệm vụ hoặc dịch vụ có thể buộc thay đổi ở đây. Thêm một nhiệm vụ mới? Thêm một dịch vụ mới? Thay đổi cách xử lý email? Thay đổi RecurringTaskScheduler. Bởi vì loại nhiệm vụ quan trọng, nó không tuân thủ Liskov thay thế (LSP). Nó không thể chỉ nhận một nhiệm vụ và được thực hiện. Nó phải yêu cầu loại và dựa trên loại làm điều này hoặc làm điều đó. Thay vì gói gọn sự khác biệt trong các nhiệm vụ, chúng tôi đang kéo tất cả những điều đó vào RecurringTaskScheduler.

Lựa chọn 3

Phương án 3 có một số vấn đề lớn. Ngay cả trong bài viết bạn liên kết đến , tác giả vẫn không khuyến khích việc này:

  • Bạn vẫn có thể sử dụng công cụ định vị dịch vụ tĩnh
  • Tôi tránh định vị dịch vụ khi tôi có thể, đặc biệt là khi định vị dịch vụ phải tĩnh tĩnh

Bạn đang tạo một công cụ định vị dịch vụ với ServiceBundlelớp của bạn . Trong trường hợp này, nó dường như không tĩnh, nhưng nó vẫn có nhiều vấn đề cố hữu trong một bộ định vị dịch vụ. Sự phụ thuộc của bạn bây giờ được ẩn đi dưới này ServiceBundle. Nếu tôi cung cấp cho bạn API sau đây về nhiệm vụ mới thú vị của tôi:

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

Các dịch vụ tôi đang sử dụng là gì? Những dịch vụ nào cần được chế giễu trong một bài kiểm tra? Điều gì ngăn tôi sử dụng mọi dịch vụ trong hệ thống, chỉ vì?

Nếu tôi muốn sử dụng hệ thống nhiệm vụ của bạn để chạy một số tác vụ, thì bây giờ tôi phụ thuộc vào mọi dịch vụ trong hệ thống của bạn, ngay cả khi tôi chỉ sử dụng một vài hoặc thậm chí không có gì cả.

Đây ServiceBundlekhông thực sự là SRP vì nó cần biết về mọi dịch vụ trong hệ thống của bạn. Nó cũng không phải là OCP. Thêm dịch vụ mới có nghĩa là thay đổi đối với ServiceBundlevà thay đổi ServiceBundlecó thể có nghĩa là thay đổi khác nhau đối với các nhiệm vụ ở nơi khác. ServiceBundlekhông tách riêng Giao diện của nó (ISP). Nó có giao diện rộng lớn của tất cả các dịch vụ này và vì nó chỉ là nhà cung cấp cho các dịch vụ đó, chúng tôi có thể xem xét giao diện của nó để bao gồm các giao diện của tất cả các dịch vụ mà nó cung cấp. Nhiệm vụ không còn tuân thủ nghịch đảo phụ thuộc (DIP), bởi vì các phụ thuộc của chúng bị che khuất phía sau ServiceBundle. Điều này cũng không tuân thủ Nguyên tắc tối thiểu về kiến ​​thức (hay còn gọi là Luật của Demeter) bởi vì mọi thứ biết về nhiều thứ hơn là chúng phải làm.

Lựa chọn 4

Trước đây, bạn có rất nhiều vật thể nhỏ có thể hoạt động độc lập. Tùy chọn 4 lấy tất cả các đối tượng này và đập chúng lại với nhau thành một Visitorđối tượng. Đối tượng này hoạt động như một đối tượng thần trên tất cả các nhiệm vụ của bạn. Nó làm giảm các RecurringTaskđối tượng của bạn thành các bóng thiếu máu mà chỉ cần gọi cho khách truy cập. Tất cả các hành vi di chuyển đến Visitor. Cần thay đổi hành vi? Cần thêm một nhiệm vụ mới? Thay đổi Visitor.

Phần khó khăn hơn là, bởi vì tất cả các hành vi khác nhau đều nằm trong một lớp duy nhất, thay đổi một số lực kéo đa hình dọc theo tất cả các hành vi khác. Ví dụ: chúng tôi muốn có hai cách khác nhau để gửi email (họ có thể sử dụng các máy chủ khác nhau không?). Làm thế nào chúng ta sẽ làm điều đó? Chúng tôi có thể tạo IVisitorgiao diện và triển khai mã đó, có khả năng sao chép mã, như #Visit(ClearDiskTask)từ khách truy cập ban đầu của chúng tôi. Sau đó, nếu chúng tôi đưa ra một cách mới để xóa đĩa, chúng tôi phải thực hiện và sao chép lại. Sau đó, chúng tôi muốn cả hai loại thay đổi. Thực hiện và nhân đôi một lần nữa. Hai hành vi khác nhau, khác biệt này được liên kết chặt chẽ.

Có lẽ thay vào đó chúng ta chỉ có thể phân lớp Visitor? Lớp con với hành vi email mới, lớp con với hành vi đĩa mới. Không có sự trùng lặp cho đến nay! Phân lớp với cả hai? Bây giờ một hoặc cái khác cần được nhân đôi (hoặc cả hai nếu đó là sở thích của bạn).

Hãy so sánh với tùy chọn 1: Chúng tôi cần một hành vi email mới. Chúng ta có thể tạo một cái mới RecurringTaskthực hiện hành vi mới, thêm vào các phụ thuộc của nó và thêm nó vào bộ sưu tập các nhiệm vụ trong RecurringTaskScheduler. Chúng tôi thậm chí không cần nói về việc xóa đĩa, vì trách nhiệm đó hoàn toàn ở một nơi khác. Chúng tôi cũng vẫn có đầy đủ các công cụ OO theo ý của chúng tôi. Chúng tôi có thể trang trí nhiệm vụ đó với đăng nhập, ví dụ.

Lựa chọn 1 sẽ cho bạn ít đau nhất, và là cách chính xác nhất để xử lý tình huống này.


Phân tích của bạn về Otion2,3,4 là tuyệt vời! Nó thực sự giúp tôi rất nhiều. Nhưng đối với Option1, tôi sẽ lập luận rằng * SendEmailTask ​​* là một thực thể. Nó có id, nó có mẫu định kỳ và các thông tin hữu ích khác cần được lưu trữ trong db. Tôi nghĩ Andy tóm tắt ý định của tôi tốt. Có lẽ một cái tên như * EMailTaskDefDefs * phù hợp hơn. tôi không muốn làm ô nhiễm thực thể của mình với mã dịch vụ của mình. Euphoric đề cập đến một số vấn đề nếu tôi tiêm dịch vụ vào thực thể. Tôi cũng cập nhật câu hỏi của mình và bao gồm Option5, mà tôi nghĩ là giải pháp tốt nhất cho đến nay.
Sher10ck

@ Sher10ck Nếu bạn đang rút cấu hình cho SendEmailTaskcơ sở dữ liệu của mình, thì cấu hình đó phải là một lớp cấu hình riêng biệt cũng nên được đưa vào SendEmailTask. Nếu bạn đang tạo dữ liệu từ của mình SendEmailTask, bạn nên tạo một đối tượng lưu trữ để lưu trữ trạng thái và đưa dữ liệu đó vào cơ sở dữ liệu của bạn.
cbojar

Tôi cần phải kéo cấu hình từ db, vì vậy bạn có đề nghị tiêm cả hai EMailTaskDefinitionsEmailServicevào SendEmailTaskkhông? Sau đó RecurringTaskScheduler, tôi cần tiêm một cái gì đó như SendEmailTaskRepositorytrách nhiệm của mình là tải định nghĩa và dịch vụ và đưa chúng vào SendEmailTask. Nhưng bây giờ tôi sẽ tranh luận về RecurringTaskSchedulernhu cầu cần biết Kho lưu trữ của mọi nhiệm vụ, như thế nào CleanDiskTaskRepository. Và tôi cần thay đổi RecurringTaskSchedulermỗi khi tôi có một nhiệm vụ mới (để thêm kho lưu trữ vào Trình lập lịch biểu).
Sher10ck

@ Sher10ck Chỉ RecurringTaskSchedulernên biết về khái niệm kho lưu trữ tác vụ tổng quát và a RecurringTask. Bằng cách này, nó có thể phụ thuộc vào trừu tượng. Các kho nhiệm vụ có thể được đưa vào hàm tạo của RecurringTaskScheduler. Sau đó, các kho lưu trữ khác nhau chỉ cần được biết nơi RecurringTaskSchedulerđược khởi tạo (hoặc có thể được ẩn đi trong một nhà máy và được gọi từ đó). Bởi vì nó chỉ phụ thuộc vào sự trừu tượng, RecurringTaskSchedulerkhông cần thay đổi với mỗi tác vụ mới. Đó là bản chất của nghịch đảo phụ thuộc.
cbojar

3

Bạn đã xem qua các thư viện hiện có, ví dụ như lò xo thạch anh hoặc lô lò xo (Tôi không chắc thứ gì phù hợp với nhu cầu của bạn nhất)?

Cho câu hỏi của bạn:

Tôi giả sử vấn đề là, bạn muốn duy trì một số siêu dữ liệu cho tác vụ theo cách đa hình, do đó, một tác vụ e-mail có địa chỉ e-mail được gán, tác vụ nhật ký ở cấp độ nhật ký, v.v. Bạn có thể lưu trữ một danh sách những người trong bộ nhớ hoặc trong cơ sở dữ liệu của bạn nhưng để tách biệt những lo ngại mà bạn không muốn thực thể bị ô nhiễm với mã dịch vụ.

Giải pháp đề xuất của tôi:

Tôi sẽ tách phần chạy và phần dữ liệu của tác vụ, để có ví dụ TaskDefinitionvà a TaskRunner. TaskDefDef có tham chiếu đến TaskRunner hoặc một nhà máy tạo ra một (ví dụ: nếu một số thiết lập được yêu cầu như máy chủ smtp). Nhà máy là một công cụ cụ thể - nó chỉ có thể xử lý EMailTaskDefinitions và chỉ trả về các thể hiện của EMailTaskRunners. Theo cách này, nó là OO nhiều hơn và thay đổi an toàn - nếu bạn giới thiệu một loại nhiệm vụ mới, bạn phải giới thiệu một nhà máy cụ thể mới (hoặc tái sử dụng một nhà máy), nếu bạn không thể biên dịch.

Bằng cách này, bạn sẽ kết thúc với một phụ thuộc: lớp thực thể -> lớp dịch vụ và quay lại, bởi vì Người chạy cần thông tin được lưu trữ trong thực thể và có thể muốn cập nhật trạng thái của nó trong DB.

Bạn có thể phá vỡ vòng tròn bằng cách sử dụng một nhà máy chung, trong đó có một TaskDefDef và trả về một TaskRunner cụ thể , nhưng điều đó sẽ đòi hỏi rất nhiều if. Bạn có thể sử dụng sự phản chiếu để tìm một người chạy có tên tương tự như định nghĩa của bạn, nhưng hãy thận trọng phương pháp này có thể tốn một số hiệu suất và có thể dẫn đến lỗi thời gian chạy.

PS tôi đang giả sử Java ở đây. Tôi nghĩ nó tương tự trong .net. Vấn đề chính ở đây là ràng buộc kép.

Theo mẫu khách truy cập

Tôi nghĩ rằng nó được dự định sử dụng để trao đổi một thuật toán cho các loại đối tượng dữ liệu khác nhau trong thời gian chạy, hơn là cho các mục đích ràng buộc kép thuần túy. Ví dụ: nếu bạn có các loại bảo hiểm khác nhau và các loại tính toán khác nhau, ví dụ vì các quốc gia khác nhau yêu cầu nó. Sau đó, bạn chọn một phương pháp tính toán cụ thể và áp dụng nó trên một số bảo hiểm.

Trong trường hợp của bạn, bạn sẽ chọn một chiến lược nhiệm vụ cụ thể (ví dụ email) và áp dụng nó cho tất cả các nhiệm vụ của mình, điều này là sai vì không phải tất cả chúng đều là nhiệm vụ email.

PS Tôi đã không kiểm tra nó, nhưng tôi nghĩ Tùy chọn 4 của bạn cũng không hoạt động, bởi vì nó lại liên kết đôi.


Bạn tóm tắt ý định của tôi thực sự tốt, thx! Tôi muốn phá vỡ vòng tròn. Bởi vì để cho TaskDefiniton giữ một tham chiếu đến TaskRunner hoặc nhà máy có vấn đề tương tự như Option1. Tôi coi nhà máy hoặc TaskRunner là dịch vụ. Nếu các nhu cầu của TaskDefDef giữ tham chiếu đến chúng, bạn có thể đưa dịch vụ vào TaskDefDef hoặc sử dụng một số phương thức tĩnh mà tôi đang cố gắng tránh.
Sher10ck

1

Tôi hoàn toàn không đồng ý với bài báo đó. Các dịch vụ (cụ thể là "API" của chúng) là bên quan trọng của Miền doanh nghiệp và như vậy sẽ tồn tại trong Mô hình miền. Và không có vấn đề với các thực thể trong lĩnh vực kinh doanh tham chiếu một cái gì đó khác trong cùng một lĩnh vực kinh doanh.

Khi X gửi mail cho Y.

Là một quy tắc kinh doanh. Và để làm điều đó, dịch vụ gửi thư là cần thiết. Và thực thể xử lý When Xnên biết về dịch vụ này.

Nhưng có một số vấn đề với việc thực hiện. Nó phải được minh bạch cho người dùng của thực thể, rằng thực thể đó đang sử dụng một dịch vụ. Vì vậy, thêm dịch vụ trong constructor không phải là một điều tốt. Đây cũng là một vấn đề, khi bạn giải tuần tự hóa thực thể khỏi cơ sở dữ liệu, bởi vì bạn cần đặt cả dữ liệu của thực thể và phiên bản dịch vụ. Giải pháp tốt nhất tôi có thể nghĩ đến là sử dụng thuộc tính tiêm sau khi thực thể được tạo. Có thể buộc mỗi phiên bản mới được tạo của bất kỳ thực thể nào phải trải qua phương thức "khởi tạo" để tiêm tất cả các thực thể mà thực thể cần.


Bài viết nào bạn đang đề cập đến mà bạn không đồng ý? Tuy nhiên, quan điểm thú vị về mô hình miền. Có lẽ bạn có thể thấy nó như vậy, tuy nhiên, mọi người thường tránh trộn các dịch vụ vào các thực thể, bởi vì nó sẽ tạo ra một khớp nối chặt chẽ rất sớm.
Andy

@Andy Một Sher10ck tham chiếu trong câu hỏi của mình. Và tôi không thấy nó sẽ tạo ra một khớp nối chặt chẽ như thế nào. Bất kỳ mã viết xấu có thể giới thiệu khớp nối chặt chẽ.
Euphoric

1

Đó là một câu hỏi tuyệt vời và một vấn đề thú vị. Tôi đề nghị bạn nên sử dụng kết hợp các mẫu Chuỗi trách nhiệm và mô hình công văn kép (ví dụ mẫu ở đây ).

Đầu tiên cho phép xác định hệ thống phân cấp nhiệm vụ. Lưu ý rằng bây giờ có nhiều runphương thức để thực hiện Công văn kép.

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

Tiếp theo hãy xác định Servicethứ bậc. Chúng tôi sẽ sử dụng Services để tạo thành Chuỗi trách nhiệm.

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

Phần cuối cùng là phần RecurringTaskSchedulerphối hợp quá trình tải và chạy.

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

Bây giờ, đây là ứng dụng ví dụ minh họa hệ thống.

public class App {

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

Chạy các đầu ra ứng dụng:

EmailService chạy SendEmailTask với nội dung 'ở đây có email đầu tiên'
EmailService chạy SendEmailTask với nội dung 'ở đây là email thứ hai'
ExecuteService chạy ExecuteTask với nội dung '/ root / python'
ExecuteService chạy ExecuteTask với nội dung '/ bin / cat'
EmailService chạy SendEmailTask với nội dung 'ở đây là email thứ ba'
ExecuteService chạy ExecuteTask với nội dung '/ bin / grep'


Tôi có thể có rất nhiều Nhiệm vụ . Mỗi lần tôi thêm một Tác vụ mới , tôi cần thay đổi RecurringTask và tôi cũng cần thay đổi tất cả các lớp con của nó, bởi vì tôi cần thêm một chức năng mới như chạy boolean trừu tượng công khai (OtherService otherService) . Tôi nghĩ Option4, mẫu khách truy cập cũng thực hiện công văn kép có cùng một vấn đề.
Sher10ck

Điểm tốt. Tôi đã chỉnh sửa câu trả lời của mình để các phương thức chạy (dịch vụ) được xác định trong RecurringTask và trả về false theo mặc định. Theo cách này, khi bạn cần thêm một lớp nhiệm vụ khác, bạn không cần phải chạm vào các nhiệm vụ anh chị em.
iluwatar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.