Nguyên tắc trách nhiệm duy nhất Vi phạm?


8

Gần đây tôi đã có một cuộc tranh luận với một nhà phát triển khác về lớp dưới đây:

public class GroupBillingPayment
{
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;
        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        UpdateGroupBilling([Parameters])
    }
}

Tôi tin rằng UpdateGroupBillingkhông nên được gọi bên trong phương thức lưu vì nó vi phạm nguyên tắc trách nhiệm duy nhất. Nhưng, ông nói rằng mỗi lần thanh toán được thực hiện, hóa đơn cần được cập nhật. Do đó đây là cách tiếp cận chính xác.

Câu hỏi của tôi, SRP có bị vi phạm ở đây không? Nếu có, làm thế nào chúng ta có thể tái cấu trúc tốt hơn để cả hai tiêu chí của chúng ta được đáp ứng?


6
Điều này phụ thuộc vào miền vấn đề và kiến ​​trúc của bạn. Dữ liệu và sự kiện được cho là chảy qua ứng dụng của bạn như thế nào? Trách nhiệm của các lớp liên quan là gì? Không có đủ ngữ cảnh trong câu hỏi của bạn để trả lời khách quan này.
amon

7
SRP không phải là một hướng dẫn không ngữ cảnh.
whatsisname

1
Phiên bản cập nhật có một số vấn đề nằm ngoài chủ đề ở đây. Bạn có thể xem xét đăng nó tại codereview.stackexchange.com để nhận một số đề xuất cải tiến ...
Timothy Truckle

1
Áp dụng POAP ! Nếu bạn không hiểu tại sao nguyên tắc tồn tại và cách áp dụng vào tình huống của bạn, thì vẫn chưa có gì để bàn cãi. Và khi bạn làm hiểu mục đích của nguyên tắc, câu trả lời sẽ được nhiều hơn nữa đơn giản .... đối với bạn. Không cho chúng tôi. Chúng tôi không biết cơ sở mã của bạn, tổ chức của bạn hoặc loại hạt cụ thể mà bạn có thể làm việc cùng. ... Tôi là VTC-ing này. Tôi không nghĩ rằng chúng tôi không thể cung cấp cho bạn câu trả lời "đúng". Trừ khi câu trả lời là: Bạn cần hiểu nguyên tắc trước khi tranh luận.
Svidgen

2
Bạn gần như chắc chắn lật đổ điều này. Nếu các yêu cầu nghiệp vụ chỉ ra rằng bạn thực hiện một số loại dọn dẹp hoặc cập nhật như là một phần của quy trình, thì việc dọn dẹp hoặc cập nhật đó là một phần của trách nhiệm, và do đó trách nhiệm vẫn là độc thân.
Robert Harvey

Câu trả lời:


6

Tôi sẽ nhìn nó theo cách này:

Một phương thức nên gọi các phương thức khác (trong cùng hoặc các đối tượng khác) làm cho nó trở thành phương thức tổng hợp hoặc thực hiện "tính toán nguyên thủy" (cùng mức độ trừu tượng).

Trách nhiệm của một phương thức tổng hợp là "gọi các phương thức khác".

Vì vậy, nếu Savephương thức của bạn thực hiện một số "tính toán nguyên thủy" (ví dụ: kiểm tra các giá trị trả về), thì XUÂN có thể bị vi phạm. Nếu "tính toán nguyên thủy" này sống trong một phương thức khác được gọi bởi Savephương thức SRP của bạn không bị vi phạm.


Phiên bản cập nhật của Savephương thức không tuân theo nguyên tắc lớp trừu tượng đơn . Vì đây là vấn đề quan trọng hơn nên bạn trích xuất nó sang một phương pháp mới.

Điều này sẽ chuyển đổi Savethành một phương thức sáng tác . Như đã viết, trách nhiệm của một phương thức tổng hợp là "gọi các phương thức khác". Do đó, gọi UpdateGroupBilling([Parameters])ở đây không phải là vi phạm SRP, mà là quyết định trường hợp kinh doanh.


