Các lớp con chỉ dành cho nhà xây dựng: Đây có phải là một mô hình chống?


37

Tôi đã có một cuộc thảo luận với một đồng nghiệp, và cuối cùng chúng tôi đã có những trực giác mâu thuẫn về mục đích của việc phân lớp. Trực giác của tôi là nếu một chức năng chính của một lớp con là thể hiện một phạm vi giới hạn các giá trị có thể có của cha mẹ thì có lẽ nó không nên là một lớp con. Ông lập luận cho trực giác ngược lại: phân lớp đó thể hiện một đối tượng là "cụ thể" hơn, và do đó, mối quan hệ của lớp con là phù hợp hơn.

Nói một cách cụ thể hơn, tôi nghĩ rằng nếu tôi có một lớp con mở rộng một lớp cha nhưng mã duy nhất mà lớp con ghi đè là một hàm tạo (vâng, tôi biết các hàm tạo thường không "ghi đè", chịu đựng với tôi), sau đó những gì thực sự cần thiết là một phương pháp trợ giúp.

Ví dụ, hãy xem xét lớp học có phần thực tế này:

public class DataHelperBuilder
{
    public string DatabaseEngine { get; set; }
    public string ConnectionString { get; set; }

    public DataHelperBuilder(string databaseEngine, string connectionString)
    {
        DatabaseEngine = databaseEngine;
        ConnectionString = connectionString;
    }

    // Other optional "DataHelper" configuration settings omitted

    public DataHelper CreateDataHelper()
    {
        Type dataHelperType = DatabaseEngineTypeHelper.GetType(DatabaseEngine);
        DataHelper dh = (DataHelper)Activator.CreateInstance(dataHelperType);
        dh.SetConnectionString(ConnectionString);

        // Omitted some code that applies decorators to the returned object
        // based on omitted configuration settings

        return dh;
    }
}

Yêu cầu của ông là nó sẽ hoàn toàn thích hợp để có một lớp con như thế này:

public class SystemDataHelperBuilder
{
    public SystemDataHelperBuilder()
        : base(Configuration.GetSystemDatabaseEngine(),
               Configuration.GetSystemConnectionString())
    {
    }
 }

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

  1. Trong số những người nói về các mẫu thiết kế, những trực giác nào là chính xác? Là phân lớp như mô tả ở trên một mô hình chống?
  2. Nếu nó là một mô hình chống, tên của nó là gì?

Tôi xin lỗi nếu điều này hóa ra là một câu trả lời dễ hiểu; các tìm kiếm của tôi trên google chủ yếu trả về thông tin về mô hình chống xây dựng kính viễn vọng và không thực sự là thứ tôi đang tìm kiếm.


2
Tôi coi từ "Người trợ giúp" trong tên là một phản mẫu. Nó giống như đọc một bài luận với các tài liệu tham khảo liên tục đến một điều và một điều gì đó. Nói nó gì . Có phải là một nhà máy? Constructor? Đối với tôi nó không có quá trình suy nghĩ lười biếng, và nó khuyến khích vi phạm nguyên tắc trách nhiệm duy nhất, vì một tên ngữ nghĩa phù hợp sẽ điều khiển nó. Tôi đồng ý với các cử tri khác và bạn nên chấp nhận câu trả lời của @ lxrec. Tạo một lớp con cho một phương thức xuất xưởng là hoàn toàn vô nghĩa, trừ khi bạn không thể tin tưởng người dùng truyền các đối số chính xác như được chỉ định trong tài liệu. Trong trường hợp đó, có được người dùng mới.
Hội trường Aaron

Tôi hoàn toàn đồng ý với câu trả lời của @ Ixrec. Rốt cuộc, câu trả lời của anh ấy nói rằng tôi đã đúng và đồng nghiệp của tôi đã sai. ;-) Nhưng nghiêm túc, đó chính xác là lý do tại sao tôi cảm thấy kỳ lạ khi chấp nhận nó; Tôi nghĩ rằng tôi có thể bị buộc tội sai lệch. Nhưng tôi mới làm quen với các lập trình viên StackExchange; Tôi sẵn sàng chấp nhận nó nếu tôi chắc chắn rằng nó sẽ không vi phạm nghi thức xã giao.
HardlyKnowEm

