2010-10-24 29 views
18

Tôi hiện đang vật lộn với một vấn đề phụ thuộc vòng tròn khi thiết kế các lớp học của tôi.Thiết kế OO và phụ thuộc vòng tròn

Kể từ khi tôi đọc về số Anemic Domain Model (điều mà tôi đã làm mọi lúc), tôi thực sự đã cố gắng tránh xa việc tạo ra các đối tượng miền chỉ là "getters of getters and setters" và trở về nguồn gốc OO của tôi.

Tuy nhiên, vấn đề bên dưới là vấn đề mà tôi gặp phải rất nhiều và tôi không chắc mình nên giải quyết nó như thế nào.

Giả sử chúng tôi có một lớp học Nhóm, có nhiều người chơi Người chơi. Nó không quan trọng môn thể thao này là gì :) Một nhóm có thể thêm và loại bỏ người chơi, theo cách giống như một người chơi có thể rời khỏi một đội và tham gia một nhóm khác.

Vì vậy, chúng tôi có đội ngũ, trong đó có một danh sách các cầu thủ:

public class Team { 

    private List<Player> players; 

    // snip. 

    public void removePlayer(Player player) { 
     players.remove(player); 
     // Do other admin work when a player leaves 
    } 
} 

Sau đó, chúng ta có Player, trong đó có một tham chiếu đến Đội bóng:

public class Player { 
    private Team team; 

    public void leaveTeam() { 
     team = null; 
     // Do some more player stuff... 
    } 
} 

Người ta có thể giả định rằng cả hai phương pháp (loại bỏ và rời khỏi) có logic theo miền cụ thể cần phải chạy bất cứ khi nào một nhóm loại bỏ một người chơi và một người chơi rời khỏi một nhóm. Vì vậy, suy nghĩ đầu tiên của tôi là khi một Đội đá một cầu thủ, removePlayer (...) cũng nên gọi phương thức player.leaveTeam() ...

Nhưng sau đó những gì nếu chơi là lái khởi hành - nên phương thức leaveTeam() gọi team.removePlayer (này)? Không phải không tạo ra một vòng lặp vô hạn!

Trong quá khứ, tôi vừa tạo các đối tượng địa lý "câm" POJO và có một lớp dịch vụ thực hiện công việc. Nhưng ngay cả bây giờ tôi vẫn còn có vấn đề rằng: để tránh phụ thuộc vòng tròn, lớp dịch vụ vẫn có liên kết nó tất cả cùng nhau - ví dụ:

public class SomeService { 

    public void leave(Player player, Team team) { 

     team.removePlayer(player); 
     player.leaveTeam(); 

    } 

} 

Tôi có phức tạp hơn điều này? Có lẽ tôi đang thiếu một số lỗi thiết kế rõ ràng. Bất kì phản hồi nào cũng sẽ được đánh giá cao.


Cảm ơn tất cả các câu trả lời. Tôi chấp nhận giải pháp của Grodriguez vì nó là rõ ràng nhất (không thể tin rằng nó không xảy ra với tôi) và dễ thực hiện. Tuy nhiên, DecaniBass thực hiện một điểm tốt. Trong tình huống tôi đang mô tả, người chơi có thể rời khỏi một đội (và nhận thức được liệu anh ấy có đang ở trong một đội hay không) cũng như cả đội lái xe. Nhưng tôi đồng ý với quan điểm của bạn và tôi không thích ý tưởng rằng có hai "điểm vào" trong quá trình này. Cảm ơn một lần nữa.

Trả lời

14

Bạn có thể phá vỡ phụ thuộc vòng tròn bằng cách thêm nhân viên bảo vệ để kiểm tra xem nhóm có còn người chơi/người chơi vẫn còn trong nhóm hay không.Ví dụ:

Trong lớp Team:

public void removePlayer(Player player) { 
    if (players.contains(player)) 
    { 
     players.remove(player); 
     player.leaveTeam(); 
     // Do other admin work when a player leaves 
    } 
} 

Trong lớp Player:

public void leaveTeam() { 
    if (team != null) 
    { 
     team.removePlayer(this); 
     team = null; 
     // Do some more player stuff.. 
    } 
} 
+2

Có thể chỉ là tôi, nhưng tôi thích sử dụng nếu ... khác càng ít càng tốt. Tôi đã nhận thấy nó làm cho mã một chút ít bảo trì –

+4

players.remove() sẽ trả về true nếu bộ sưu tập đã được thay đổi; không cần phải làm .contains(). – KarlP

+0

@KarlP: Tôi biết, nhưng tôi nghĩ việc kiểm tra rõ ràng sẽ làm cho logic rõ ràng hơn. – Grodriguez

1
public void removePlayer(Player player) { 
    if (players.contains(player)) { 
     players.remove(player); 
     player.leaveTeam(); 
    } 
} 

Ditto bên trong leaveTeam.

2

Idea là để làm công cụ miền liên quan đến các phương pháp khác nhau mà không gọi nhau nhưng không miền liên quan công cụ cho đối tượng của riêng họ, tức là phương pháp của nhóm thực hiện nó cho phương pháp của nhóm và người chơi thực hiện cho người chơi

public class Team { 

    private List<Player> players; 

    public void removePlayer(Player player) { 
     removePlayerFromTeam(player); 
     player.removeFromTeam(); 
    } 
    public void removePlayerFromTeam(Player player) { 
     players.remove(player); 
     //domain stuff 
    } 
} 

public class Player { 
    private Team team; 

    public void removeFromTeam() { 
     team = null; 
     //domain stuff 
    } 
    public void leaveTeam() { 
     team.removePlayerFromTeam(this); 
     removeFromTeam(); 
    } 

} 
+1

Phương thức 'leaveTeam()' sẽ ném NPE khi bạn gọi 'team.removePlayerFromTeam()' sau khi thiết lập 'team = null'. – Grodriguez

+0

Cũng trong giải pháp này, gọi 'player.leaveTeam()' không thực sự loại bỏ trình phát khỏi danh sách của người chơi trong đối tượng nhóm. Tương tự như vậy, gọi 'team.removePlayer()' sẽ không đặt 'team' var thành' null' trong đối tượng trình phát. – Grodriguez

+1

Trong thiết kế này, các phương thức chứa mã miền cụ thể phải là gói riêng và không công khai, tôi nghĩ vậy. Nhưng đó chắc chắn là con đường tôi muốn. – Waldheinz

7

Ben,

Tôi sẽ bắt đầu bằng cách hỏi liệu người chơi có thể tự mình loại bỏ khỏi nhóm hay không. Tôi sẽ nói rằng đối tượng người chơi không biết anh ấy đang ở đội nào (!), Anh ấy là một phần của một đội. Vì vậy, xóa Player#leaveTeam() và thực hiện tất cả các thay đổi của nhóm xảy ra thông qua phương pháp Team#removePlayer().

Trong trường hợp bạn chỉ có một cầu thủ và cần phải loại bỏ nó khỏi đội của mình, sau đó bạn có thể có một phương pháp tra cứu tĩnh trên Đội public static Team findTeam(Player player) ...

Tôi biết điều này là ít thỏa mãn và tự nhiên hơn so với một phương pháp Player#leaveTeam(), nhưng theo kinh nghiệm của tôi, bạn vẫn có thể có một mô hình miền có ý nghĩa.

2 cách tài liệu tham khảo (mẹ -> Trẻ em và từ trẻ> mẹ) thường đầy những thứ khác, nói Garbage Collection, duy trì sự "toàn vẹn tham chiếu", vv

Thiết kế là một thỏa hiệp!

Các vấn đề liên quan