5
+1, chính xác. Các đơn Trách nhiệm Nguyên tắc không có nghĩa là bạn chỉ có thể bao giờ làm một điều - mà có thể làm cho nó không thể vài điều cùng nhau ở tất cả . Bạn chỉ nên làm một điều ở mức độ trừu tượng hiện tại của bạn .
Kilian Foth

Savephương pháp đã được cập nhật. Vui lòng xem nếu điều này có ích
gvk

2

Trách nhiệm duy nhất có thể được hiểu là chức năng / lớp nên chỉ có một lý do để thay đổi.

Vì vậy, Savephương pháp hiện tại của bạn sẽ vi phạm nguyên tắc đó, bởi vì nó nên được thay đổi bởi nhiều hơn một lý do.
1. Lưu logic đã thay đổi
2. Nếu bạn quyết định cập nhật thứ khác ngoài việc cập nhật nhóm thanh toán

Bạn có thể cấu trúc lại nó bằng cách giới thiệu SaveModelphương thức sẽ chỉ chịu trách nhiệm tiết kiệm. Và giới thiệu một phương pháp khác kết hợp tất cả các hoạt động cần thiết dựa trên yêu cầu của bạn. Vì vậy, bạn kết thúc với hai phương pháp

public void SaveModel(IGroupBillingPayment model)
{
    // only saves model
}

public void Save(IGroupBillingPayment model)
{
    SaveModel(model);
    UpdateGroupBilling([Parameters]);
}

Trường hợp SaveModelphương thức sẽ có trách nhiệm lưu mô hình vào cơ sở dữ liệu và một lý do để thay đổi - nếu lưu "logic" sẽ thay đổi.

Save phương thức hiện có trách nhiệm gọi tất cả các phương thức cần thiết cho quy trình "tiết kiệm" đầy đủ và có một lý do để thay đổi - nếu số lượng phương thức được yêu cầu sẽ thay đổi.

Tôi nghĩ rằng xác nhận có thể được di chuyển bên ngoài SaveModelquá.


0

Trách nhiệm duy nhất là IMHO một khái niệm không dễ hiểu.

Một nguyên tắc đơn giản là:

Khi tôi phải giải thích, những gì một phương thức / lớp làm và phải sử dụng từ »và«, đó là một chỉ báo, rằng một cái gì đó có mùi có thể đang diễn ra .

Một mặt, nó chỉ là một chỉ báo và mặt khác nó hoạt động ngược lại tốt hơn: Nếu có hai điều đang diễn ra, bạn không thể tránh sử dụng từ »và« và vì bạn đang làm hai việc, bạn vi phạm SRP .

Nhưng, điều đó có nghĩa là mặt khác, nếu bạn làm nhiều hơn một điều, bạn đang vi phạm SRP ? Không. Bởi vì nếu không, bạn bị giới hạn trong các vấn đề tầm thường và tầm thường để giải quyết. Bạn sẽ làm tổn thương chính mình nếu bạn quá nghiêm khắc trong việc giải thích.

Một quan điểm khác về SRP là: một mức độ trừu tượng . Miễn là bạn đang đối phó với một mức độ trừu tượng, bạn hầu như ổn.

Tất cả điều đó có nghĩa gì cho câu hỏi của bạn:

Tôi tin rằng UpdategroupBilling không nên được gọi bên trong phương thức lưu vì nó vi phạm nguyên tắc trách nhiệm duy nhất. Nhưng, ông nói rằng mỗi lần thanh toán được thực hiện, hóa đơn cần được cập nhật. Do đó đây là cách tiếp cận chính xác.

