Làm thế nào để tuân thủ nguyên tắc đóng mở trong thực tế


14

Tôi hiểu ý định của nguyên tắc đóng mở. Nó có nghĩa là để giảm nguy cơ phá vỡ một cái gì đó đã hoạt động trong khi sửa đổi nó, bằng cách nói với bạn cố gắng mở rộng mà không sửa đổi.

Tuy nhiên, tôi đã gặp một số khó khăn khi hiểu nguyên tắc này được áp dụng như thế nào trong thực tế. Theo hiểu biết của tôi, có hai cách để áp dụng nó. Beofore và sau khi có thể thay đổi:

  1. Trước: chương trình trừu tượng hóa và 'dự đoán tương lai' càng nhiều càng tốt. Ví dụ: một phương thức drive(Car car)sẽ phải thay đổi nếu Motorcycles được thêm vào hệ thống trong tương lai, vì vậy nó có thể vi phạm OCP. Nhưng phương pháp drive(MotorVehicle vehicle)ít có khả năng phải thay đổi trong tương lai, vì vậy nó tuân thủ OCP.

    Tuy nhiên, thật khó để dự đoán tương lai và biết trước những thay đổi sẽ được thực hiện cho hệ thống.

  2. Sau: khi cần thay đổi, hãy mở rộng một lớp thay vì sửa đổi mã hiện tại.

Thực hành # 1 không khó hiểu. Tuy nhiên, thực tế # 2 là tôi gặp khó khăn trong việc hiểu cách áp dụng.

Ví dụ: (tôi đã lấy nó từ một video trên YouTube): giả sử chúng ta có một phương thức trong một lớp chấp nhận CreditCardcác đối tượng : makePayment(CraditCard card). Một ngày Vouchers được thêm vào hệ thống. Phương pháp này không hỗ trợ chúng nên nó phải được sửa đổi.