1
Không, bạn nên chấp nhận câu trả lời mà bạn cho là có giá trị nhất. Cộng đồng cho rằng giá trị nhất từ ​​trước đến nay là của Ixrec cho đến hơn 3 đến 1. Do đó, họ sẽ mong bạn chấp nhận nó. Trong cộng đồng này, có rất ít sự thất vọng được thể hiện ở một người hỏi chấp nhận câu trả lời sai, nhưng có rất nhiều sự thất vọng được thể hiện ở những người hỏi không bao giờ chấp nhận câu trả lời. Lưu ý rằng không ai phàn nàn về câu trả lời được chấp nhận ở đây, thay vào đó họ phàn nàn về việc bỏ phiếu, dường như là chính nó: stackoverflow.com/q/2052390/541136
Aaron Hall

Rất tốt, tôi sẽ chấp nhận nó. Cảm ơn bạn đã dành thời gian để giáo dục tôi về các tiêu chuẩn cộng đồng ở đây.
HardlyKnowEm

Câu trả lời:


54

Nếu tất cả những gì bạn muốn làm là tạo lớp X với một số đối số nhất định, thì phân lớp là một cách kỳ lạ để thể hiện ý định đó, bởi vì bạn không sử dụng bất kỳ tính năng nào mà các lớp và kế thừa cung cấp cho bạn. Nó không thực sự là một mô hình chống, nó chỉ lạ và hơi vô nghĩa (trừ khi bạn có một số lý do khác cho nó). Một cách tự nhiên hơn để thể hiện ý định này sẽ là Phương thức của Nhà máy , trong trường hợp này là một cái tên lạ mắt cho "phương thức trợ giúp" của bạn.

Về trực giác chung, cả "cụ thể hơn" và "phạm vi hạn chế" đều là những cách suy nghĩ có hại cho các lớp con, bởi vì cả hai đều ngụ ý rằng biến Square thành một lớp con của Hình chữ nhật là một ý tưởng tốt. Không dựa vào một cái gì đó trang trọng như LSP, tôi sẽ nói một trực giác tốt hơn là một lớp con cung cấp việc thực hiện giao diện cơ sở hoặc mở rộng giao diện để thêm một số chức năng mới.


2
Phương thức của nhà máy mặc dù không cho phép bạn thực thi một số hành vi nhất định (bị chi phối bởi các đối số) trong cơ sở mã của bạn. Và đôi khi thật hữu ích khi chú thích một cách rõ ràng rằng lớp / phương thức / trường này SystemDataHelperBuildercụ thể.
Telastyn

3
Ah, đối số hình vuông so với hình chữ nhật là chính xác những gì tôi đang tìm kiếm, tôi nghĩ. Cảm ơn!
HardlyKnowEm

7
Tất nhiên, có hình vuông là một lớp con của hình chữ nhật có thể ổn nếu chúng không thay đổi. Bạn thực sự phải tìm đến LSP.
David Conrad

10
Vấn đề của "vấn đề" hình vuông / hình chữ nhật là hình dạng hình học là bất biến. Bạn luôn có thể sử dụng hình vuông bất cứ nơi nào hình chữ nhật có ý nghĩa, nhưng thay đổi kích thước không phải là hình vuông hoặc hình chữ nhật làm.
Doval

3
@nha LSP = Nguyên tắc thay thế Liskov
Ixrec

16

Những trực giác nào là đúng?

Đồng nghiệp của bạn là chính xác (giả sử hệ thống loại tiêu chuẩn).

Hãy suy nghĩ về nó, các lớp đại diện cho các giá trị pháp lý có thể. Nếu lớp Acó một trường byte F, bạn có thể có xu hướng nghĩ rằng Acó 256 giá trị pháp lý, nhưng trực giác đó không chính xác. Ađang giới hạn "mọi hoán vị của các giá trị" thành "phải có trường Flà 0-255".

