Khi nào tôi nên sử dụng lập trình dựa trên sự kiện?


65

Tôi đã chuyển các cuộc gọi lại hoặc chỉ kích hoạt các chức năng từ các chức năng khác trong các chương trình của mình để thực hiện mọi thứ sau khi hoàn thành nhiệm vụ. Khi một cái gì đó kết thúc, tôi kích hoạt chức năng trực tiếp:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

Nhưng tôi đã đọc về nhiều chiến lược khác nhau trong lập trình, và một chiến lược mà tôi hiểu là mạnh mẽ, nhưng chưa được thực hành, là dựa trên sự kiện (tôi nghĩ rằng một phương pháp tôi đọc được gọi là "pub-sub" ):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

Tôi muốn hiểu các điểm mạnh và điểm yếu khách quan của lập trình dựa trên sự kiện, so với việc chỉ gọi tất cả các chức năng của bạn từ bên trong các chức năng khác. Trong các tình huống lập trình nào lập trình dựa trên sự kiện có ý nghĩa để sử dụng?


2
Như một bên, bạn chỉ có thể sử dụng $(document).bind('snow', shovelShow). Không cần phải bọc nó trong một chức năng ẩn danh.
Karl Bielefeldt

4
Bạn cũng có thể quan tâm đến việc tìm hiểu về "lập trình phản ứng", có nhiều điểm chung với lập trình hướng sự kiện.
Eric Lippert

Câu trả lời:


75

Một sự kiện là một thông báo mô tả sự xuất hiện từ quá khứ gần đây.

Một triển khai điển hình của một hệ thống hướng sự kiện sử dụng một bộ điều phối sự kiện và các hàm xử lý (hoặc thuê bao ). Bộ điều phối cung cấp API để xử lý các sự kiện (jQuery bind) và một phương thức để xuất bản một sự kiện tới những người đăng ký của nó ( triggertrong jQuery). Khi bạn đang nói về các sự kiện IO hoặc UI, thường cũng có một vòng lặp sự kiện , phát hiện các sự kiện mới như nhấp chuột và chuyển chúng đến bộ điều phối. Trong vùng đất JS, trình điều phối và vòng lặp sự kiện được trình duyệt cung cấp.

Đối với mã tương tác trực tiếp với người dùng - phản ứng với phím bấm và nhấp chuột - lập trình hướng sự kiện (hoặc một biến thể của nó, chẳng hạn như lập trình phản ứng chức năng ) là gần như không thể tránh khỏi. Bạn, lập trình viên, không biết người dùng sẽ nhấp vào khi nào hoặc ở đâu, do đó, hãy xuống khung GUI hoặc trình duyệt để phát hiện hành động của người dùng trong vòng lặp sự kiện và thông báo mã của bạn. Loại cơ sở hạ tầng này cũng được sử dụng trong các ứng dụng mạng (cf NodeJS).

Ví dụ của bạn, trong đó bạn nêu ra một sự kiện trong mã của mình thay vì gọi hàm trực tiếp có một số sự đánh đổi thú vị hơn, mà tôi sẽ thảo luận dưới đây. Sự khác biệt chính là nhà xuất bản của một sự kiện ( makeItSnow) không chỉ định người nhận cuộc gọi; đó là kết nối ở nơi khác (trong cuộc gọi đến bindtrong ví dụ của bạn). Điều này được gọi là lửa và quên : makeItSnowthông báo với thế giới rằng tuyết đang rơi, nhưng nó không quan tâm ai đang lắng nghe, điều gì xảy ra tiếp theo hoặc khi nó xảy ra - nó chỉ đơn giản là phát thông điệp và phủi bụi.


Vì vậy, cách tiếp cận theo hướng sự kiện sẽ tách người gửi tin nhắn từ người nhận. Một lợi thế này cho bạn là một sự kiện nhất định có thể có nhiều trình xử lý. Bạn có thể liên kết một gritRoadschức năng với sự kiện tuyết của bạn mà không ảnh hưởng đến shovelSnowtrình xử lý hiện có . Bạn có sự linh hoạt trong cách ứng dụng của bạn được nối dây; để tắt một hành vi, bạn chỉ cần xóa bindcuộc gọi thay vì tìm kiếm mã để tìm tất cả các trường hợp của hành vi.

Một ưu điểm khác của lập trình hướng sự kiện là nó cung cấp cho bạn một nơi nào đó để đặt các mối quan tâm xuyên suốt. Bộ điều phối sự kiện đóng vai trò của Người hòa giải và một số thư viện (như Sáng hơn ) sử dụng một đường ống dẫn để bạn có thể dễ dàng cắm các yêu cầu chung như ghi nhật ký hoặc chất lượng dịch vụ.

