Điều đó có nghĩa là gì khi người ta nói rằng Encapsulation có gì khác nhau?


25

Một trong những nguyên tắc OOP mà tôi đã gặp là: -Tìm hiểu những gì khác nhau.

Tôi hiểu nghĩa đen của cụm từ nghĩa là ẩn những gì khác nhau. Tuy nhiên, tôi không biết chính xác nó sẽ đóng góp như thế nào cho một thiết kế tốt hơn. Ai đó có thể giải thích nó bằng một ví dụ tốt?


Xem en.wikipedia.org/wiki/Encapsulation_(computer_programming) giải thích rõ về nó. Tôi nghĩ rằng "những gì thay đổi" là không chính xác vì đôi khi bạn cũng nên đóng gói các hằng số.
qwerty_so

I don't know how exactly would it contribute to a better designChi tiết đóng gói là về khớp nối lỏng lẻo giữa "mô hình" và chi tiết thực hiện. Càng ít ràng buộc là "mô hình" cho các chi tiết thực hiện, giải pháp càng linh hoạt. Và nó làm cho nó dễ dàng hơn để phát triển nó. "Tóm tắt bản thân từ các chi tiết".
Laiv

@Laiv Vì vậy, "khác nhau" đề cập đến những gì phát triển trong quá trình vòng đời phần mềm của bạn hoặc những thay đổi trong quá trình thực hiện chương trình của bạn hoặc cả hai?
Haris Ghauri

2
@HarisGhauri cả. Nhóm lại với nhau những gì khác nhau. Cô lập những gì thay đổi độc lập. Hãy nghi ngờ về những gì bạn cho là sẽ không bao giờ thay đổi.
candied_orange

1
@laiv nghĩ "trừu tượng" là một điểm tốt. Nó có thể cảm thấy quá sức để làm điều đó. Trong bất kỳ một đối tượng nào, bạn sẽ phải có một trách nhiệm. Điều tốt đẹp về điều đó là bạn chỉ cần suy nghĩ cẩn thận về điều đó ở đây. Khi các chi tiết của phần còn lại của vấn đề là vấn đề của ai đó, nó sẽ giúp mọi việc dễ dàng hơn.
candied_orange

Câu trả lời:


30

Bạn có thể viết mã trông như thế này:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

hoặc bạn có thể viết mã trông như thế này:

pet.speak();

Nếu những gì thay đổi được gói gọn thì bạn không phải lo lắng về nó. Bạn chỉ lo lắng về những gì bạn cần và bất cứ điều gì bạn đang sử dụng số liệu làm thế nào để làm những gì bạn thực sự cần dựa trên những gì khác nhau.

Đóng gói những gì khác nhau và bạn không phải trải rộng mã xung quanh mà quan tâm đến những gì khác nhau. Bạn chỉ cần đặt thú cưng thành một loại nhất định biết cách nói như loại đó và sau đó bạn có thể quên loại nào và chỉ coi nó như thú cưng. Bạn không cần phải hỏi loại nào.

Bạn có thể nghĩ rằng loại được đóng gói bởi vì một getter được yêu cầu để truy cập nó. Tôi không. Getter không thực sự gói gọn. Họ chỉ căng thẳng khi ai đó phá vỡ đóng gói của bạn. Chúng là một công cụ trang trí đẹp như móc định hướng theo khía cạnh thường được sử dụng làm mã gỡ lỗi. Cho dù bạn cắt nó như thế nào, bạn vẫn tiếp xúc với loại.

Bạn có thể nhìn vào ví dụ này và nghĩ rằng tôi đang kết hợp đa hình và đóng gói. Tôi không. Tôi đang giới thiệu "những gì khác nhau" và "chi tiết".

Thực tế là thú cưng của bạn là một con chó là một chi tiết. Một cái có thể khác nhau cho bạn. Một trong đó có thể không. Nhưng chắc chắn một trong những có thể thay đổi từ người này sang người khác. Trừ khi chúng tôi tin rằng phần mềm này sẽ chỉ được sử dụng bởi những người yêu chó, thật thông minh khi coi chó là một chi tiết và gói gọn nó. Bằng cách đó, một số bộ phận của hệ thống không biết gì về con chó và sẽ không bị ảnh hưởng khi chúng ta hợp nhất với "vẹt là chúng ta".

Tách riêng, tách riêng và ẩn chi tiết khỏi phần còn lại của mã. Đừng để kiến ​​thức về các chi tiết lan truyền trong hệ thống của bạn và bạn sẽ theo dõi "gói gọn những gì khác nhau".


