2008-09-29 20 views
9

Làm cách nào để tránh các phụ thuộc vòng tròn khi bạn đang thiết kế hai lớp với mối quan hệ của nhà sản xuất/người tiêu dùng? Ở đây ListenerImpl cần một tham chiếu đến Broadcaster để đăng ký/unregister chính nó, và Broadcaster cần một tham chiếu trở lại để nghe để gửi tin nhắn. Ví dụ này là trong Java nhưng nó có thể áp dụng cho bất kỳ ngôn ngữ OO nào.Làm thế nào để tránh các phụ thuộc vòng tròn khi sử dụng các cuộc gọi lại?

public interface Listener { 
    void callBack(Object arg); 
} 
public class ListenerImpl implements Listener { 
    public ListenerImpl(Broadcaster b) { b.register(this); } 
    public void callBack(Object arg) { ... } 
    public void shutDown() { b.unregister(this); } 
} 
public class Broadcaster { 
    private final List listeners = new ArrayList(); 
    public void register(Listener lis) { listeners.add(lis); } 
    public void unregister(Listener lis) {listeners.remove(lis); } 
    public void broadcast(Object arg) { for (Listener lis : listeners) { lis.callBack(arg); } } 
} 

Trả lời

8

Tôi không thấy đó là phụ thuộc vòng tròn.

Trình nghe phụ thuộc vào không có gì.

ListenerImpl tùy thuộc vào Trình nghe và đài phát sóng

Trình phát sóng phụ thuộc vào Trình nghe.

 Listener 
    ^ ^
    /  \ 
    /   \ 
Broadcaster <-- ListenerImpl 

Tất cả các mũi tên kết thúc tại Trình nghe. Không có chu kỳ. Vì vậy, tôi nghĩ bạn ổn.

+0

Nhưng vẫn có một chu kỳ tài liệu tham khảo - đài truyền hình có một tham chiếu đối tượng ListenerImpl cụ thể mặc dù kiểu tham chiếu là kiểu giao diện Listener. Không một chu trình tham chiếu hàm ý một chu trình phụ thuộc? –

+1

Không thực sự là một cách để loại bỏ chu trình tham chiếu. Đó là loại yêu cầu cho loại điều này. Chu kỳ tham chiếu không thực sự là vấn đề trong những ngày này, vì bất kỳ bộ thu gom rác tốt nào cũng đều xử lý chúng tốt. Đó là chu kỳ phụ thuộc mà bạn cần phải lo lắng. – Herms

+0

@sk: để xác định 'chu kỳ', trước tiên bạn phải xác định 'phụ thuộc': không có chu kỳ biên dịch. Có _is_ một chu kỳ thời gian chạy nếu tất cả các tham chiếu là bằng nhau, và ai đó cần phải phá vỡ chu kỳ khi nó không còn cần thiết (loại bỏ người nghe) – xtofl

0

Tôi không phải là dev java, nhưng một cái gì đó như thế này:

public class ListenerImpl implements Listener { 
    public Foo() {} 
    public void registerWithBroadcaster(Broadcaster b){ b.register(this); isRegistered = true;} 
    public void callBack(Object arg) { if (!isRegistered) throw ... else ... } 
    public void shutDown() { isRegistered = false; } 
} 

public class Broadcaster { 
    private final List listeners = new ArrayList(); 
    public void register(Listener lis) { listeners.add(lis); } 
    public void unregister(Listener lis) {listeners.remove(lis); } 
    public void broadcast(Object arg) { for (Listener lis : listeners) { if (lis.isRegistered) lis.callBack(arg) else unregister(lis); } } 
} 
7

Bất kỳ ngôn ngữ OOP? ĐƯỢC. Đây là một phiên bản mười phút trong CLOS.

Broadcasting khuôn khổ