Để quyết định, liệu đây có phải là vi phạm SRP hay không , điều cần thiết là phải biết, chuyện gì đang xảy ra trong save()-Method. Nếu phương thức là - như tên - gợi ý có trách nhiệm duy trì mô hình cho cơ sở dữ liệu, thì một cuộc gọi đến UpdateGroupBilling IMHO vi phạm SRP , vì bạn đang mở rộng bối cảnh »tiết kiệm thanh toán«. Bạn sẽ diễn giải nó với »Tôi đang lưu khoản thanh toán và cập nhật hóa đơn nhóm«, như tôi đã nói trước đó - một chỉ báo (có thể) về vi phạm SRP .

Mặt khác, nếu phương thức mô tả một "công thức thanh toán" - hãy xem các bước cần thực hiện trong quy trình - và quyết định: mỗi khi thanh toán kết thúc, nó phải được lưu (xử lý ở một nơi khác) và sau đó các hóa đơn nhóm phải được cập nhật (xử lý ở một nơi khác), điều đó sẽ không vi phạm SRP, vì bạn không để lại sự trừu tượng của "công thức": bạn phân biệt giữa phải làm gì (trong công thức) và ở đâu nó được thực hiện (trong lớp / phương thức / mô-đun). Nhưng nếu đó là những gì bạn muốn save()làm (mô tả những bước cần thực hiện), bạn nên đổi tên nó.

Không có bối cảnh xa hơn, thật khó để nói bất cứ điều gì cụ thể.

Chỉnh sửa: bạn đã cập nhật bài viết bẩm sinh của bạn. Tôi có thể nói rằng phương pháp này vi phạm SRP và nên được tái cấu trúc. Việc tìm nạp dữ liệu nên được xác định (nó phải là một tham số của phương thức này). Việc bổ sung dữ liệu (updateBy / On) nên được thực hiện ở nơi khác. Việc tiết kiệm nên được thực hiện ở nơi khác. Sau đó, nó sẽ ổn để lại UpdateGroupBilling([Parameters])như vậy.


0

Khó có thể chắc chắn nếu không có bối cảnh đầy đủ nhưng tôi nghĩ rằng trực giác của bạn là chính xác. Chỉ báo SRP yêu thích cá nhân của tôi là liệu bạn có biết chính xác nơi sẽ đi và thay đổi thứ gì đó để sửa đổi một tính năng sau đó không.

Một lớp xác định loại thanh toán không phải là nơi tôi mong đợi để xác định lại ai thực hiện thanh toán hoặc cách thanh toán được thực hiện. Tôi hy vọng nó sẽ là một trong nhiều loại thanh toán chỉ cung cấp các chi tiết quan trọng mà một số phần khác của ứng dụng sử dụng để bắt đầu, thực hiện hoặc quay lại / hủy giao dịch, thu thập các chi tiết đó thông qua giao diện thống nhất.

Nó cũng giống như một công thức cho một vi phạm nguyên tắc DRY tổng quát hơn nếu bạn sẽ có nhiều loại mỗi loại sắp xếp các giao dịch của riêng mình bằng nhiều phương thức giống nhau. RẮN không phải lúc nào cũng cảm thấy phù hợp 100% với nhà phát triển JavaScript chủ yếu (tôi nghi ngờ rất nhiều điều được giải thích rất tệ) nhưng DRY là tiêu chuẩn vàng trong phương pháp lập trình chung IMO. Bất cứ khi nào tôi bắt gặp một ý tưởng giống như một phần mở rộng của DRY, tôi sẽ cân nhắc nó. SRP là một trong số đó. Nếu mọi người ở lại chủ đề, bạn sẽ ít có khả năng lặp lại chính mình.


0

Nếu về mặt khái niệm chỉ có một cách duy nhất UpdateGroupBilling(...)và nó chỉ xảy ra ở nơi đó, thì có lẽ nó vẫn ổn ở nơi đó. Nhưng câu hỏi liên quan đến khái niệm, không liên quan đến hoàn cảnh tạm thời, tức là "trong thời gian này, chúng tôi chỉ làm điều đó ở đây".