3
Điều đó thực sự kỳ quặc. "Đóng gói những gì khác nhau" với tôi có nghĩa là để che giấu những thay đổi trạng thái, ví dụ như không bao giờ có biến toàn cục. Nhưng bạn trả lời cũng có ý nghĩa, ngay cả khi nó cảm thấy câu trả lời cho đa hình hơn là đóng gói :)
David Arno

2
@DavidArno Đa hình là một cách để làm cho công việc này. Tôi có thể chỉ định cấu trúc nếu cấu trúc thành thú cưng và mọi thứ sẽ tốt đẹp ở đây nhờ vào sự đóng gói của thú cưng. Nhưng đó sẽ chỉ là di chuyển mớ hỗn độn thay vì dọn dẹp nó.
candied_orange

1
"Đóng gói những gì khác nhau" với tôi có nghĩa là để che giấu những thay đổi trạng thái . Không, nay. Tôi thích bình luận của CO. Câu trả lời của Derick Elkin đi sâu hơn, đọc nó nhiều lần. Như @JacquesB đã nói "Nguyên tắc này thực sự khá sâu sắc"
radarbob

16

"Khác nhau" ở đây có nghĩa là "có thể thay đổi theo thời gian do yêu cầu thay đổi". Đây là một nguyên tắc thiết kế cốt lõi: Để tách và tách các đoạn mã hoặc dữ liệu có thể phải thay đổi riêng trong tương lai. Nếu một yêu cầu duy nhất thay đổi, lý tưởng nhất là chỉ yêu cầu chúng tôi thay đổi mã liên quan ở một nơi duy nhất. Nhưng nếu cơ sở mã được thiết kế tồi, tức là có tính liên kết cao và logic cho yêu cầu trải rộng ở nhiều nơi, thì việc thay đổi sẽ khó khăn và có nguy cơ cao gây ra hiệu ứng không mong muốn.

Giả sử bạn có một ứng dụng sử dụng tính thuế bán hàng ở nhiều nơi. Nếu thuế suất bán hàng thay đổi, bạn thích điều gì hơn:

  • thuế suất bán hàng là một nghĩa đen được mã hóa cứng ở mọi nơi trong ứng dụng nơi tính thuế bán hàng.

  • thuế suất bán hàng là một hằng số toàn cầu, được sử dụng ở mọi nơi trong ứng dụng nơi tính thuế bán hàng.

  • có một phương pháp duy nhất được gọi calculateSalesTax(product)là nơi duy nhất áp dụng thuế suất bán hàng.

  • thuế suất bán hàng được chỉ định trong một tệp cấu hình hoặc trường cơ sở dữ liệu.

Vì thuế suất bán hàng có thể thay đổi do một quyết định chính trị độc lập với yêu cầu khác, chúng tôi muốn cách ly nó trong một cấu hình, vì vậy nó có thể được thay đổi mà không ảnh hưởng đến bất kỳ mã nào. Nhưng nó cũng có thể hiểu được logic để tính thuế bán hàng có thể thay đổi, ví dụ như các mức giá khác nhau cho sản phẩm khác nhau, vì vậy chúng tôi cũng muốn có logic tính toán được gói gọn. Hằng số toàn cầu có vẻ như là một ý tưởng tốt, nhưng thực sự tồi tệ, vì nó có thể khuyến khích sử dụng thuế doanh thu ở những nơi khác nhau trong chương trình thay vì ở một nơi duy nhất.

Bây giờ hãy xem xét một hằng số khác, Pi, cũng được sử dụng ở rất nhiều nơi trong mã. Liệu các nguyên tắc thiết kế tương tự giữ? Không, bởi vì Pi sẽ không thay đổi. Trích xuất nó vào một tệp cấu hình hoặc trường cơ sở dữ liệu không chỉ giới thiệu độ phức tạp không cần thiết (và mọi thứ khác đều bằng nhau, chúng tôi thích mã đơn giản nhất). Thật ý nghĩa khi biến nó thành một hằng số toàn cầu thay vì mã hóa cứng ở nhiều nơi để tránh sự không nhất quán và cải thiện khả năng đọc.

Vấn đề là, nếu chúng ta chỉ nhìn vào cách chương trình hoạt động bây giờ , thuế suất bán hàng và Pi là tương đương, cả hai đều là hằng số. Chỉ khi chúng ta xem xét những gì có thể thay đổi trong tương lai , chúng ta mới nhận ra chúng ta phải đối xử với chúng khác nhau trong thiết kế.

