Mã nào nên được bao gồm trong một lớp trừu tượng?


9

Gần đây tôi gặp rắc rối về việc sử dụng các lớp trừu tượng.

Đôi khi một lớp trừu tượng được tạo trước và hoạt động như một khuôn mẫu về cách các lớp dẫn xuất sẽ hoạt động. Điều đó có nghĩa là, ít nhiều, họ cung cấp một số chức năng cấp cao nhưng bỏ qua một số chi tiết nhất định sẽ được thực hiện bởi các lớp dẫn xuất. Lớp trừu tượng xác định sự cần thiết của các chi tiết này bằng cách đặt một số phương thức trừu tượng. Trong các trường hợp như vậy, một lớp trừu tượng hoạt động như một bản thiết kế, một mô tả cấp cao về chức năng hoặc bất cứ điều gì bạn muốn gọi nó. Nó không thể được sử dụng riêng, nhưng phải được chuyên môn hóa để xác định các chi tiết còn sót lại trong quá trình thực hiện cấp cao.

Đôi khi, lớp trừu tượng được tạo ra sau khi tạo một số lớp "dẫn xuất" (vì lớp cha / lớp trừu tượng không có ở đó nên chúng chưa được dẫn xuất, nhưng bạn biết ý tôi là gì). Trong các trường hợp này, lớp trừu tượng thường được sử dụng làm nơi bạn có thể đặt bất kỳ loại mã phổ biến nào mà các lớp dẫn xuất hiện tại chứa.

Sau khi thực hiện các quan sát trên, tôi tự hỏi trường hợp nào trong hai trường hợp này nên là quy tắc. Có nên sử dụng bất kỳ loại chi tiết nào cho lớp trừu tượng chỉ vì chúng hiện đang phổ biến trong tất cả các lớp dẫn xuất? Có nên sử dụng mã phổ biến không phải là một phần của chức năng cấp cao không?

Có phải mã có thể không có ý nghĩa đối với lớp trừu tượng chỉ vì nó xảy ra phổ biến cho các lớp dẫn xuất?

Tôi xin đưa ra một ví dụ: Tóm tắt lớp A có phương thức a () và phương thức trừu tượng aq (). Phương thức aq (), trong cả hai lớp dẫn xuất AB và AC, sử dụng phương thức b (). Có nên b () chuyển sang A? Nếu có, thì trong trường hợp ai đó chỉ nhìn vào A (giả vờ AB và AC không có ở đó), sự tồn tại của b () sẽ không có ý nghĩa nhiều! đây là một điều xấu? Nếu ai đó có thể có một cái nhìn trong một lớp trừu tượng và hiểu những gì đang xảy ra mà không cần truy cập các lớp dẫn xuất?

Thành thật mà nói, tại thời điểm hỏi điều này, tôi có xu hướng tin rằng viết một lớp trừu tượng có ý nghĩa mà không cần phải tìm trong các lớp dẫn xuất, đó là vấn đề về mã sạch và kiến ​​trúc sạch. Không thực sự thích ý tưởng về một lớp trừu tượng hoạt động như một bãi chứa cho bất kỳ loại mã nào xảy ra là phổ biến trong tất cả các lớp dẫn xuất.

Bạn nghĩ gì / thực hành?


7
Tại sao phải có một "quy tắc?"
Robert Harvey

1
Writing an abstract class that makes sense without having to look in the derived classes is a matter of clean code and clean architecture.-- Tại sao? Không phải là trong quá trình thiết kế và phát triển một ứng dụng, tôi phát hiện ra rằng một số lớp có chức năng chung có thể tự nhiên được tái cấu trúc thành một lớp trừu tượng? Tôi phải đủ thông minh để luôn lường trước điều này trước khi viết bất kỳ mã nào cho các lớp dẫn xuất? Nếu tôi thất bại trong việc này, tôi có bị cấm thực hiện tái cấu trúc như vậy không? Tôi phải bỏ mã của tôi và bắt đầu lại?
Robert Harvey

Xin lỗi, nếu tôi bị hiểu lầm! Tôi đã cố gắng nói những gì tôi cảm thấy (hiện tại) là một thực tiễn tốt hơn và không ngụ ý rằng cần phải có một quy tắc tuyệt đối. Hơn nữa, tôi không ngụ ý rằng bất kỳ mã nào thuộc về lớp trừu tượng chỉ nên được viết trước. Trong thực tế, tôi đã mô tả làm thế nào một lớp trừu tượng kết thúc bằng mã cấp cao (hoạt động như một khuôn mẫu cho các dẫn xuất) cũng như mã cấp thấp (mà bạn không thể hiểu được tính khả dụng của nó khi bạn không nhìn vào các lớp dẫn xuất).
Alexandros Gougousis