Nếu bạn mở rộng Avới Btrường byte khác FF, đó là thêm các hạn chế . Thay vì "giá trị Flà byte", bạn có "giá trị Flà byte && FFcũng là byte". Vì bạn đang giữ các quy tắc cũ, mọi thứ hoạt động cho kiểu cơ sở vẫn hoạt động cho kiểu con của bạn. Nhưng vì bạn đang thêm quy tắc, bạn sẽ tránh xa "có thể là bất cứ điều gì".

Hoặc để suy nghĩ về nó theo cách khác, giả định rằng Ađã có một loạt các phân nhóm: B, C, và D. Một biến kiểu Acó thể là bất kỳ kiểu con nào trong số các kiểu con này, nhưng một biến kiểu Blà cụ thể hơn. Nó (trong hầu hết các hệ thống loại) không thể là a Choặc a D.

Đây có phải là một mô hình chống?

Tăng cường? Có các đối tượng chuyên dụng là hữu ích và không gây hại rõ ràng cho mã. Đạt được kết quả đó bằng cách sử dụng phân nhóm có thể là một câu hỏi nhỏ, nhưng phụ thuộc vào công cụ bạn có theo ý của bạn. Ngay cả với các công cụ tốt hơn, việc thực hiện này rất đơn giản, dễ hiểu và mạnh mẽ.

Tôi sẽ không coi đây là một mô hình chống vì nó không rõ ràng sai và xấu trong mọi tình huống.


5
"Nhiều quy tắc hơn có nghĩa là ít giá trị có thể hơn, có nghĩa là cụ thể hơn." Tôi thực sự không làm theo điều này. Loại byte and bytechắc chắn có nhiều giá trị hơn chỉ loại byte; Nhiều gấp 256 lần. Điều đó sang một bên, tôi không nghĩ rằng bạn có thể kết hợp các quy tắc với số lượng phần tử (hoặc cardinality nếu bạn muốn chính xác). Nó bị phá vỡ khi bạn xem xét các bộ vô hạn. Có nhiều số chẵn như có số nguyên, nhưng biết rằng một số chẵn vẫn là một hạn chế / cung cấp cho tôi nhiều thông tin hơn là chỉ biết đó là số nguyên! Tương tự có thể được nói cho các số tự nhiên.
Doval

6
@Telastyn Chúng tôi đang lạc đề, nhưng có thể chứng minh rằng tính chính xác (lỏng lẻo, "kích thước") của tập hợp các số nguyên chẵn giống như tập hợp của tất cả các số nguyên hoặc thậm chí là tất cả các số hữu tỷ. Đó là thứ thực sự tuyệt vời. Xem math.grinnell.edu/~miletijo/museum/infinite.html
HardlyKnowEm

2
Đặt lý thuyết là khá không trực quan. Bạn có thể đặt các số nguyên và evens tương ứng một-một (ví dụ: sử dụng hàm n -> 2n). Điều này không phải lúc nào cũng có thể; bạn không thể ánh xạ các số nguyên thành các số thực, vì vậy tập hợp các số thực là "lớn hơn" các số nguyên. Nhưng bạn có thể ánh xạ khoảng (thực) [0, 1] thành tập hợp tất cả các số thực, do đó làm cho chúng có cùng "kích thước". Các tập hợp con được định nghĩa là "mọi phần tử Acũng là một phần tử B" chính xác bởi vì một khi các tập hợp của bạn là vô hạn, có thể là trường hợp chúng giống nhau về số lượng nhưng có các phần tử khác nhau.
Doval

1
Liên quan nhiều hơn đến chủ đề hiện tại, ví dụ bạn đưa ra là một hạn chế, về mặt lập trình hướng đối tượng, thực sự mở rộng - chức năng - về mặt một lĩnh vực bổ sung. Tôi đang nói về các lớp con không mở rộng chức năng - như vấn đề hình elip hình tròn.
HardlyKnowEm

1
@Doval: Có nhiều hơn một nghĩa có thể là "ít hơn" - tức là có nhiều hơn một quan hệ đặt hàng trên các bộ. Mối quan hệ "là tập con của" đưa ra thứ tự (một phần) được xác định rõ trên các tập hợp. Ngoài ra, "nhiều quy tắc" có thể được hiểu là "tất cả các yếu tố của tập hợp thỏa mãn nhiều hơn (tức là một siêu bộ) các mệnh đề (từ một tập hợp nhất định)". Với các định nghĩa này, "nhiều quy tắc" thực sự có nghĩa là "ít giá trị hơn". Đây là, nói một cách đại khái, cách tiếp cận được thực hiện bởi một số mô hình ngữ nghĩa của các chương trình máy tính, ví dụ như miền Scott.
psmears