Nếu không phải như vậy, thì một cách để cấu trúc lại là sử dụng Xuất bản / Đăng ký và thông báo bất cứ khi nào thanh toán được lưu và đăng ký thanh toán cho sự kiện đó và cập nhật các mục liên quan. Điều này đặc biệt tốt nếu bạn cần có một số loại hóa đơn cần được cập nhật. Một phương pháp khác là nghĩ về Billing như một mẫu chiến lược, tức là chọn một và sử dụng nó, hoặc chấp nhận nó cũng như một tham số. Hoặc, nếu thanh toán không luôn xảy ra, bạn có thể thêm nó làm trang trí, v.v. Nhưng một lần nữa, điều này phụ thuộc vào mô hình khái niệm của bạn và cách bạn muốn nghĩ về nó, vì vậy bạn phải tìm ra nó .

Tuy nhiên, một điều quan trọng cần xem xét là xử lý lỗi. Nếu thanh toán không thành công, điều gì xảy ra với các hoạt động trước đó?


0

Tôi nghĩ rằng thiết kế này đang vi phạm SRP, nhưng nó thực sự dễ sửa chữa vẫn cần làm mọi thứ khác.

Hãy nghĩ về nó về tin nhắn và về những gì xảy ra trong phương pháp này. Nó sẽ được lưu GroupBillingPayment, nhưng không có gì sai nếu GroupBillingPayment thông báo cho các lớp rằng nó đã được lưu. Đặc biệt là nếu nó đang thực hiện một mô hình phơi bày rõ ràng hành vi đó.

Bạn có thể sử dụng mẫu Observer .

Đây là một ví dụ về cách nó hoạt động trong trường hợp của bạn:

public interface Subject {

    public void register(Observer observer);
    public void unregister(Observer observer);

    public void notifyObservers();      
}

public class GroupBillingPayment implements Subject {

    private List<Observer> observers;
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;

        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        //UpdateGroupBilling([Parameters]) The Observer will have this responsability instead now
        notifyObservers();
    }

    public void notifyObservers() {
        for (Observer obj : observers) {
            obj.update();
        }
    }
}

public interface Observer {     
    public void update();

    public void setSubject(Subject sub);
}

Bây giờ tất cả những gì bạn phải làm là tạo một hoặc nhiều Observersthứ sẽ bị ràng buộc GroupBillingPaymentvà do đó được thông báo bất cứ khi nào nó được lưu. Nó giữ các phụ thuộc riêng của mình, không biết những gì được thông báo vì vậy nó hoàn toàn không phụ thuộc vào những điều này.


0

Bạn muốn đạt được X. Trừ khi X khá tầm thường, bạn viết một phương thức sẽ làm mọi thứ để đạt được X, gọi phương thức là "gainX" và gọi phương thức đó. Trách nhiệm duy nhất của "achX" là làm mọi thứ để đạt được X. "gainX" không nên làm những việc khác.

Nếu X phức tạp, thì có thể cần nhiều hành động để đạt được X. Tập hợp các hành động cần thiết có thể thay đổi theo thời gian, vì vậy khi điều đó xảy ra, bạn thay đổi phương thức gainX, nhưng nếu mọi thứ đều ổn, bạn vẫn có thể gọi nó.

Trong ví dụ của bạn, phương pháp "Lưu" trách nhiệm không phải là lưu hóa đơn và cập nhật hóa đơn nhóm (hai trách nhiệm), trách nhiệm của nó là thực hiện tất cả các hành động cần thiết để ghi hóa đơn vĩnh viễn. Một trách nhiệm. Có lẽ bạn đặt tên cho nó là xấu.


-2

Nếu tôi hiểu quan điểm của bạn, tôi sẽ có khoản thanh toán để nêu ra một sự kiện, chẳng hạn như "Thanh toán được thực hiện" và lớp xử lý việc thanh toán đăng ký nó. Theo cách này, người xử lý thanh toán không biết gì về thanh toán.

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.