@RobertHarvey cho một lớp cơ sở công cộng, bạn sẽ bị cấm nhìn vào các lớp dẫn xuất. Đối với các lớp nội bộ, nó không làm cho sự khác biệt.
Frank Hileman

Câu trả lời:


4

Tôi xin đưa ra một ví dụ: Tóm tắt lớp A có phương thức a () và phương thức trừu tượng aq (). Phương thức aq (), trong cả hai lớp dẫn xuất AB và AC, sử dụng phương thức b (). Có nên b () chuyển sang A? Nếu có, thì trong trường hợp ai đó chỉ nhìn vào A (giả vờ AB và AC không có ở đó), sự tồn tại của b () sẽ không có ý nghĩa nhiều! đây là một điều xấu? Nếu ai đó có thể có một cái nhìn trong một lớp trừu tượng và hiểu những gì đang xảy ra mà không cần truy cập các lớp dẫn xuất?

Những gì bạn hỏi là nơi để đặt b(), và, trong một số ý nghĩa khác, một câu hỏi là liệu Asự lựa chọn tốt nhất là siêu hạng ngay lập tức cho ABAC.

Có vẻ như có ba sự lựa chọn:

  1. để lại b()trong cả hai ABAC
  2. tạo một lớp trung gian ABAC-Parentkế thừa từ Avà giới thiệu b(), và sau đó được sử dụng làm siêu lớp ngay lập tức cho ABAC
  3. đưa b()vào A(không biết liệu một lớp tương lai khác ADsẽ muốn b()hay không)

  1. chịu đựng không được KHÔ .
  2. bị YAGNI .
  3. vì vậy, mà rời khỏi cái này

Cho đến khi một lớp khác ADkhông muốn b()trình bày, (3) dường như là lựa chọn đúng đắn.

Tại thời điểm như một ADmón quà, sau đó chúng ta có thể cấu trúc lại cách tiếp cận trong (2) - sau tất cả, đó là phần mềm!


7
Có một lựa chọn thứ 4. Đừng đưa b()vào bất kỳ lớp học nào. Làm cho nó trở thành một hàm miễn phí, lấy tất cả dữ liệu của nó làm đối số và có cả hai ABACgọi nó. Điều này có nghĩa là bạn không cần phải di chuyển nó hoặc tạo thêm bất kỳ lớp nào khi bạn thêm AD.
dùng1118321

1
@ user1118321, xuất sắc, suy nghĩ đẹp ngoài hộp. Có ý nghĩa đặc biệt nếu b()không cần trạng thái tồn tại lâu dài.
Erik Eidt

Điều gây phiền toái cho tôi trong (3) là lớp trừu tượng không mô tả nữa một đoạn hành vi khép kín. Đó là các bit và các đoạn mã và tôi không thể hiểu mã lớp trừu tượng mà không qua lại giữa cái này và tất cả các lớp dẫn xuất.
Alexandros Gougousis

Bạn có thể tạo một cơ sở / mặc định acmà gọi bthay vì actrừu tượng?
Erik Eidt

3
Đối với tôi, câu hỏi quan trọng nhất là, TẠI SAO cả AB và AC đều sử dụng cùng một phương thức b (). Nếu chỉ là trùng hợp ngẫu nhiên, tôi sẽ để lại hai triển khai tương tự ở AB và AC. Nhưng có lẽ đó là vì tồn tại một số trừu tượng chung cho AB và AC. Đối với tôi, DRY không phải là một giá trị quá lớn, nhưng một gợi ý cho bạn rằng bạn có thể đã bỏ lỡ một số trừu tượng hữu ích.
Ralf Kleberhoff

2

Một lớp trừu tượng không có nghĩa là bãi thải cho các chức năng hoặc dữ liệu khác nhau được đưa vào lớp trừu tượng vì nó thuận tiện.

Một nguyên tắc nhỏ dường như cung cấp cách tiếp cận hướng đối tượng đáng tin cậy và có thể mở rộng nhất là " Thích thành phần hơn sự kế thừa ". Một lớp trừu tượng có lẽ được coi là một đặc tả giao diện không chứa bất kỳ mã nào.