Tiết lộ đầy đủ: Sáng hơn được phát triển tại Huddle, nơi tôi làm việc.

Ưu điểm thứ ba của việc tách người gửi sự kiện khỏi người nhận là nó mang lại cho bạn sự linh hoạt khi bạn xử lý sự kiện. Bạn có thể xử lý từng loại sự kiện trên luồng của chính nó (nếu bộ điều phối sự kiện của bạn hỗ trợ nó) hoặc bạn có thể đưa các sự kiện được nêu lên lên một nhà môi giới tin nhắn như RabbitMQ và xử lý chúng với quy trình không đồng bộ hoặc thậm chí xử lý hàng loạt qua đêm. Người nhận sự kiện có thể ở một quy trình riêng hoặc trên một máy riêng. Bạn không phải thay đổi mã làm tăng sự kiện để làm điều này! Đây là ý tưởng lớn đằng sau kiến ​​trúc "microservice": các dịch vụ tự trị giao tiếp bằng các sự kiện, với phần mềm trung gian nhắn tin là xương sống của ứng dụng.

Đối với một ví dụ khá khác biệt về phong cách hướng sự kiện, hãy tìm đến thiết kế hướng theo miền , trong đó các sự kiện miền được sử dụng để giúp tách biệt các tập hợp. Ví dụ: hãy xem xét một cửa hàng trực tuyến giới thiệu các sản phẩm dựa trên lịch sử mua hàng của bạn. Cần Customerphải cập nhật lịch sử mua hàng khi ShoppingCartđược trả tiền. Tổng ShoppingCarthợp có thể thông báo cho Customerbằng cách đưa ra một CheckoutCompletedsự kiện; các Customersẽ được cập nhật trong một giao dịch riêng biệt để đáp ứng với sự kiện này.


Nhược điểm chính của mô hình hướng sự kiện này là không xác định. Bây giờ khó hơn để tìm mã xử lý sự kiện vì bạn không thể điều hướng đến nó bằng IDE của mình; bạn phải tìm ra nơi sự kiện bị ràng buộc trong cấu hình và hy vọng rằng bạn đã tìm thấy tất cả các trình xử lý. Có nhiều thứ để giữ trong đầu của bạn bất cứ lúc nào. Các quy ước kiểu mã có thể giúp ở đây (ví dụ: đặt tất cả các cuộc gọi bindvào một tệp). Vì lợi ích của bạn, điều quan trọng là chỉ sử dụng một bộ điều phối sự kiện và sử dụng nó một cách nhất quán.

Một nhược điểm khác là khó tái cấu trúc các sự kiện. Nếu bạn cần thay đổi định dạng của một sự kiện, bạn cũng cần thay đổi tất cả những người nhận. Điều này trở nên trầm trọng hơn khi những người đăng ký một sự kiện ở trên các máy khác nhau, bởi vì bây giờ bạn cần đồng bộ hóa các bản phát hành phần mềm!

Trong một số trường hợp nhất định, hiệu suất có thể là một mối quan tâm. Khi xử lý một tin nhắn, người điều phối phải:

  1. Tra cứu các trình xử lý chính xác trong một số cấu trúc dữ liệu.
  2. Xây dựng một đường ống xử lý tin nhắn cho mỗi xử lý. Điều này có thể liên quan đến một loạt các phân bổ bộ nhớ.
  3. Tự động gọi các trình xử lý (có thể sử dụng sự phản chiếu nếu ngôn ngữ yêu cầu).

Điều này chắc chắn là chậm hơn so với một cuộc gọi chức năng thông thường, chỉ liên quan đến việc đẩy một khung mới trên ngăn xếp. Tuy nhiên, tính linh hoạt mà kiến ​​trúc hướng sự kiện mang lại cho bạn giúp việc cách ly và tối ưu hóa mã chậm dễ dàng hơn nhiều. Có khả năng gửi công việc tới bộ xử lý không đồng bộ là một chiến thắng lớn ở đây, vì nó cho phép bạn phục vụ yêu cầu ngay lập tức trong khi công việc khó khăn được xử lý ở chế độ nền. Trong mọi trường hợp, nếu bạn tương tác với DB hoặc vẽ nội dung trên màn hình thì chi phí của IO sẽ hoàn toàn chi phí cho việc xử lý tin nhắn. Đó là một trường hợp tránh tối ưu hóa sớm.


Tóm lại, các sự kiện là một cách tuyệt vời để xây dựng phần mềm kết nối lỏng lẻo, nhưng chúng không phải là không có chi phí. Nó sẽ là một sai lầm, ví dụ, để thay thế mọi cuộc gọi chức năng trong ứng dụng của bạn bằng một sự kiện. Sử dụng các sự kiện để làm cho các bộ phận kiến ​​trúc có ý nghĩa.