(defclass broadcaster() 
    ((listeners :accessor listeners 
       :initform '()))) 

(defgeneric add-listener (broadcaster listener) 
    (:documentation "Add a listener (a function taking one argument) 
    to a broadcast's list of interested parties")) 

(defgeneric remove-listener (broadcaster listener) 
    (:documentation "Reverse of add-listener")) 

(defgeneric broadcast (broadcaster object) 
    (:documentation "Broadcast an object to all registered listeners")) 

(defmethod add-listener (broadcaster listener) 
    (pushnew listener (listeners broadcaster))) 

(defmethod remove-listener (broadcaster listener) 
    (let ((listeners (listeners broadcaster))) 
    (setf listeners (remove listener listeners)))) 

(defmethod broadcast (broadcaster object) 
    (dolist (listener (listeners broadcaster)) 
    (funcall listener object))) 

Ví dụ lớp con

(defclass direct-broadcaster (broadcaster) 
    ((latest-broadcast :accessor latest-broadcast) 
    (latest-broadcast-p :initform nil)) 
    (:documentation "I broadcast the latest broadcasted object when a new listener is added")) 

(defmethod add-listener :after ((broadcaster direct-broadcaster) listener) 
    (when (slot-value broadcaster 'latest-broadcast-p) 
    (funcall listener (latest-broadcast broadcaster)))) 

(defmethod broadcast :after ((broadcaster direct-broadcaster) object) 
    (setf (slot-value broadcaster 'latest-broadcast-p) t) 
    (setf (latest-broadcast broadcaster) object)) 

Ví dụ đang

Lisp> (let ((broadcaster (make-instance 'broadcaster))) 
     (add-listener broadcaster 
         #'(lambda (obj) (format t "I got myself a ~A object!~%" obj))) 
     (add-listener broadcaster 
         #'(lambda (obj) (format t "I has object: ~A~%" obj))) 
     (broadcast broadcaster 'cheezburger)) 

I has object: CHEEZBURGER 
I got myself a CHEEZBURGER object! 

Lisp> (defparameter *direct-broadcaster* (make-instance 'direct-broadcaster)) 
     (add-listener *direct-broadcaster* 
        #'(lambda (obj) (format t "I got myself a ~A object!~%" obj))) 
     (broadcast *direct-broadcaster* 'kitty) 

I got myself a KITTY object! 

Lisp> (add-listener *direct-broadcaster* 
        #'(lambda (obj) (format t "I has object: ~A~%" obj))) 

I has object: KITTY 

Thật không may, Lisp giải quyết hầu hết các vấn đề mẫu thiết kế (như bạn) bằng cách loại bỏ sự cần thiết cho họ.

4

Ngược lại với câu trả lời của Herms, tôi làm xem vòng lặp. Nó không phải là một vòng lặp phụ thuộc, nó là một vòng lặp tham chiếu: LI giữ đối tượng B, đối tượng B nắm giữ (một mảng của) đối tượng LI (s). Chúng không miễn phí một cách dễ dàng và cần phải cẩn thận để đảm bảo chúng miễn phí khi có thể.

Một cách giải quyết đơn giản là để đối tượng LI giữ một WeakReference cho đài truyền hình. Về mặt lý thuyết, nếu đài truyền hình đã biến mất, không có gì để đăng ký lại, vì vậy, đăng ký hủy đăng ký của bạn sẽ chỉ đơn giản là kiểm tra xem có một đài truyền hình không đăng ký hay không và làm như vậy nếu có.

+0

Tham khảo vòng lặp không thực sự là một vấn đề. Bộ thu gom rác của AFAIK java nên xử lý nó tốt. Tôi không nghĩ rằng bạn thực sự đạt được bất cứ điều gì từ việc sử dụng một tài liệu tham khảo yếu. – Herms

+0

@ Herms: bạn đang nhận được hư hỏng bởi bộ sưu tập rác tự động :) Tôi thấy nó là một vấn đề tốt _very_: không bao giờ chỉ đơn giản là giả sử quyền sở hữu là hoàn toàn mà không cho nó một ý nghĩ! +1 – xtofl

0

Sử dụng tham chiếu yếu để phá vỡ chu kỳ.

Xem this answer.

0

Dưới đây là một ví dụ trong Lua (Tôi sử dụng riêng của mình Oop lib tại đây, xem tham chiếu đến 'Đối tượng' trong mã).

Giống như trong ví dụ về của Mikael Jansson, bạn có thể sử dụng trực tiếp các chức năng, loại bỏ nhu cầu xác định người nghe (lưu ý việc sử dụng '...', Đó là varargs Lua của):

Broadcaster = Object:subclass() 

function Broadcaster:initialize() 
    self._listeners = {} 
end 

function Broadcaster:register(listener) 
    self._listeners[listener] = true 
end 

function Broadcaster:unregister(listener) 
    self._listeners[listener] = nil 
end 
function Broadcaster:broadcast(...) 
    for listener in pairs(self._listeners) do 
     listener(...) 
    end 
end 

Bám sát thực hiện của bạn, sau đây là một ví dụ mà có thể được viết bằng bất kỳ ngôn ngữ năng động, tôi đoán:

--# Listener 
Listener = Object:subclass() 
function Listener:callback(arg) 
    self:subclassResponsibility() 
end 

--# ListenerImpl 
function ListenerImpl:initialize(broadcaster) 
    self._broadcaster = broadcaster 
    broadcaster:register(this) 
end 
function ListenerImpl:callback(arg) 
    --# ... 
end 
function ListenerImpl:shutdown() 
    self._broadcaster:unregister(self) 
end 

--# Broadcaster 
function Broadcaster:initialize() 
    self._listeners = {} 
end 
function Broadcaster:register(listener) 
    self._listeners[listener] = true 
end 
function Broadcaster:unregister(listener) 
    self._listeners[listener] = nil 
end 
function Broadcaster:broadcast(arg) 
    for listener in pairs(self._listeners) do 
     listener:callback(arg) 
    end 
end