Nếu bạn có một phương thức nào đó là một loại phương thức thư viện đi cùng với lớp trừu tượng, thì một phương thức đó là phương tiện có khả năng nhất để thể hiện một số chức năng hoặc hành vi mà các lớp xuất phát từ một lớp trừu tượng thường cần có để tạo ra một lớp ở giữa lớp trừu tượng và các lớp khác nơi phương thức này có sẵn. Lớp mới này cung cấp một khởi tạo cụ thể của lớp trừu tượng xác định một thay thế hoặc đường dẫn thực hiện cụ thể bằng cách cung cấp phương thức này.

Ý tưởng của một lớp trừu tượng là có một mô hình trừu tượng về những gì các lớp dẫn xuất thực sự thực hiện lớp trừu tượng được cho là cung cấp cho đến khi dịch vụ hoặc hành vi. Kế thừa rất dễ sử dụng và rất nhiều lần các lớp hữu ích nhất bao gồm nhiều lớp khác nhau sử dụng mẫu mixin .

Tuy nhiên, luôn có những câu hỏi thay đổi, điều gì sẽ thay đổi và nó sẽ thay đổi như thế nào và tại sao nó sẽ thay đổi.

Kế thừa có thể dẫn đến các nguồn dễ vỡ và dễ vỡ (xem thêm Kế thừa: chỉ cần ngừng sử dụng nó! ).

Vấn đề lớp cơ sở mong manh là một vấn đề kiến ​​trúc cơ bản của các hệ thống lập trình hướng đối tượng, trong đó các lớp cơ sở (siêu lớp) được coi là "dễ vỡ" vì dường như các sửa đổi an toàn cho lớp cơ sở, khi được kế thừa bởi các lớp dẫn xuất, có thể khiến các lớp dẫn xuất bị trục trặc . Lập trình viên không thể xác định liệu một sự thay đổi của lớp cơ sở có an toàn hay không bằng cách kiểm tra cách ly các phương thức của lớp cơ sở.


Tôi chắc chắn rằng bất kỳ khái niệm OOP nào cũng có thể dẫn đến các vấn đề nếu nó bị lạm dụng hoặc lạm dụng hoặc lạm dụng! Hơn nữa, việc đưa ra giả định rằng b () là một phương thức thư viện (ví dụ: một hàm thuần túy không có các phụ thuộc khác) thu hẹp phạm vi câu hỏi của tôi. Hãy tránh điều này.
Alexandros Gougousis

@AlexandrosGougousis Tôi không cho rằng đó b()là một hàm thuần túy. Nó có thể là một dấu chấm hoặc một mẫu hoặc một cái gì đó khác. Tôi đang sử dụng cụm từ "phương thức thư viện" có nghĩa là một số thành phần cho dù chức năng thuần túy, đối tượng COM hoặc bất cứ điều gì được sử dụng cho chức năng mà nó cung cấp cho giải pháp.
Richard Chambers

Xin lỗi, nếu tôi không rõ ràng! Tôi đã đề cập đến hàm thuần túy là một trong nhiều ví dụ (bạn đã đưa ra nhiều hơn).
Alexandros Gougousis

1

Câu hỏi của bạn cho thấy một hoặc hoặc cách tiếp cận các lớp trừu tượng. Nhưng tôi nghĩ rằng bạn nên coi chúng như một công cụ khác trong hộp công cụ của bạn. Và sau đó câu hỏi trở thành: Đối với những công việc / vấn đề nào là các lớp trừu tượng, công cụ phù hợp?

Một trường hợp sử dụng tuyệt vời là triển khai mẫu phương thức mẫu . Bạn đặt tất cả logic bất biến trong lớp trừu tượng và logic biến thể trong các lớp con của nó. Lưu ý rằng bản thân logic được chia sẻ là không đầy đủ và không có chức năng. Hầu hết thời gian là về việc thực hiện một thuật toán, trong đó một số bước luôn giống nhau, nhưng ít nhất một bước khác nhau. Đặt một bước này như một phương thức trừu tượng được gọi từ một trong các hàm bên trong lớp trừu tượng.

