Tôi nên thích thành phần hoặc kế thừa trong kịch bản này?


11

Hãy xem xét một giao diện:

interface IWaveGenerator
{
    SoundWave GenerateWave(double frequency, double lengthInSeconds);
}

Giao diện này được thực hiện bởi một số lớp tạo ra các sóng có hình dạng khác nhau (ví dụ, SineWaveGeneratorSquareWaveGenerator).

Tôi muốn thực hiện một lớp tạo ra SoundWavedựa trên dữ liệu âm nhạc, không phải dữ liệu âm thanh thô. Nó sẽ nhận được tên của một nốt nhạc và độ dài theo nhịp (không phải giây) và sử dụng IWaveGeneratorchức năng bên trong để tạo ra một nốt nhạc SoundWavephù hợp.

Câu hỏi là, nó có nên NoteGeneratorchứa IWaveGeneratorhoặc nên kế thừa từ một IWaveGeneratortriển khai?

Tôi nghiêng về sáng tác vì hai lý do:

1- Nó cho phép tôi tiêm bất kỳ IWaveGeneratorvào NoteGeneratorđộng. Ngoài ra, tôi chỉ cần một NoteGeneratorlớp, thay vì SineNoteGenerator, SquareNoteGeneratorvv

2- Không cần phải NoteGeneratorlộ giao diện cấp thấp hơn được xác định bởi IWaveGenerator.

Tuy nhiên tôi đang đăng câu hỏi này để nghe ý kiến ​​khác về vấn đề này, có thể những điểm tôi chưa nghĩ đến.

BTW: Tôi muốn nói NoteGenerator về mặt khái niệm IWaveGeneratorbởi vì nó tạo ra SoundWaves.

Câu trả lời:


14

Nó cho phép tôi tự động tiêm bất kỳ IWaveGenerator nào vào NoteGenerator. Ngoài ra, tôi chỉ cần một lớp NoteGenerator, thay vì SineNoteGenerator , SquareNoteGenerator , v.v.

Đó là một dấu hiệu rõ ràng sẽ tốt hơn nếu sử dụng bố cục ở đây và không được thừa hưởng từ SineGeneratorhoặc SquareGeneratorhoặc (tệ hơn) cả hai. Tuy nhiên, sẽ hợp lý khi kế thừa NoteGenerator trực tiếp từ IWaveGeneratornếu bạn thay đổi cái sau một chút.

Các vấn đề thực sự ở đây là, nó có lẽ là ý nghĩa có NoteGeneratorvới một phương pháp như

SoundWave GenerateWave(string noteName, double noOfBeats, IWaveGenerator waveGenerator);

nhưng không phải với một phương pháp

SoundWave GenerateWave(double frequency, double lengthInSeconds);

bởi vì giao diện này quá cụ thể Bạn muốn IWaveGenerators là các đối tượng tạo SoundWaves, nhưng hiện tại giao diện của bạn thể hiện IWaveGenerators là các đối tượng tạo SoundWaves từ tần số và độ dài riêng . Vì vậy, thiết kế tốt hơn một giao diện theo cách này

interface IWaveGenerator
{
    SoundWave GenerateWave();
}

và truyền các tham số như frequencyhoặc lengthInSeconds, hoặc một bộ tham số hoàn toàn khác thông qua các hàm tạo của a SineWaveGenerator, a SquareGeneratorhoặc bất kỳ trình tạo nào khác mà bạn có trong đầu. Điều này sẽ cho phép bạn tạo các loại IWaveGenerators khác với các thông số xây dựng hoàn toàn khác nhau. Có thể bạn muốn thêm một bộ tạo sóng hình chữ nhật cần một tham số tần số và hai chiều dài hoặc tương tự, có thể bạn muốn thêm một bộ tạo sóng tam giác tiếp theo, với ít nhất ba tham số. Hoặc, một NoteGenerator, với các thông số nhà xây dựng noteName, noOfBeatswaveGenerator.

Vì vậy, giải pháp chung ở đây là tách các tham số đầu vào khỏi hàm đầu ra và chỉ tạo phần chức năng đầu ra của giao diện.


Thú vị, chưa nghĩ đến điều này. Nhưng tôi tự hỏi: điều này (thiết lập 'tham số thành hàm đa hình' trong hàm tạo) có thường hoạt động trong thực tế không? Bởi vì sau đó, mã thực sự sẽ phải biết nó đang xử lý kiểu gì, do đó làm hỏng tính đa hình. Bạn có thể cho một ví dụ nơi này sẽ làm việc?
Manila Cohn

2
@AvivCohn: "mã thực sự sẽ phải biết nó đang xử lý kiểu gì" - không, đó là một quan niệm sai lầm. Chỉ một phần của mã xây dựng loại máy phát cụ thể (tôi là nhà máy) và phải biết luôn luôn loại nào đang xử lý.
Doc Brown

... và nếu bạn cần làm cho quá trình xây dựng các đối tượng của mình trở nên đa hình, bạn có thể sử dụng mô hình "nhà máy trừu tượng" ( en.wikipedia.org/wiki/ Ab khu_factory_potype )
Doc Brown

Đây là giải pháp tôi sẽ chọn. Các lớp học nhỏ, bất biến là cách đúng đắn để đi đến đây.
Stephen

9

Việc NoteGenerator có "về mặt khái niệm" hay không, một IWaveGenerator không thành vấn đề.

Bạn chỉ nên kế thừa từ một giao diện nếu bạn có kế hoạch triển khai giao diện chính xác đó theo Nguyên tắc thay thế Liskov, tức là với ngữ nghĩa chính xác cũng như cú pháp đúng.