12

Không, đó không phải là một mô hình chống. Tôi có thể nghĩ về một số trường hợp sử dụng thực tế cho việc này:

  1. Nếu bạn muốn sử dụng kiểm tra thời gian biên dịch để đảm bảo các bộ sưu tập các đối tượng chỉ tuân theo một lớp con cụ thể. Ví dụ: nếu bạn có MySQLDaoSqliteDaotrong hệ thống của mình, nhưng vì một số lý do bạn muốn đảm bảo rằng một bộ sưu tập chỉ chứa dữ liệu từ một nguồn, nếu bạn sử dụng các lớp con như bạn mô tả, bạn có thể yêu cầu trình biên dịch xác minh tính chính xác cụ thể này. Mặt khác, nếu bạn lưu trữ nguồn dữ liệu dưới dạng một trường, điều này sẽ trở thành kiểm tra thời gian chạy.
  2. Ứng dụng hiện tại của tôi có một mối quan hệ 1-1 giữa AlgorithmConfiguration+ ProductClassAlgorithmInstance. Nói cách khác, nếu bạn cấu hình FooAlgorithmcho ProductClasstrong file thuộc tính, bạn có thể chỉ nhận được một FooAlgorithmcho rằng lớp sản phẩm. Cách duy nhất để có được hai FooAlgorithms cho một cái đã cho ProductClasslà tạo một lớp con FooBarAlgorithm. Điều này không sao, vì nó làm cho tệp thuộc tính dễ sử dụng (không cần cú pháp cho nhiều cấu hình khác nhau trong một phần trong tệp thuộc tính) và rất hiếm khi có nhiều hơn một trường hợp cho một lớp sản phẩm nhất định.
  3. Câu trả lời của @ Telastyn cho một ví dụ tốt khác.

Điểm mấu chốt: Thực sự không có bất kỳ tác hại nào khi làm điều này. Một mẫu chống được định nghĩa là:

Một mô hình chống (hoặc antipotype) là một phản ứng phổ biến cho một vấn đề tái diễn thường không hiệu quả và rủi ro rất phản tác dụng.

Không có nguy cơ bị phản tác dụng cao ở đây, vì vậy nó không phải là một mô hình chống.


2
Vâng, tôi nghĩ rằng nếu tôi phải diễn đạt câu hỏi một lần nữa, tôi sẽ nói "mùi mã." Nó không đủ tệ để đảm bảo cảnh báo cho thế hệ các nhà phát triển trẻ tiếp theo tránh nó, nhưng đó là điều mà nếu bạn nhìn thấy nó, nó có thể chỉ ra rằng có vấn đề trong thiết kế bao trùm, nhưng cuối cùng cũng có thể đúng.
HardlyKnowEm 27/2/2015

Điểm 1 là đúng mục tiêu. Tôi thực sự không hiểu điểm 2, nhưng tôi chắc rằng nó cũng tốt như vậy ... :)
Phil

9

Nếu một cái gì đó là một mẫu hoặc một antipotype phụ thuộc đáng kể vào ngôn ngữ và môi trường mà bạn đang viết. Ví dụ, forcác vòng lặp là một mẫu trong lắp ráp, C, và các ngôn ngữ tương tự và một mẫu chống lại không thể thiếu.

Hãy để tôi kể cho bạn một câu chuyện về một số mã mà tôi đã viết từ lâu ...

Đó là ngôn ngữ gọi là LPC và thực hiện một khuôn khổ để sử dụng phép thuật trong trò chơi. Bạn có một siêu lớp phép thuật, có một lớp phép thuật chiến đấu xử lý một số kiểm tra, và sau đó là một lớp con cho các phép sát thương trực tiếp mục tiêu duy nhất, sau đó được phân lớp thành các phép thuật riêng lẻ - tên lửa ma thuật, tia sét và những thứ tương tự.