Đôi khi một lớp trừu tượng được tạo trước và hoạt động như một khuôn mẫu về cách các lớp dẫn xuất sẽ hoạt động. Điều đó có nghĩa là, ít nhiều, họ cung cấp một số chức năng cấp cao nhưng bỏ qua một số chi tiết nhất định sẽ được thực hiện bởi các lớp dẫn xuất. Lớp trừu tượng xác định sự cần thiết của các chi tiết này bằng cách đặt một số phương thức trừu tượng. Trong các trường hợp như vậy, một lớp trừu tượng hoạt động như một bản thiết kế, một mô tả cấp cao về chức năng hoặc bất cứ điều gì bạn muốn gọi nó. Nó không thể được sử dụng riêng, nhưng phải được chuyên môn hóa để xác định các chi tiết còn sót lại trong quá trình thực hiện cấp cao.

Tôi nghĩ rằng ví dụ đầu tiên của bạn về cơ bản là một mô tả về mẫu phương thức mẫu (sửa tôi nếu tôi sai), vì vậy tôi sẽ coi đây là trường hợp sử dụng hoàn toàn hợp lệ của các lớp trừu tượng.

Đôi khi, lớp trừu tượng được tạo ra sau khi tạo một số lớp "dẫn xuất" (vì lớp cha / lớp trừu tượng không có ở đó nên chúng chưa được dẫn xuất, nhưng bạn biết ý tôi là gì). Trong các trường hợp này, lớp trừu tượng thường được sử dụng làm nơi bạn có thể đặt bất kỳ loại mã phổ biến nào mà các lớp dẫn xuất hiện tại chứa.

Đối với ví dụ thứ hai của bạn, tôi nghĩ rằng sử dụng các lớp trừu tượng không phải là lựa chọn tối ưu, bởi vì có các phương pháp ưu việt để xử lý logic chung và mã trùng lặp. Hãy nói rằng bạn có lớp trừu tượng A, các lớp thừa kế BCvà cả các lớp thừa kế chia sẻ một số logic trong các hình thức của phương pháp s(). Để quyết định cách tiếp cận chính xác để thoát khỏi sự trùng lặp, điều quan trọng là phải biết, liệu phương thức có phải s()là một phần của giao diện công cộng hay không.

Nếu nó không (như trong ví dụ cụ thể của bạn với phương thức b()), trường hợp này khá đơn giản. Chỉ cần tạo một lớp riêng biệt từ nó, cũng trích xuất bối cảnh cần thiết để thực hiện thao tác. Đây là một ví dụ cổ điển của thành phần trên thừa kế . Nếu có ít hoặc không có ngữ cảnh cần thiết, một hàm trợ giúp đơn giản có thể đã đủ như đề xuất trong một số nhận xét.

Nếu s()là một phần của giao diện công cộng, nó trở nên khó khăn hơn một chút. Theo giả định s()không liên quan gì BCcó liên quan đến A, bạn không nên đặt s()bên trong A. Vậy đặt nó ở đâu? Tôi sẽ tranh luận về việc khai báo một giao diện riêng I, trong đó xác định s(). Sau đó, một lần nữa, bạn nên tạo một lớp riêng có chứa logic để thực hiện s()và cả hai BCphụ thuộc vào nó.

Cuối cùng, đây là một liên kết đến một câu trả lời xuất sắc cho một câu hỏi thú vị về SO có thể giúp bạn quyết định khi nào nên đi giao diện và khi nào cho một lớp trừu tượng:

Một lớp trừu tượng tốt sẽ làm giảm số lượng mã phải viết lại vì chức năng hoặc trạng thái của nó có thể được chia sẻ.


Tôi đồng ý rằng s () là một phần của giao diện công cộng khiến mọi thứ trở nên khó khăn hơn nhiều. Tôi thường sẽ tránh định nghĩa các phương thức công khai gọi các phương thức công khai khác trong cùng một lớp.
Alexandros Gougousis

1

Dường như với tôi bạn đang thiếu điểm định hướng đối tượng, cả về mặt logic và kỹ thuật. Bạn mô tả hai kịch bản: nhóm hành vi phổ biến của các loại trong một lớp cơ sở và đa hình. Đây là cả hai ứng dụng hợp pháp của một lớp trừu tượng. Nhưng việc bạn có nên làm cho lớp trừu tượng hay không phụ thuộc vào mô hình phân tích của bạn, chứ không phải các khả năng kỹ thuật.