Nguyên tắc này thực sự khá sâu sắc, bởi vì nó có nghĩa là bạn phải nhìn xa hơn những gì mà cơ sở mã phải làm hôm nay , và cũng xem xét các lực lượng bên ngoài có thể khiến nó thay đổi, và thậm chí hiểu các bên liên quan khác nhau đằng sau các yêu cầu.


2
Các loại thuế là một ví dụ tốt. Luật và Thuế có thể thay đổi từ ngày này sang ngày khác. Nếu bạn đang thực hiện một hệ thống kê khai thuế, bạn sẽ bị ràng buộc chặt chẽ với loại thay đổi này. Đồng thời thay đổi từ Địa điểm này sang Địa điểm khác (quốc gia, tỉnh, ...)
Laiv

"Pi sẽ không thay đổi" làm tôi cười. Đó là sự thật, Pi không có khả năng thay đổi, nhưng giả sử bạn không được phép sử dụng nó nữa? Nếu một số người có cách của họ thì Pi sẽ bị từ chối. Giả sử điều đó trở thành một yêu cầu? Hy vọng bạn có một ngày Tau vui vẻ . Câu trả lời hay của BTW. Thực sự sâu sắc.
candied_orange 7/12/2016

14

Cả hai câu trả lời hiện tại dường như chỉ đạt được một phần, và họ tập trung vào các ví dụ che mờ ý tưởng cốt lõi. Đây cũng không phải (chỉ) một nguyên tắc OOP mà là một nguyên tắc thiết kế phần mềm nói chung.

Điều "thay đổi" trong cụm từ này là mã. Barshe có quan điểm khi nói rằng nó thường là một cái gì đó có thể thay đổi, đó là bạn thường dự đoán điều này. Mục tiêu là để bảo vệ bản thân khỏi những thay đổi trong tương lai của mã. Điều này liên quan chặt chẽ đến lập trình dựa trên giao diện . Tuy nhiên, Barshe không chính xác để giới hạn điều này trong "chi tiết thực hiện". Trên thực tế, giá trị của lời khuyên này thường là do những thay đổi trong yêu cầu .

Điều này chỉ liên quan gián tiếp đến trạng thái đóng gói, đó là điều tôi tin David Arno đang nghĩ đến. Lời khuyên này không phải lúc nào cũng (nhưng thường không) đề xuất trạng thái đóng gói và lời khuyên này cũng áp dụng cho các đối tượng bất biến. Trong thực tế, chỉ đơn thuần đặt tên hằng là một hình thức (rất cơ bản) để gói gọn những gì khác nhau.

CandiedOrange kết hợp rõ ràng "những gì thay đổi" với "chi tiết". Điều này chỉ đúng một phần. Tôi đồng ý rằng bất kỳ mã nào thay đổi là "chi tiết" theo một nghĩa nào đó, nhưng "chi tiết" có thể không thay đổi (trừ khi bạn xác định "chi tiết" để tạo ra tautological này). Có thể có lý do để gói gọn các chi tiết không thay đổi, nhưng điều này không phải là một. Nói một cách đơn giản, nếu bạn rất tự tin rằng "chó", "mèo" và "vịt" sẽ là những loại duy nhất bạn cần phải đối phó, thì điều này không đề xuất việc tái cấu trúc CandiedOrange.

Đúc ví dụ của CandiedOrange trong một ngữ cảnh khác, giả sử chúng ta có ngôn ngữ thủ tục như C. Nếu tôi có một số mã có chứa:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Tôi có thể mong đợi một cách hợp lý rằng đoạn mã này sẽ thay đổi trong tương lai. Tôi có thể "gói gọn" nó chỉ bằng cách xác định một thủ tục mới:

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

và sử dụng thủ tục mới này thay vì khối mã (tức là tái cấu trúc "phương thức trích xuất"). Tại thời điểm này, thêm một loại "bò" hoặc bất cứ điều gì chỉ yêu cầu cập nhật speakthủ tục. Tất nhiên, bằng ngôn ngữ OO, thay vào đó, bạn có thể tận dụng công văn động như được ám chỉ bởi câu trả lời của CandiedOrange. Điều này sẽ xảy ra một cách tự nhiên nếu bạn truy cập petthông qua một giao diện. Loại bỏ logic có điều kiện thông qua công văn động là một mối quan tâm trực giao, đó là một phần lý do tại sao tôi thực hiện biểu hiện thủ tục này. Tôi cũng muốn nhấn mạnh rằng điều này không yêu cầu các tính năng đặc biệt đối với OOP. Ngay cả trong ngôn ngữ OO, việc gói gọn những gì thay đổi không nhất thiết có nghĩa là một lớp hoặc giao diện mới cần được tạo.