Bản chất của cách LPC hoạt động là bạn có một đối tượng chính là Singleton. trước khi bạn đi 'eww Singleton' - đó là và nó hoàn toàn không phải là "eww". Người ta đã không tạo ra các bản sao của các phép thuật, nhưng lại gọi các phương thức tĩnh (hiệu quả) trong các phép thuật. Mã cho tên lửa ma thuật trông giống như:

inherit "spells/direct";

void init() {
  ::init();
  damage = 10;
  cost = 3;
  delay = 1.0;
  caster_message = "You cast a magic missile at ${target}";
  target_message = "You are hit with a magic missile cast by ${caster}";
}

Và bạn thấy điều đó? Đó là một lớp xây dựng duy nhất. Nó đặt một số giá trị trong lớp trừu tượng cha mẹ của nó (không, nó không nên sử dụng setters - nó là bất biến) và đó là nó. Nó hoạt động đúng . Nó rất dễ viết mã, dễ mở rộng và dễ hiểu.

Kiểu mẫu này chuyển sang các tình huống khác trong đó bạn có một lớp con mở rộng một lớp trừu tượng và đặt một vài giá trị của riêng nó, nhưng tất cả các chức năng đều nằm trong lớp trừu tượng mà nó kế thừa. Hãy xem qua StringBuilder trong Java để biết ví dụ về điều này - không hoàn toàn chỉ là hàm tạo, nhưng tôi bị ép phải tìm nhiều logic trong chính các phương thức (mọi thứ đều nằm trong AbstractStringBuilder).

Đây không phải là một điều xấu, và nó chắc chắn không phải là một mô hình chống (và trong một số ngôn ngữ, nó có thể là một mô hình chính nó).


2

Việc sử dụng phổ biến nhất của các lớp con như vậy mà tôi có thể nghĩ đến là phân cấp ngoại lệ, mặc dù đó là trường hợp suy biến trong đó chúng ta thường không định nghĩa gì cả trong lớp, miễn là ngôn ngữ cho phép chúng ta kế thừa các hàm tạo. Chúng tôi sử dụng tính kế thừa để thể hiện đó ReadErrorlà trường hợp đặc biệt IOError, v.v., nhưng ReadErrorkhông cần ghi đè bất kỳ phương thức nào IOError.

Chúng tôi làm điều này bởi vì các ngoại lệ được bắt bằng cách kiểm tra loại của chúng. Vì vậy, chúng ta cần mã hóa chuyên môn theo kiểu để ai đó chỉ có thể bắt ReadErrormà không bắt được tất cả IOError, nếu họ muốn.

Thông thường, kiểm tra các loại sự việc là hình thức tồi tệ khủng khiếp và chúng tôi cố gắng tránh nó. Nếu chúng ta thành công trong việc tránh nó, thì không cần thiết phải thể hiện các chuyên môn trong hệ thống loại.

Mặc dù vậy, nó vẫn có thể hữu ích, nếu tên của loại xuất hiện ở dạng chuỗi đã ghi của đối tượng: đây là ngôn ngữ và có thể là khung cụ thể. Về nguyên tắc, bạn có thể thay thế tên của loại trong bất kỳ hệ thống nào như vậy bằng một cuộc gọi đến một phương thức của lớp, trong trường hợp đó chúng ta sẽ ghi đè nhiều hơn chỉ là hàm tạo SystemDataHelperBuilder, chúng ta cũng sẽ ghi đè loggingname(). Vì vậy, nếu có bất kỳ đăng nhập như vậy trong hệ thống của bạn, bạn có thể tưởng tượng rằng mặc dù lớp của bạn chỉ ghi đè lên hàm tạo, "về mặt đạo đức", nó cũng ghi đè tên lớp, và vì vậy với mục đích thực tế, nó không phải là lớp con chỉ dành cho nhà xây dựng. Nó có hành vi khác nhau mong muốn, nó '