Bạn nên nhận ra một loại không có hiện thân trong thế giới thực, nhưng vẫn đặt nền tảng cho các loại cụ thể tồn tại. Ví dụ: một con vật. Không có những thứ như một con vật. Nó luôn luôn là một con chó hoặc một con mèo hoặc bất cứ điều gì nhưng không có động vật thực sự. Động vật khung tất cả chúng.

Sau đó, bạn nói về cấp độ. Cấp độ không phải là một phần của thỏa thuận khi nói đến thừa kế. Cũng không nhận ra dữ liệu chung hoặc hành vi phổ biến, đó là một phương pháp kỹ thuật có thể sẽ không giúp đỡ. Bạn nên nhận ra một bản mẫu và sau đó chèn lớp cơ sở. Nếu không có khuôn mẫu như vậy, bạn có thể tốt hơn với một vài giao diện được thực hiện bởi nhiều lớp.

Tên của lớp trừu tượng của bạn cùng với các thành viên của nó sẽ có ý nghĩa. Nó phải độc lập với bất kỳ lớp dẫn xuất nào, cả về mặt kỹ thuật và logic. Giống như Animal có thể có một phương pháp trừu tượng Eat (sẽ là đa hình) và các thuộc tính trừu tượng boolean IsPet và IsLifry, điều đó có ý nghĩa mà không cần biết về mèo hoặc chó hoặc lợn. Bất kỳ sự phụ thuộc (kỹ thuật hoặc logic) chỉ nên đi một chiều: từ hậu duệ đến cơ sở. Bản thân các lớp cơ sở cũng không có kiến ​​thức về các lớp giảm dần.


Định kiến ​​mà bạn đề cập ít nhiều giống với ý tôi khi tôi nói về chức năng cấp cao hơn hoặc khi người khác nói về các khái niệm tổng quát được mô tả bởi lớp cao hơn trong phân cấp (so với các khái niệm chuyên biệt hơn được mô tả bởi các lớp thấp hơn trong hệ thống cấp bậc).
Alexandros Gougousis

"Bản thân các lớp cơ sở cũng không nên có kiến ​​thức về các lớp giảm dần." Tôi hoàn toàn đồng ý với điều này. Một lớp cha (trừu tượng hay không) nên chứa một đoạn logic nghiệp vụ khép kín, không cần kiểm tra việc thực hiện lớp con để hiểu ý nghĩa của nó.
Alexandros Gougousis

Có một điều khác về một lớp cơ sở biết các kiểu con của nó. Bất cứ khi nào một kiểu con mới được phát triển, lớp cơ sở cần phải được sửa đổi, ngược lại và vi phạm nguyên tắc đóng mở. Gần đây tôi đã gặp điều này trong mã hiện có. Một lớp cơ sở có một phương thức tĩnh tạo ra các thể hiện của các kiểu con dựa trên cấu hình của ứng dụng. Mục đích là một mô hình nhà máy. Làm theo cách này cũng vi phạm SRP. Với một số điểm RẮN tôi từng nghĩ là "duh, điều đó là hiển nhiên" hoặc "ngôn ngữ sẽ tự động xử lý vấn đề đó" nhưng tôi thấy mọi người sáng tạo hơn tôi có thể tưởng tượng.
Martin Maat

0

Trường hợp đầu tiên , từ câu hỏi của bạn:

Trong các trường hợp như vậy, một lớp trừu tượng hoạt động như một bản thiết kế, một mô tả cấp cao về chức năng hoặc bất cứ điều gì bạn muốn gọi nó.

Trường hợp thứ hai:

Một số lần khác ... lớp trừu tượng thường được sử dụng làm nơi bạn có thể đặt bất kỳ loại mã phổ biến nào mà các lớp dẫn xuất hiện tại chứa.

Sau đó, câu hỏi của bạn :

Tôi đang tự hỏi cái nào trong hai trường hợp này nên là quy tắc. ... Có phải mã có thể không có ý nghĩa đối với lớp trừu tượng chỉ vì nó xảy ra phổ biến cho các lớp dẫn xuất?

IMO bạn có hai kịch bản khác nhau, do đó bạn không thể rút ra một quy tắc duy nhất để quyết định thiết kế nào phải được áp dụng.

Đối với trường hợp đầu tiên, chúng ta có những thứ như mẫu Phương thức mẫu đã được đề cập trong các câu trả lời khác.