Là một ví dụ điển hình hơn (gần với OO hơn), chúng tôi muốn xóa các bản sao khỏi danh sách. Giả sử chúng tôi thực hiện nó bằng cách lặp lại danh sách theo dõi các mục chúng tôi đã thấy cho đến nay trong một danh sách khác và xóa bất kỳ mục nào chúng tôi đã thấy. Thật hợp lý khi cho rằng chúng tôi có thể muốn thay đổi cách chúng tôi theo dõi các mục được nhìn thấy, ít nhất là vì lý do hiệu suất. Dictum để gói gọn những gì khác nhau cho thấy rằng chúng ta nên xây dựng một kiểu dữ liệu trừu tượng để thể hiện tập hợp các mục được nhìn thấy. Thuật toán của chúng tôi hiện được xác định theo kiểu dữ liệu Đặt trừu tượng này và nếu chúng tôi quyết định chuyển sang cây tìm kiếm nhị phân, thuật toán của chúng tôi không cần phải thay đổi hoặc quan tâm. Trong ngôn ngữ OO, chúng tôi có thể sử dụng một lớp hoặc giao diện để nắm bắt kiểu dữ liệu trừu tượng này. Trong một ngôn ngữ như SML / O '

Đối với một ví dụ hướng theo yêu cầu, giả sử bạn cần xác thực một số lĩnh vực liên quan đến logic kinh doanh. Mặc dù bạn có thể có các yêu cầu cụ thể bây giờ, bạn hoàn toàn nghi ngờ rằng chúng sẽ phát triển. Bạn có thể gói gọn logic hiện tại trong thủ tục / hàm / quy tắc / lớp của chính nó.

Mặc dù đây là một mối quan tâm trực giao không phải là một phần của "đóng gói những gì thay đổi", nhưng nó thường là tự nhiên để trừu tượng hóa, được tham số hóa bởi, logic hiện được đóng gói. Điều này thường dẫn đến mã linh hoạt hơn và cho phép thay đổi logic bằng cách thay thế trong một triển khai thay thế thay vì sửa đổi logic được đóng gói.


Ôi cay đắng ngọt ngào. Vâng, đây không chỉ là vấn đề OOP. Bạn bắt tôi để cho một mô hình ngôn ngữ chi tiết làm ô nhiễm câu trả lời của tôi và trừng phạt tôi một cách đúng đắn bằng cách "thay đổi" mô hình.
candied_orange 7/12/2016

"Ngay cả trong ngôn ngữ OO, việc đóng gói những gì khác nhau không nhất thiết phải tạo ra một lớp hoặc giao diện mới" - thật khó để tưởng tượng một tình huống không tạo ra một lớp hoặc giao diện mới sẽ không vi phạm SRP
taurelas

11

"Đóng gói những gì thay đổi" đề cập đến việc ẩn các chi tiết triển khai có thể thay đổi và phát triển.

Thí dụ:

Ví dụ, giả sử lớp Coursetheo dõi Studentscó thể đăng ký (). Bạn có thể thực hiện nó với một LinkedListvà hiển thị container để cho phép lặp lại trên nó:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Nhưng đây không phải là một ý tưởng tốt:

  • Đầu tiên, mọi người có thể thiếu hành vi tốt và sử dụng nó như tự phục vụ, trực tiếp thêm sinh viên vào danh sách, mà không thông qua phương thức register ().
  • Nhưng thậm chí còn khó chịu hơn: điều này tạo ra sự phụ thuộc của "mã sử dụng" vào các chi tiết triển khai bên trong của lớp được sử dụng. Điều này có thể ngăn chặn sự phát triển trong tương lai của lớp, ví dụ nếu bạn muốn sử dụng một mảng, vectơ, bản đồ với số chỗ ngồi hoặc cấu trúc dữ liệu liên tục của riêng bạn.

Nếu bạn đóng gói những gì khác nhau (hay nói đúng hơn, những gì có thể thay đổi), bạn giữ tự do cho cả mã sử dụng và lớp được đóng gói để tự phát triển lẫn nhau. Đây là lý do tại sao nó là một nguyên tắc quan trọng trong OOP.

Đọc thêm:

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.