2
Câu trả lời này giống như câu trả lời của 5377 mà tôi đã chọn là chính xác; Tôi đang thay đổi lựa chọn của mình để đánh dấu cái này vì nó sẽ phát triển thêm.
Viziionary

1
Là tốc độ một bất lợi đáng kể của mã hướng sự kiện? Có vẻ như nó có thể, nhưng tôi không biết nhiều.
raptortech97

1
@ raptortech97 chắc chắn là có thể. Đối với mã cần đặc biệt nhanh, có lẽ bạn sẽ muốn tránh gửi các sự kiện trong một vòng lặp bên trong; may mắn thay, trong những tình huống như vậy, nó thường xác định rõ những gì bạn cần làm, vì vậy bạn không cần linh hoạt thêm các sự kiện (hoặc xuất bản / đăng ký hoặc quan sát, là các cơ chế tương đương với các thuật ngữ khác nhau).
Jules

1
Cũng lưu ý rằng có một số ngôn ngữ (ví dụ Erlang) được xây dựng xung quanh mô hình diễn viên trong đó mọi thứ đều là thông điệp (sự kiện). Trong trường hợp này, trình biên dịch có thể quyết định thực hiện các thông báo / sự kiện như các cuộc gọi chức năng trực tiếp hoặc như là giao tiếp.
Brendan

1
Đối với "hiệu suất" tôi nghĩ rằng chúng ta cần phân biệt giữa hiệu suất đơn luồng và khả năng mở rộng. Tin nhắn / sự kiện có thể tệ hơn đối với hiệu suất đơn luồng (nhưng có thể được chuyển đổi thành các lệnh gọi hàm với chi phí bổ sung bằng 0 và không tệ hơn) và về khả năng mở rộng, nó vượt trội về mọi mặt (ví dụ như có thể cải thiện hiệu suất lớn trên đa hiện đại -CPU và các hệ thống "nhiều CPU" trong tương lai).
Brendan

25

Lập trình dựa trên sự kiện được sử dụng khi chương trình không kiểm soát chuỗi sự kiện mà nó thực hiện. Thay vào đó, luồng chương trình được điều khiển bởi một quy trình bên ngoài, chẳng hạn như người dùng (ví dụ GUI), hệ thống khác (ví dụ: máy khách / máy chủ) hoặc quy trình khác (ví dụ RPC).

Ví dụ, một kịch bản xử lý hàng loạt biết những gì nó cần làm để nó chỉ thực hiện nó. Nó không dựa trên sự kiện.

Một trình xử lý văn bản nằm ở đó và chờ người dùng bắt đầu nhập. Nhấn phím là các sự kiện kích hoạt chức năng để cập nhật bộ đệm tài liệu nội bộ. Chương trình không thể biết những gì bạn muốn gõ, vì vậy nó phải được điều khiển theo sự kiện.

Hầu hết các chương trình GUI được điều khiển theo sự kiện vì chúng được xây dựng xung quanh sự tương tác của người dùng. Tuy nhiên, các chương trình dựa trên sự kiện không giới hạn ở GUI, đó đơn giản là ví dụ quen thuộc nhất với hầu hết mọi người. Các máy chủ web chờ khách hàng kết nối và làm theo một thành ngữ tương tự. Quá trình nền trên máy tính của bạn cũng có thể phản ứng với các sự kiện. Ví dụ: trình quét vi-rút theo yêu cầu có thể nhận được một sự kiện từ HĐH liên quan đến tệp mới được tạo hoặc cập nhật, sau đó quét tệp đó để tìm vi-rút.


18

Trong một ứng dụng dựa trên Sự kiện, khái niệm Trình lắng nghe Sự kiện sẽ cung cấp cho bạn khả năng viết các ứng dụng được kết nối lỏng lẻo hơn .

Ví dụ, mô-đun hoặc trình cắm của bên thứ ba có thể xóa một bản ghi khỏi cơ sở dữ liệu và sau đó kích hoạt receordDeletedsự kiện và để phần còn lại cho người nghe sự kiện thực hiện công việc của họ. Mọi thứ sẽ hoạt động tốt, mặc dù mô-đun kích hoạt thậm chí không biết ai đang nghe sự kiện cụ thể này hay điều gì sẽ xảy ra tiếp theo.


6

Một tương tự đơn giản tôi muốn thêm đã giúp tôi:

Hãy nghĩ về các thành phần (hoặc đối tượng) của ứng dụng của bạn như một nhóm bạn bè lớn trên Facebook.