Đối với trường hợp thứ hai, bạn đã đưa ra ví dụ về phương thức trừu tượng A nhận ab (); IMO phương thức b () chỉ nên được di chuyển nếu nó có ý nghĩa đối với TẤT CẢ các lớp dẫn xuất khác. Nói cách khác, nếu bạn di chuyển nó bởi vì nó được sử dụng ở hai nơi, có lẽ đây không phải là một lựa chọn tốt, bởi vì ngày mai có thể có một lớp Derogen cụ thể mới mà b () không có ý nghĩa gì cả. Ngoài ra, như bạn đã nói, nếu bạn xem riêng lớp A và b () cũng không có ý nghĩa gì trong bối cảnh đó, thì có lẽ đó là một gợi ý rằng đây cũng không phải là một lựa chọn thiết kế tốt.


-1

Tôi đã có chính xác những câu hỏi tương tự một thời gian trước đây!

Những gì tôi đến là bất cứ khi nào tôi vấp phải vấn đề này, tôi thấy rằng có một số khái niệm ẩn mà tôi không tìm thấy. Và khái niệm này có lẽ nhiều hơn không nên được thể hiện với một số đối tượng giá trị . Bạn có ví dụ rất trừu tượng, vì vậy tôi e rằng tôi không thể chứng minh ý tôi sử dụng mã của bạn. Nhưng đây là một trường hợp từ thực tiễn của tôi. Tôi đã có hai lớp đại diện cho một cách để gửi yêu cầu đến tài nguyên bên ngoài và phân tích phản hồi. Có những điểm tương đồng trong cách các yêu cầu được hình thành và cách các phản hồi được phân tích cú pháp. Vì vậy, đó là cách phân cấp của tôi trông như thế nào:

abstract class AbstractProtocol
{
    /**
     * @return array Registration params to send
     */
    abstract protected function assembleRegistrationPart();

    /**
     * @return array Payment params to send
     */
    abstract protected function assemblePaymentPart();

    protected function doSend(array $data)
    {
        return
            (new HttpClient(
                [
                    'timeout' => 60,
                    'encoding' => 'utf-8',
                    'language' => 'en',
                ]
            ))
                ->send($data);
    }

    protected function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}

class ClassicProtocol extends AbstractProtocol
{
    public function send()
    {
        $registration = $this->assembleRegistrationPart();
        $payment = $this->assemblePaymentPart();
        $specificParams = $this->assembleClassicSpecificPart();

        $dataToSend =
            array_merge(
                $registration, $payment, $specificParams
            );

        $this->log($dataToSend);

        $this->doSend($dataToSend);
    }

    protected function assembleRegistrationPart()
    {
        return ['hello' => 'there'];
    }

    protected function assemblePaymentPart()
    {
        return ['pay' => 'yes'];
    }
}

Mã như vậy chỉ ra rằng tôi chỉ đơn giản là lạm dụng thừa kế. Đó là cách nó có thể được tái cấu trúc:

class ClassicProtocol
{
    private $request;
    private $logger;

    public function __construct(Request $request, Logger $logger, Client $client)
    {
        $this->request = $request;
        $this->client = $client;
        $this->logger = $logger;
    }

    public function send()
    {
        $this->logger->log($this->request->getData());
        $this->client->send($this->request->getData());
    }
}

$protocol =
    new ClassicProtocol(
        new PaymentRequest(
            new RegistrationData(),
            new PaymentData(),
            new ClassicSpecificData()
        ),
        new ClassicLogger(),
        new ClassicClient()
    );

class RegistrationData
{
    public function getData()
    {
        return ['hello' => 'there'];
    }
}

class PaymentData
{
    public function getData()
    {
        return ['pay' => 'yes'];
    }
}

class ClassicLogger
{
    public function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}
class ClassicClient
{
    private $properties;

    public function __construct()
    {
        $this->properties =
            [
                'timeout' => 60,
                'encoding' => 'utf-8',
                'language' => 'en',
            ];
    }
}

Từ đó tôi đối xử rất cẩn thận khiến tôi bị tổn thương nhiều lần.

Kể từ đó tôi đi đến một kết luận khác về thừa kế. Tôi mạnh mẽ chống lại sự kế thừa dựa trên cấu trúc bên trong . Nó phá vỡ đóng gói, nó dễ vỡ, sau tất cả là thủ tục. Và khi tôi phân tách tên miền của mình đúng cách , việc thừa kế đơn giản là không xảy ra rất thường xuyên.


@Downvoter, xin hãy giúp tôi và nhận xét có gì sai với câu trả lời này.
Vadim Samokhin
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.