Khi thực hiện phương thức ở nơi đầu tiên, chúng tôi đã không dự đoán được tương lai và chương trình theo các thuật ngữ trừu tượng hơn (ví dụ: makePayment(Payment pay)vì vậy bây giờ chúng tôi phải thay đổi mã hiện có.

Thực hành # 2 nói rằng chúng ta nên thêm chức năng bằng cách mở rộng thay vì sửa đổi. Điều đó nghĩa là gì? Tôi có nên phân lớp lớp hiện tại thay vì chỉ thay đổi mã hiện có không? Tôi có nên tạo một số loại trình bao quanh nó chỉ để tránh viết lại mã không?

Hoặc nguyên tắc thậm chí không đề cập đến 'cách sửa đổi / thêm chức năng chính xác', mà chỉ đề cập đến 'làm thế nào để tránh phải thay đổi ở nơi đầu tiên (tức là chương trình để trừu tượng hóa)?



1
Nguyên tắc mở / đóng không quyết định cơ chế bạn sử dụng. Kế thừa thường là sự lựa chọn sai lầm. Ngoài ra, không thể bảo vệ chống lại tất cả các thay đổi trong tương lai. Tốt nhất không nên cố gắng dự đoán tương lai, nhưng một khi cần thay đổi, hãy sửa đổi thiết kế để những thay đổi trong tương lai có thể được điều chỉnh.
Doval

Câu trả lời:


14

Nguyên tắc thiết kế luôn phải được cân bằng với nhau. Bạn không thể dự đoán tương lai và hầu hết các lập trình viên làm điều đó một cách khủng khiếp khi họ cố gắng. Đó là lý do tại sao chúng ta có quy tắc ba , chủ yếu là về sao chép, nhưng cũng áp dụng cho tái cấu trúc cho bất kỳ nguyên tắc thiết kế nào khác.

Khi bạn chỉ có một triển khai giao diện, bạn không cần quan tâm nhiều đến OCP, trừ khi nó rõ ràng ở bất kỳ tiện ích mở rộng nào sẽ diễn ra. Trên thực tế, bạn thường mất đi sự rõ ràng khi cố gắng thiết kế quá mức trong tình huống này. Khi bạn mở rộng nó một lần, bạn tái cấu trúc để làm cho nó thân thiện với OCP nếu đó là cách dễ nhất và rõ ràng nhất để làm điều đó. Khi bạn mở rộng nó sang triển khai thứ ba, bạn đảm bảo tái cấu trúc nó đưa OCP vào tài khoản, ngay cả khi nó đòi hỏi một chút nỗ lực hơn.

Trong thực tế, khi bạn chỉ có hai triển khai, tái cấu trúc khi bạn thêm một phần ba thường không quá khó. Đó là khi bạn để nó phát triển quá mức mà nó trở nên rắc rối để duy trì.


1
Cảm ơn đã trả lời. Hãy cho tôi xem nếu tôi hiểu những gì bạn đang nói: những gì bạn đang nói là tôi nên quan tâm đến OCP chủ yếu sau khi tôi bị buộc phải thay đổi một lớp học. Ý nghĩa: khi triển khai một lớp học lần đầu tiên, tôi không nên lo lắng nhiều về OCP, vì dù sao cũng khó dự đoán tương lai. Khi tôi cần gia hạn / sửa đổi lần đầu tiên, có lẽ nên cấu trúc lại một chút để linh hoạt hơn trong tương lai (nhiều OCP hơn). Và trong lần thứ ba tôi cần gia hạn / sửa đổi lớp, đã đến lúc thực hiện một số phép tái cấu trúc để làm cho nó tuân thủ OCP hơn. Ý bạn là vậy đúng không?
Manila Cohn

1
Đó là ý tưởng.
Karl Bielefeldt

2

Tôi nghĩ rằng bạn đang nhìn quá xa vào tương lai. Giải quyết vấn đề hiện tại một cách linh hoạt tuân thủ mở / đóng.

Giả sử bạn cần thực hiện một drive(Car car)phương pháp. Tùy thuộc vào ngôn ngữ của bạn, bạn có một vài lựa chọn.

  • Đối với các ngôn ngữ hỗ trợ nạp chồng (C ++), thì chỉ cần sử dụng drive(const Car& car)

    Tại một số điểm sau bạn có thể cần drive(const Motorcycle& motorcycle), nhưng nó sẽ không can thiệp vào drive(const Car& car). Không vấn đề gì!

  • Đối với các ngôn ngữ không hỗ trợ quá tải (Mục tiêu C), sau đó bao gồm tên loại trong phương thức -driveCar:(Car *)car.

    Tại một số điểm sau bạn có thể cần -driveMotorcycle:(Motorcycle *)motorcycle, nhưng một lần nữa, nó sẽ không can thiệp.

Điều này cho phép drive(Car car)đóng cửa để sửa đổi, nhưng mở ra để mở rộng cho các loại xe khác. Kế hoạch tương lai tối giản này cho phép bạn hoàn thành công việc ngay hôm nay, nhưng ngăn bạn ngăn chặn bản thân trong tương lai.

Cố gắng tưởng tượng các loại cơ bản nhất bạn cần có thể dẫn đến hồi quy vô hạn. Điều gì xảy ra khi bạn muốn lái Segue, xe đạp hoặc máy bay phản lực Jumbo. Làm thế nào để bạn xây dựng một loại trừu tượng chung duy nhất có thể giải thích cho tất cả các thiết bị mà mọi người truy cập và sử dụng cho tính di động?


Sửa đổi một lớp để thêm phương thức mới của bạn vi phạm Nguyên tắc đóng mở. Đề xuất của bạn cũng loại bỏ khả năng áp dụng Nguyên tắc thay thế Liskov cho tất cả các phương tiện có thể lái mà về cơ bản là loại bỏ một trong những phần mạnh nhất của OO.
Dunk

@Dunk Tôi dựa trên câu trả lời của tôi dựa trên nguyên tắc đóng / mở đa hình, chứ không phải nguyên tắc đóng / mở Meyer nghiêm ngặt. Nó được phép cập nhật các lớp để hỗ trợ các giao diện mới. Trong ví dụ này, giao diện xe hơi được giữ tách biệt với giao diện xe máy. Chúng có thể được chính thức hóa thành các lớp trừu tượng lái xe riêng cho ô tô và xe máy mà lớp thực hiện có thể hỗ trợ.
Jeffery Thomas

@Dunk Nguyên tắc thay thế Liskov là hữu ích, nhưng không miễn phí. Nếu đặc điểm kỹ thuật ban đầu chỉ yêu cầu một chiếc xe, thì việc tạo ra một chiếc xe chung chung hơn có thể không xứng đáng với chi phí thêm về tiền bạc, thời gian và sự phức tạp. Ngoài ra, không có khả năng một chiếc xe chung chung hơn sẽ hoàn toàn phù hợp để xử lý các lớp con không có kế hoạch. Giao diện cho xe máy sẽ cần được đưa vào giao diện xe (được thiết kế để chỉ xử lý ô tô), hoặc bạn sẽ cần sửa đổi phương tiện để xử lý xe máy (vi phạm thực sự về mở / đóng).
Jeffery Thomas

Nguyên tắc thay thế Liskov không miễn phí nhưng nó cũng không đi kèm với nhiều chi phí. Và thường thì nó trả lại nhiều hơn bao giờ hết chi phí nhiều lần ngay cả khi một lớp con khác không bao giờ được thừa kế từ nó trong ứng dụng chính. Áp dụng LSP giúp cho việc kiểm tra tự động dễ dàng hơn rất nhiều, đó là một chiến thắng. Ngoài ra, trong khi bạn chắc chắn không nên cuồng nhiệt và cho rằng mọi thứ sẽ cần LSP, nếu bạn đang xây dựng một ứng dụng và không có cảm giác tốt về những gì có thể sẽ cần nó trong một phiên bản tương lai thì bạn không nên biết đủ về ứng dụng của bạn hoặc tên miền của nó.
Dunk

1
Liên quan đến định nghĩa của OCP. Đó có thể là các ngành mà tôi đã làm việc, có xu hướng yêu cầu mức độ xác minh cao hơn so với chỉ một công ty thương mại thông thường, nhưng nói chung nếu một tệp / lớp thay đổi thì bạn không chỉ cần kiểm tra lại tệp / lớp mà còn tất cả mọi thứ mà sử dụng tệp / lớp đó trong kiểm tra hồi quy của bạn. Vì vậy, không có vấn đề gì nếu ai đó nói mở / đóng đa hình là tốt, việc thay đổi giao diện có nhiều hậu quả khác nhau nên không ổn chút nào.
Dunk

2

Tôi hiểu ý định của nguyên tắc đóng mở. Nó có nghĩa là để giảm nguy cơ phá vỡ một cái gì đó đã hoạt động trong khi sửa đổi nó, bằng cách nói với bạn cố gắng mở rộng mà không sửa đổi.

Nó cũng là về việc không phá vỡ tất cả các đối tượng dựa trên phương thức đó bằng cách không thay đổi hành vi của các đối tượng đã tồn tại. Một khi một đối tượng đã quảng cáo thay đổi hành vi thì sẽ rất rủi ro vì bạn đang thay đổi hành vi đã biết và dự kiến ​​của đối tượng mà không biết chính xác những gì đối tượng khác mong đợi hành vi đó.

Điều đó nghĩa là gì? Tôi có nên phân lớp lớp hiện tại thay vì chỉ thay đổi mã hiện có không?

Vâng

"Chỉ chấp nhận thẻ tín dụng" được định nghĩa là một phần trong hành vi của lớp đó, thông qua giao diện công khai. Lập trình viên đã tuyên bố với thế giới rằng phương thức của đối tượng này chỉ lấy thẻ tín dụng. Cô ấy đã làm nó bằng cách sử dụng một tên phương thức không đặc biệt rõ ràng, nhưng nó đã được thực hiện. Phần còn lại của hệ thống đang dựa vào điều này.

Điều đó có thể có ý nghĩa vào thời điểm đó, nhưng bây giờ nếu cần thay đổi ngay bây giờ, bạn nên tạo một lớp mới chấp nhận những thứ khác ngoài thẻ tín dụng.

Hành vi mới = lớp mới

Như một bên - Một cách tốt để dự đoán tương lai là suy nghĩ về tên bạn đã đưa ra một phương pháp. Bạn đã đưa ra một tên phương thức âm thanh thực sự chung chung như makePayment cho một phương thức với các quy tắc cụ thể trong phương thức để biết chính xác khoản thanh toán nào có thể thực hiện chưa? Đó là một mùi mã. Nếu bạn có các quy tắc cụ thể, những quy tắc này sẽ được làm rõ từ tên phương thức - makePayment nên là makeCreditCardPayment. Làm điều này khi bạn đang viết đối tượng lần đầu tiên và các lập trình viên khác sẽ cảm ơn bạn vì điều đó.

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.