Vì vậy, tôi sẽ nói rằng mã của anh ta có thể là một ý tưởng tốt nếu có một số mã ở nơi nào đó kiểm tra các trường hợp SystemDataHelperBuilder, bằng cách rõ ràng với một nhánh (như ngoại lệ bắt các nhánh dựa trên loại) hoặc có thể vì có một số mã chung sẽ kết thúc sử dụng tên SystemDataHelperBuildertheo cách hữu ích (ngay cả khi chỉ là đăng nhập).

Tuy nhiên, sử dụng các loại cho các trường hợp đặc biệt sẽ dẫn đến một số nhầm lẫn, vì nếu ImmutableRectangle(1,1)ImmutableSquare(1)hành xử khác nhau theo bất kỳ cách nào thì cuối cùng sẽ có người tự hỏi tại sao và có lẽ ước gì nó không xảy ra. Không giống như hệ thống phân cấp ngoại lệ, bạn kiểm tra xem hình chữ nhật có phải là hình vuông hay không bằng cách kiểm tra xem có height == widthphải không instanceof. Trong ví dụ của bạn, có một sự khác biệt giữa a SystemDataHelperBuildervà a DataHelperBuilderđược tạo với cùng một đối số và điều đó có khả năng gây ra vấn đề. Do đó, một hàm để trả về một đối tượng được tạo bằng các đối số "đúng" đối với tôi dường như là điều đúng đắn phải làm: lớp con dẫn đến hai biểu diễn khác nhau của "cùng một thứ" và nó chỉ giúp ích nếu bạn đang làm gì đó với bạn bình thường sẽ cố gắng không làm.

Lưu ý rằng tôi không nói về các mẫu thiết kế và các mẫu chống, bởi vì tôi không tin rằng có thể cung cấp phân loại tất cả các ý tưởng tốt và xấu mà một lập trình viên từng nghĩ đến. Do đó, không phải mọi ý tưởng đều là một mẫu hoặc chống mẫu, nó chỉ trở thành khi ai đó xác định rằng nó xảy ra lặp đi lặp lại trong các bối cảnh khác nhau và đặt tên cho nó ;-) Tôi không biết bất kỳ tên cụ thể nào cho loại phân lớp này.


Tôi có thể thấy việc sử dụng cho ImmutableSquareMatrix:ImmutableMatrix, với điều kiện là có một phương tiện hiệu quả để yêu cầu ImmutableMatrixhiển thị nội dung của nó ImmutableSquareMatrixnếu có thể. Ngay cả khi ImmutableSquareMatrixvề cơ bản là một hàm tạo ImmutableMatrixcủa nó yêu cầu khớp chiều cao và chiều rộng và AsSquareMatrixphương thức của nó sẽ tự trả về (chứ không phải là xây dựng một ImmutableSquareMatrixmảng bao bọc cùng một mảng sao lưu), một thiết kế như vậy sẽ cho phép xác thực tham số thời gian biên dịch cho các phương thức yêu cầu hình vuông ma trận
supercat

@supercat: điểm tốt và trong ví dụ của người hỏi nếu có các hàm phải được thông qua SystemDataHelperBuilder, và không phải bất kỳ DataHelperBuildersẽ làm gì, thì sẽ hợp lý khi xác định loại cho điều đó (và giao diện, giả sử bạn xử lý DI theo cách đó) . Trong ví dụ của bạn, có một số thao tác mà ma trận vuông có thể có là một hình vuông không, chẳng hạn như định thức, nhưng quan điểm của bạn là tốt ngay cả khi hệ thống không cần những thứ bạn vẫn muốn kiểm tra theo loại .
Steve Jessop

... Vì vậy, tôi cho rằng nó không hoàn toàn đúng như những gì tôi đã nói, rằng "bạn kiểm tra xem hình chữ nhật có phải là hình vuông hay không bằng cách kiểm tra xem có height == widthphải không instanceof". Bạn có thể thích một cái gì đó bạn có thể khẳng định tĩnh.
Steve Jessop

Tôi ước có một cơ chế cho phép một cái gì đó giống như cú pháp của hàm tạo để gọi các phương thức tĩnh của nhà máy. Loại biểu thức foo=new ImmutableMatrix(someReadableMatrix)rõ ràng hơn so với someReadableMatrix.AsImmutable()hoặc thậm chí ImmutableMatrix.Create(someReadableMatrix), nhưng các hình thức sau cung cấp các khả năng ngữ nghĩa hữu ích mà trước đây thiếu; nếu hàm tạo có thể nói rằng ma trận là hình vuông, có thể có kiểu phản ánh của nó có thể sạch hơn yêu cầu mã phía dưới cần một ma trận vuông để tạo một thể hiện đối tượng mới.
supercat