Khi một trong những người bạn của bạn muốn nói với bạn điều gì đó, họ có thể gọi trực tiếp cho bạn hoặc đăng nó lên tường Facebook của họ. Khi họ đăng nó lên Facebook của họ, sau đó bất cứ ai cũng có thể nhìn thấy nó và phản ứng với nó, nhưng rất nhiều người không. Đôi khi, một điều quan trọng là mọi người có thể sẽ cần phải phản ứng với nó, như "Chúng tôi đang có em bé!" hoặc "Ban nhạc tương tự đang thực hiện một buổi hòa nhạc bất ngờ tại quán bar Drunkin 'Clam!". Trong trường hợp cuối cùng, những người bạn còn lại có thể sẽ cần phải phản ứng với nó, đặc biệt nếu họ quan tâm đến ban nhạc đó.

Nếu bạn của bạn muốn giữ bí mật giữa bạn và họ, họ có thể sẽ không đăng nó lên tường Facebook của họ, họ sẽ gọi trực tiếp cho bạn và nói với bạn. Hình dung một kịch bản mà bạn nói với một cô gái bạn thích mà bạn muốn gặp cô ấy tại một nhà hàng để hẹn hò. Thay vì gọi trực tiếp cho cô ấy và hỏi cô ấy, bạn đăng nó lên tường Facebook của bạn để tất cả bạn bè của bạn nhìn thấy. Điều này hoạt động, nhưng nếu bạn có một người yêu cũ ghen tuông, thì cô ấy có thể thấy điều đó và xuất hiện tại nhà hàng để hủy hoại ngày của bạn.

Khi quyết định có hay không xây dựng trong trình lắng nghe sự kiện để thực hiện một cái gì đó, hãy nghĩ về sự tương tự này. Có thành phần này cần phải đưa doanh nghiệp của họ ra khỏi đó cho bất cứ ai nhìn thấy? Hay họ cần gọi trực tiếp cho ai đó? Mọi thứ có thể trở nên lộn xộn khá dễ dàng vì vậy hãy cẩn thận.


0

Sự tương tự sau đây có thể giúp bạn hiểu lập trình I / O theo hướng sự kiện bằng cách vẽ một đường song song với hàng chờ tại bàn Lễ tân của Bác sĩ.

Chặn I / O giống như, nếu bạn đang đứng trong hàng đợi, nhân viên tiếp tân yêu cầu một anh chàng trước mặt bạn điền vào mẫu đơn và cô ấy đợi cho đến khi anh ta kết thúc. Bạn phải đợi đến lượt của mình cho đến khi anh chàng hoàn thành mẫu của mình, điều này đang chặn.

Nếu anh chàng độc thân mất 3 phút để điền vào, anh chàng thứ 10 phải đợi đến 30 phút. Bây giờ để giảm thời gian chờ đợi thứ 10 này, giải pháp sẽ là, tăng số lượng nhân viên tiếp tân, rất tốn kém. Đây là những gì xảy ra trong các máy chủ web truyền thống. Nếu bạn yêu cầu thông tin người dùng, yêu cầu tiếp theo của người dùng khác sẽ đợi cho đến khi hoạt động hiện tại, tìm nạp từ Cơ sở dữ liệu, được hoàn tất. Điều này làm tăng "thời gian đáp ứng" của yêu cầu thứ 10 và nó tăng theo cấp số nhân cho người dùng thứ n. Để tránh các máy chủ web truyền thống này tạo ra luồng (tương đương với số lượng nhân viên tiếp tân tăng lên) cho mỗi yêu cầu, ví dụ, về cơ bản, nó tạo ra một bản sao của máy chủ cho mỗi yêu cầu, tiêu tốn nhiều CPU vì mỗi yêu cầu sẽ cần một hệ điều hành chủ đề. Để mở rộng ứng dụng,

Hướng sự kiện : Cách tiếp cận khác để tăng quy mô "thời gian phản hồi" của hàng đợi là sử dụng phương pháp tiếp cận theo hướng sự kiện, trong đó anh chàng trong hàng đợi sẽ được trao mẫu, yêu cầu điền vào và hoàn tất. Do đó nhân viên tiếp tân luôn có thể yêu cầu. Đây chính xác là những gì javascript đã làm từ khi nó bắt đầu. Trong trình duyệt, javascript sẽ phản hồi với sự kiện nhấp chuột của người dùng, cuộn, vuốt hoặc tìm nạp cơ sở dữ liệu, v.v. Điều này có thể có trong javascript, bởi vì javascript coi các hàm là các đối tượng hạng nhất và chúng có thể được truyền dưới dạng tham số cho các hàm khác (được gọi là gọi lại) và có thể được gọi khi hoàn thành nhiệm vụ cụ thể. Đây là những gì chính xác node.js làm trên máy chủ. Bạn có thể tìm thêm thông tin về lập trình hướng sự kiện và chặn i / o, trong ngữ cảnh của nút tại đây

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.