Nguyên tắc đóng trong các mẫu thiết kế


8

Tôi hơi bối rối về cách nguyên tắc Mở đóng có thể được áp dụng trong cuộc sống thực. Yêu cầu trong bất kỳ doanh nghiệp thay đổi theo thời gian. Theo nguyên tắc Mở-Đóng, bạn nên mở rộng lớp thay vì sửa đổi lớp hiện có. Đối với tôi mỗi lần mở rộng một lớp học dường như không thực tế để đáp ứng yêu cầu. Hãy để tôi đưa ra một ví dụ với hệ thống đặt vé tàu.

Trong hệ thống đặt vé tàu sẽ có một đối tượng Vé. Có thể có nhiều loại vé khác nhau Vé thông thường, Vé giảm giá, v.v. Vé là lớp trừu tượng và RoutTicket và ConcessionTickets là các lớp cụ thể. Vì tất cả các vé sẽ có phương thức PrintTicket là phổ biến, do đó, nó được viết trong Ticket là lớp trừu tượng cơ bản. Hãy nói rằng điều này làm việc tốt trong vài tháng. Bây giờ yêu cầu mới xuất hiện, trong đó tuyên bố thay đổi định dạng của vé. Có thể thêm vài trường được thêm vào vé in hoặc có thể thay đổi định dạng. Để đáp ứng yêu cầu này, tôi có các tùy chọn sau

  1. Sửa đổi phương thức PrintTicket () trong lớp trừu tượng Ticket. Nhưng điều này sẽ vi phạm nguyên tắc Mở-Đóng.
  2. Ghi đè phương thức PrintTicket () trong các lớp con nhưng điều này sẽ nhân đôi logic in vi phạm nguyên tắc DRY (Không lặp lại chính mình).

Vì vậy, câu hỏi là

  1. Làm thế nào tôi có thể đáp ứng yêu cầu kinh doanh trên mà không vi phạm nguyên tắc Mở / Đóng.
  2. Khi lớp học được cho là đóng cửa để sửa đổi? Các tiêu chí để xem xét lớp được đóng cửa để sửa đổi là gì? Có phải sau khi thực hiện ban đầu của lớp hoặc có thể là sau khi triển khai đầu tiên trong sản xuất hoặc có thể là một cái gì đó khác.

1
Tôi đoán rằng vấn đề ở đây là lớp vé của bạn không nên biết cách tự in. Một lớp TicketPrinter sẽ nhận được một vé và biết cách in nó. Trong trường hợp này, bạn có thể sử dụng bộ giải mã cha để tăng lớp máy in của bạn hoặc ghi đè lên nó để thay đổi hoàn toàn.

1
Nhà trang trí cũng sẽ phải đối mặt với vấn đề Mở / Đóng tương tự. Yêu cầu có thể thay đổi nhiều lần trong tương lai. Trong trường hợp đó, bạn sẽ thay đổi trang trí hoặc mở rộng trang trí. Mở rộng trang trí mỗi lần dường như không thực tế và sửa đổi trang trí sẽ vi phạm nguyên tắc mở / đóng.
parag

1
Rất khó để thiết kế với OCP cho những thay đổi tùy ý trong thiết kế. Đây là một canh bạc để chọn kích thước mà bạn mong đợi sẽ thay đổi đối với phần ổn định.
Fuhrmanator

Câu trả lời:


5

Tôi có các lựa chọn sau

  • Sửa đổi PrintTicket()phương thức trong lớp trừu tượng Ticket. Nhưng điều này sẽ vi phạm nguyên tắc Mở-Đóng.
  • Ghi đè PrintTicket()phương thức trong các lớp con nhưng điều này sẽ nhân đôi logic in vi phạm nguyên tắc DRY (Không lặp lại chính bạn).

Điều này phụ thuộc vào cách thức PrintTicketthực hiện. Nếu phương thức cần xem xét thông tin từ các lớp con, nó cần cung cấp một cách để chúng cung cấp thêm thông tin.

Hơn nữa, bạn có thể ghi đè mà không cần lặp lại mã của bạn. Ví dụ, nếu bạn gọi phương thức lớp cơ sở của bạn từ việc triển khai, bạn sẽ tránh lặp lại:

class ConcessionTicket : Ticket {
    public override string PrintTicket() {
        return $"{base.PrintTicket()} (concession)"
    }
}

Làm cách nào tôi có thể đáp ứng yêu cầu kinh doanh trên mà không vi phạm nguyên tắc Mở / Đóng?

Mẫu Phương thức mẫu cung cấp tùy chọn thứ ba: triển khaiPrintTickettrong lớp cơ sở và dựa vào các lớp dẫn xuất để cung cấp thêm chi tiết khi cần.

Dưới đây là một ví dụ sử dụng hệ thống phân cấp lớp của bạn:

abstract class Ticket {
    public string Name {get;}
    public string Train {get;}
    protected virtual string AdditionalDetails() {
        return "";
    }
    public string PrintTicket() {
        return $"{Name} : {Train}{AdditionalDetails()}";
    }
}

class RegularTicket : Ticket {
    ... // Uses the default implementation of AdditionalDetails()
}


class ConcessionTicket : Ticket {
    protected override string AdditionalDetails() {
        return " (concession)";
    }
}

Các tiêu chí để xem xét lớp được đóng cửa để sửa đổi là gì?

Đây không phải là lớp nên được đóng lại để sửa đổi, mà là giao diện của lớp đó (ý tôi là "giao diện" theo nghĩa rộng, nghĩa là một tập hợp các phương thức và thuộc tính và hành vi của chúng, chứ không phải cấu trúc ngôn ngữ). Lớp che giấu việc thực hiện của nó, vì vậy các chủ sở hữu của lớp có thể sửa đổi nó bất cứ lúc nào, miễn là hành vi nhìn thấy bên ngoài của nó không thay đổi.

Là sau khi thực hiện ban đầu của lớp hoặc có thể là sau khi triển khai đầu tiên trong sản xuất hoặc có thể là một cái gì đó khác?

Giao diện của lớp cần được đóng lại sau lần đầu tiên bạn xuất bản nó để sử dụng bên ngoài. Các lớp sử dụng nội bộ vẫn mở để tái cấu trúc mãi mãi, bởi vì bạn có thể tìm và sửa tất cả các công dụng của nó.

Ngoài các trường hợp đơn giản nhất, sẽ không thực tế khi hy vọng rằng hệ thống phân cấp lớp của bạn sẽ bao gồm tất cả các tình huống sử dụng có thể xảy ra sau một số lần lặp lại tái cấu trúc nhất định. Các yêu cầu bổ sung yêu cầu các phương thức hoàn toàn mới trong lớp cơ sở xuất hiện thường xuyên, vì vậy lớp của bạn sẽ mở cho bạn sửa đổi mãi mãi.


Có, trong trường hợp cụ thể này templating sẽ hoạt động. Nhưng đây chỉ là một ví dụ để đặt vấn đề. Có thể có nhiều tình huống / vấn đề kinh doanh trong đó phương pháp tạo khuôn mẫu / plugin có thể không được áp dụng.
parag

1
Quan điểm của bạn về giao diện lớp theo ý kiến ​​của tôi là độ phân giải tốt nhất cho kịch bản cụ thể này. Để tập trung vào câu hỏi cụ thể: người ta có thể có một phương thức PrintTicket với đầu ra được chỉ định được xác định trong giao diện cho lớp Ticket (cùng với các đầu vào được xác định), miễn là đầu ra của vé luôn giống nhau, không quan trọng như thế nào các lớp cơ bản thay đổi. - Ngoài ra nếu các thuộc tính của một vé luôn được dự kiến ​​sẽ thay đổi, tại sao không thiết kế lớp của bạn với yêu cầu kinh doanh đã biết. Vé nên chứa một bộ sưu tập một đối tượng TicketProperty thuộc loại nào đó
user3908435

Cũng có thể tạo một TicketPrinterlớp được truyền cho hàm tạo.
Zymus

2

Hãy bắt đầu với dòng nguyên tắc đóng mở đơn giản - "Open for extension Closed for modification". Xem xét vấn đề của bạn Tôi sẽ thiết kế các lớp để Vé cơ sở chịu trách nhiệm in thông tin Vé chung và các loại Vé khác sẽ chịu trách nhiệm in định dạng của riêng chúng trên đầu in cơ sở.

abstract class Ticket
{
    public string Print()
    {
        // Print base properties here. and return 
        return PrintBasicInfo() + AddionalPrint();
    }

    protected abstract string AddionalPrint();

    private string PrintBasicInfo()
    {
        // print base ticket info
    }
}

class ConcessionTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Conecession ticket printing
    }
}

class RegularTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Regular ticket printing
    }
}

Bằng cách này, bạn sẽ thực thi bất kỳ loại vé mới nào sẽ được giới thiệu trong hệ thống sẽ triển khai tính năng in riêng của nó trên đầu trang của thông tin in vé cơ bản. Vé cơ sở hiện đã bị đóng để sửa đổi nhưng mở để mở rộng bằng cách cung cấp phương thức bổ sung cho các loại dẫn xuất của nó.


2

Vấn đề không phải là bạn đang vi phạm nguyên tắc Mở / Đóng, vấn đề là bạn đang vi phạm Nguyên tắc Trách nhiệm duy nhất.

Đây thực sự là một ví dụ trong sách học về một vấn đề SRP, hoặc như wikipedia tuyên bố:

Ví dụ, xem xét một mô-đun biên dịch và in báo cáo. Hãy tưởng tượng một mô-đun như vậy có thể được thay đổi vì hai lý do. Đầu tiên, nội dung của báo cáo có thể thay đổi. Thứ hai, định dạng của báo cáo có thể thay đổi. Hai điều này thay đổi vì những nguyên nhân rất khác nhau; một thực chất, và một mỹ phẩm. Nguyên tắc trách nhiệm duy nhất nói rằng hai khía cạnh của vấn đề này thực sự là hai trách nhiệm riêng biệt, và do đó nên ở trong các lớp hoặc mô-đun riêng biệt. Sẽ là một thiết kế tồi khi kết hợp hai thứ thay đổi vì những lý do khác nhau vào những thời điểm khác nhau.

Các lớp vé khác nhau có thể thay đổi vì bạn thêm thông tin mới cho chúng. Việc in vé có thể thay đổi vì bạn cần bố cục mới, do đó bạn nên có lớp TicketPrinter chấp nhận vé làm đầu vào.

Các lớp Ticket của bạn sau đó có thể triển khai các giao diện khác nhau để cung cấp dữ liệu loại khác nhau hoặc cung cấp một số loại mẫu dữ liệu.


1

Sửa đổi phương thức PrintTicket () trong lớp trừu tượng Ticket. Nhưng điều này sẽ vi phạm nguyên tắc Mở-Đóng.

Điều này không vi phạm hiệu trưởng đóng mở. Mã thay đổi mọi lúc, đó là lý do tại sao RẮN là quan trọng, vì vậy mã vẫn có thể duy trì, linh hoạt, có thể thay đổi. Không có gì sai khi thay đổi mã.


OCP nói thêm về các lớp bên ngoài không thể can thiệp vào chức năng dự định của một lớp.

Chẳng hạn, tôi có một lớp Alấy Chuỗi trong hàm tạo và xác minh Chuỗi này có định dạng nhất định bằng cách gọi một phương thức để xác minh chuỗi. Phương pháp xác minh này cũng cần được khách hàng sử dụng A, do đó, trong quá trình thực hiện ngây thơ, nó đã được thực hiện public.

Bây giờ tôi có thể 'phá vỡ' lớp này bằng cách kế thừa từ nó và ghi đè phương thức xác minh để luôn luôn trả về true. Do tính đa hình, tôi vẫn có thể sử dụng lớp con của mình ở bất cứ nơi nào tôi có thể sử dụng A, có thể tạo ra hành vi không mong muốn trong chương trình của mình.

Điều này có vẻ như là một điều độc hại để làm, nhưng trong một cơ sở mã phức tạp hơn, nó có thể được thực hiện như một 'sai lầm trung thực'. Để phù hợp với OCP, lớp Anên được thiết kế theo cách không thể thực hiện sai lầm này.

Tôi có thể làm điều này bằng cách, ví dụ, thực hiện phương pháp xác minh final.


Tôi chưa bao giờ thấy một mô tả về OCP sẽ nói rằng đây là vi phạm. LSP, chắc chắn, nhưng không phải OCP.
Jules

@Jules Tôi thấy sau đó định nghĩa wikipedia thực sự rất khác với điều này. Đây là một trong những ví dụ được sử dụng để dạy cho tôi OCP (mặc dù nó có thể hơi khác một chút). Vấn đề là, đó không phải là về việc cấm bản thân thay đổi mã của bạn. Nó cũng không có ý nghĩa với tôi như thế. Nếu bạn viết mã và môi trường thay đổi để mã của bạn không còn phù hợp nữa, hãy thay đổi hoặc tốt hơn, vứt nó đi và bắt đầu lại. Tại sao giữ một cái gì đó bị hỏng ...
Jorn Vernee

0

Kế thừa không phải là cơ chế duy nhất có thể được sử dụng để đáp ứng các yêu cầu của OCP và trong hầu hết các trường hợp tôi sẽ cho rằng nó hiếm khi là tốt nhất - thường có những cách tốt hơn, miễn là bạn lên kế hoạch trước một chút để xem xét loại thay đổi bạn có thể cần.

Trong trường hợp này, tôi sẽ lập luận rằng nếu bạn mong đợi nhận được các thay đổi định dạng thường xuyên đối với hệ thống in vé của bạn (và đó có vẻ như là một đặt cược khá an toàn với tôi), sử dụng một hệ thống tạo khuôn mẫu sẽ rất có ý nghĩa. Bây giờ, chúng tôi không cần phải chạm vào mã nào cả: tất cả những gì chúng tôi phải làm để thực hiện yêu cầu này là thay đổi một mẫu (miễn là hệ thống mẫu của bạn có thể truy cập tất cả dữ liệu cần thiết, dù sao).

Trong thực tế, một hệ thống không bao giờ có thể hoàn toàn đóng cửa chống lại sửa đổi và thậm chí việc đóng nó sẽ đòi hỏi một lượng lớn công sức lãng phí, vì vậy bạn phải đưa ra phán quyết về loại thay đổi nào có khả năng và cấu trúc mã của bạn theo đó.

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.