@supercat: Một điều nhỏ về Python mà tôi thích là sự vắng mặt của một newnhà điều hành. Một lớp chỉ là một cuộc gọi, mà khi được gọi sẽ xảy ra trả về (theo mặc định) một thể hiện của chính nó. Vì vậy, nếu bạn muốn duy trì tính nhất quán của giao diện, hoàn toàn có thể viết một hàm xuất xưởng có tên bắt đầu bằng chữ in hoa, do đó, nó trông giống như một lệnh gọi của hàm tạo. Không phải tôi làm điều này thường xuyên với các chức năng của nhà máy, nhưng nó ở đó khi bạn cần.
Steve Jessop

2

Định nghĩa đối tượng nghiêm ngặt nhất về mối quan hệ của lớp con được gọi là «is-a». Sử dụng ví dụ truyền thống về hình vuông và hình chữ nhật, hình chữ nhật «is-a» hình vuông.

Bằng cách này, bạn nên sử dụng phân lớp cho bất kỳ tình huống nào mà bạn cảm thấy «is-a» là mối quan hệ có ý nghĩa đối với mã của bạn.

Trong trường hợp cụ thể của bạn về một lớp con không có gì ngoài một hàm tạo, bạn nên phân tích nó từ phối cảnh «is-a». Rõ ràng là SystemDataHelperBuilder «is-a» DataHelperBuilder, vì vậy, lần đầu tiên cho thấy nó là một cách sử dụng hợp lệ.

Câu hỏi mà bạn nên trả lời là liệu có bất kỳ mã nào khác thúc đẩy mối quan hệ «is-a» này không. Có mã nào khác cố gắng phân biệt SystemDataHelperBuilders với dân số chung của DataHelperBuilders không? Nếu vậy, mối quan hệ là khá hợp lý, và bạn nên giữ lớp con.

Tuy nhiên, nếu không có mã nào khác thực hiện điều này, thì cái bạn có là một mối quan hệ có thể là mối quan hệ «is-a» hoặc nó có thể được thực hiện với một nhà máy. Trong trường hợp này, bạn có thể đi bằng bất kỳ cách nào, nhưng tôi muốn giới thiệu một Nhà máy hơn là mối quan hệ «là-a». Khi phân tích mã hướng đối tượng được viết bởi một cá nhân khác, hệ thống phân cấp lớp là một nguồn thông tin chính. Nó cung cấp các mối quan hệ «is-a» mà họ sẽ cần để hiểu mã. Theo đó, việc đưa hành vi này vào một lớp con sẽ thúc đẩy hành vi từ "một cái gì đó mà ai đó sẽ tìm thấy nếu họ đào sâu vào các phương thức của siêu lớp" đến "một cái gì đó mọi người sẽ xem qua trước khi họ bắt đầu đi sâu vào mã." Trích dẫn Guido van Rossum, "mã được đọc thường xuyên hơn so với nó được viết", vì vậy việc giảm khả năng đọc mã của bạn cho các nhà phát triển sắp tới là một cái giá phải trả. Tôi sẽ chọn không trả nó, nếu tôi có thể sử dụng một nhà máy thay thế.


1

Một kiểu con không bao giờ là "sai" miễn là bạn luôn có thể thay thế một thể hiện của siêu kiểu của nó bằng một thể hiện của kiểu con và mọi thứ vẫn hoạt động chính xác. Điều này kiểm tra miễn là lớp con không bao giờ cố gắng làm suy yếu bất kỳ đảm bảo nào mà siêu kiểu tạo ra. Nó có thể đảm bảo mạnh mẽ hơn (cụ thể hơn), do đó, theo nghĩa đó, trực giác của đồng nghiệp của bạn là chính xác.