Nghe có vẻ như NoteGenerator của bạn có thể có cùng một giao diện, nhưng ngữ nghĩa của nó (trong trường hợp này, ý nghĩa của các tham số cần có) sẽ rất khác nhau, vì vậy sử dụng tính kế thừa trong trường hợp này sẽ rất dễ gây hiểu lầm và dễ bị lỗi. Bạn có quyền thích sáng tác ở đây.


Trên thực tế tôi không có nghĩa là NoteGeneratorsẽ thực hiện GenerateWavenhưng giải thích các tham số khác nhau, vâng tôi đồng ý rằng đó sẽ là một ý tưởng tồi tệ. Ý tôi là NoteGenerator là một loại chuyên môn hóa của bộ tạo sóng: nó có thể lấy dữ liệu đầu vào 'mức cao hơn thay vì chỉ là dữ liệu âm thanh thô (ví dụ như tên ghi chú thay vì tần số). Tức sineWaveGenerator.generate(440) == noteGenerator.generate("a4"). Vì vậy, có câu hỏi, thành phần hoặc thừa kế.
Manila Cohn

Nếu bạn có thể đưa ra một giao diện duy nhất phù hợp với cả các lớp tạo sóng mức cao và mức thấp, thì sự kế thừa có thể được chấp nhận. Nhưng điều đó có vẻ rất khó khăn và không có lợi ích thực sự. Thành phần chắc chắn có vẻ như sự lựa chọn tự nhiên hơn.
Ixrec

@Ixrec: thực ra, không khó để có một giao diện duy nhất cho tất cả các loại máy phát điện, OP có lẽ nên làm cả hai, sử dụng thành phần để tiêm một trình tạo mức thấp kế thừa từ một giao diện đơn giản (nhưng không kế thừa NoteGenerator từ một thực hiện trình tạo mức thấp) Xem câu trả lời của tôi.
Doc Brown

5

2- Không cần NoteGenerator để lộ giao diện cấp thấp hơn được xác định bởi IWaveGenerator.

Âm thanh như NoteGeneratorkhông phải là một WaveGenerator, do đó không nên thực hiện giao diện.

Thành phần là sự lựa chọn chính xác.


Tôi có thể nói NoteGenerator về mặt khái niệm IWaveGeneratorbởi vì nó tạo ra SoundWaves.
Manila Cohn

1
Chà, nếu nó không cần phải phơi bày GenerateWave, thì đó không phải là một IWaveGenerator. Nhưng có vẻ như nó sử dụng IWaveGenerator (có thể nhiều hơn?), Do đó thành phần.
Eric King

@EricKing: đây là một câu trả lời đúng miễn là người ta phải bám vào GenerateWavechức năng như nó được viết trong câu hỏi. Nhưng từ nhận xét trên tôi đoán đó không phải là điều OP thực sự có trong đầu.
Doc Brown

3

Bạn có một trường hợp vững chắc cho thành phần. Bạn có thể có một trường hợp để cũng thêm thừa kế. Cách để nói là bằng cách nhìn vào mã gọi. Nếu bạn muốn có thể sử dụng một NoteGeneratormã gọi hiện có mong đợi IWaveGenerator, thì bạn cần phải thực hiện giao diện. Bạn đang tìm kiếm một nhu cầu thay thế. Cho dù nó về mặt khái niệm "is-a" máy phát sóng nằm bên cạnh điểm.


Trong trường hợp đó, tức là chọn thành phần, nhưng vẫn cần sự kế thừa đó để làm cho tính thay thế xảy ra, "thừa kế" sẽ được đặt tên IHasWaveGenerator, ví dụ , và phương thức có liên quan trên giao diện GetWaveGeneratorđó sẽ trả về một thể hiện của IWaveGenerator. Tất nhiên việc đặt tên có thể được thay đổi. (Tôi chỉ đang cố gắng để biết thêm chi tiết - hãy cho tôi biết nếu chi tiết của tôi sai.)
rwong

2

Nó là tốt NoteGeneratorđể thực hiện giao diện, và cũng NoteGeneratorcó thể thực hiện nội bộ tham chiếu (theo thành phần) khác IWaveGenerator.

Nói chung, thành phần dẫn đến mã có thể duy trì nhiều hơn (nghĩa là có thể đọc được), bởi vì bạn không có sự phức tạp của ghi đè để lý do hơn. Quan sát của bạn về ma trận của các lớp bạn có khi sử dụng tính kế thừa cũng là điểm chính và có thể được coi là mùi mã chỉ vào thành phần.

Kế thừa được sử dụng tốt hơn khi bạn có một triển khai mà bạn muốn chuyên môn hóa hoặc tùy chỉnh, điều này dường như không phải là trường hợp ở đây: bạn chỉ cần sử dụng giao diện.


1
Nó không ổn NoteGeneratorđể thực hiện IWaveGeneratorbởi vì các ghi chú yêu cầu nhịp đập. không phải giây-.
Tulains Córdova

Có, chắc chắn nếu không có triển khai hợp lý của giao diện, thì lớp không nên thực hiện nó. Tuy nhiên, OP đã tuyên bố rằng "tôi sẽ nói NoteGeneratorlà về mặt khái niệm là IWaveGeneratorvì nó tạo ra SoundWaves", và, anh ta đang xem xét tính kế thừa, vì vậy tôi đã nghĩ đến khả năng có thể có một số triển khai giao diện, mặc dù có một giao diện khác giao diện tốt hơn hoặc chữ ký cho lớp.
Erik Eidt
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.