Do đó, lớp con bạn tạo có lẽ không sai (vì tôi không biết chính xác họ làm gì, tôi không thể nói chắc chắn được.) Bạn cần cảnh giác với vấn đề của lớp cơ sở mong manh, và bạn cũng phải xem xét liệu một điều interfacecó lợi cho bạn hay không, nhưng điều đó đúng với tất cả các cách sử dụng thừa kế.


1

Tôi nghĩ rằng chìa khóa để trả lời câu hỏi này là xem xét kịch bản sử dụng cụ thể này.

Kế thừa được sử dụng để thực thi các tùy chọn cấu hình cơ sở dữ liệu, như chuỗi kết nối.

Đây là một mô hình chống bởi vì lớp đang vi phạm Hiệu trưởng Trách nhiệm duy nhất --- Nó đang tự cấu hình với một nguồn cụ thể của cấu hình đó và không "Làm một việc và làm tốt." Cấu hình của một lớp không nên được nướng vào chính lớp đó. Để thiết lập các chuỗi kết nối cơ sở dữ liệu, hầu hết các lần tôi đã thấy điều này xảy ra ở cấp ứng dụng trong một số loại sự kiện xảy ra khi ứng dụng được khởi động.


1

Tôi sử dụng constructor chỉ các lớp con khá thường xuyên để thể hiện các khái niệm khác nhau.

Ví dụ, hãy xem xét các lớp sau:

public class Outputter
{
    private readonly IOutputMethod outputMethod;
    private readonly IDataMassager dataMassager;

    public Outputter(IOutputMethod outputMethod, IDataMassager dataMassager)
    {
        this.outputMethod = outputMethod;
        this.dataMassager = dataMassager;
    }

    public MassageDataAndOutput(string data)
    {
        this.outputMethod.Output(this.dataMassager.Massage(data));
    }
}

Sau đó chúng ta có thể tạo một lớp con:

public class CapitalizeAndPrintOutputter : Outputter
{
    public CapitalizeAndPrintOutputter()
        : base(new PrintOutputMethod(), new Capitalizer())
    {
    }
}

Sau đó, bạn có thể sử dụng CapitalizeAndPrintOutputter ở bất cứ đâu trong mã của mình và bạn biết chính xác loại đầu ra đó là gì. Hơn nữa, mẫu này thực sự làm cho nó rất dễ sử dụng một thùng chứa DI để tạo lớp này. Nếu bộ chứa DI biết về PrintOutputMethod và Capitalizer, nó thậm chí có thể tự động tạo CapitalizeAndPrintOutputter cho bạn.

Sử dụng mô hình này thực sự khá linh hoạt và hữu ích. Có, bạn có thể sử dụng các phương thức của nhà máy để làm điều tương tự, nhưng sau đó (ít nhất là trong C #), bạn mất một phần sức mạnh của hệ thống loại và chắc chắn việc sử dụng các thùng chứa DI trở nên cồng kềnh hơn.


1

Trước tiên tôi xin lưu ý rằng khi mã được viết, lớp con thực sự không cụ thể hơn là cha mẹ, vì hai trường được khởi tạo đều có thể giải quyết được bằng mã máy khách.

Một thể hiện của lớp con chỉ có thể được phân biệt bằng cách sử dụng downcast.

Bây giờ, nếu bạn có một thiết kế trong đó các thuộc tính DatabaseEngineConnectionStringđược thực hiện lại bởi lớp con, được truy cập Configurationđể làm như vậy, thì bạn có một ràng buộc có ý nghĩa hơn khi có lớp con.

Phải nói rằng, "chống mẫu" quá mạnh so với sở thích của tôi; không có gì thực sự sai ở đây


0

Trong ví dụ bạn đã đưa ra, đối tác của bạn là đúng. Hai người xây dựng trợ giúp dữ liệu là chiến lược. Một cái cụ thể hơn cái kia, nhưng cả hai đều có thể hoán đổi cho nhau một khi được khởi tạo.

Về cơ bản nếu một khách hàng của datahelperbuilder sẽ nhận được systemdatahelperbuilder, sẽ không có gì phá vỡ. Một tùy chọn khác sẽ có để systemdatahelperbuilder là một thể hiện tĩnh của lớp datahelperbuilder.

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.