Các khả năng của một đối tượng có nên được xác định riêng bởi các giao diện mà nó thực hiện không?


36

isToán tử C # và toán tử Java instanceofcho phép bạn phân nhánh trên giao diện (hoặc, hơn nữa, lớp cơ sở của nó), một cá thể đối tượng đã triển khai.

Có phù hợp để sử dụng tính năng này để phân nhánh cấp cao dựa trên các khả năng mà giao diện cung cấp không?

Hay một lớp cơ sở nên cung cấp các biến boolean để cung cấp một giao diện để mô tả các khả năng mà một đối tượng có?

Thí dụ:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

so với

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Có bất kỳ cạm bẫy của họ để sử dụng thực hiện giao diện để xác định khả năng?


Ví dụ này chỉ là một ví dụ. Tôi đang tự hỏi về một ứng dụng tổng quát hơn.


10
Nhìn vào hai ví dụ mã của bạn. Cái nào dễ đọc hơn?
Robert Harvey

39
Chỉ là ý kiến ​​của tôi, nhưng tôi sẽ đi với người đầu tiên chỉ vì nó đảm bảo bạn có thể chuyển sang loại phù hợp một cách an toàn. Điều thứ hai giả định rằng điều đó CanResetPasswordsẽ chỉ đúng khi có gì đó thực hiện IResetsPassword. Bây giờ bạn đang tạo một thuộc tính xác định một cái gì đó mà hệ thống loại sẽ kiểm soát (và cuối cùng sẽ là khi bạn tạo khuôn mẫu).
Becuzz

10
@nocomprende: xkcd.com/927
Robert Harvey

14
Tiêu đề câu hỏi của bạn và cơ thể câu hỏi hỏi những điều khác nhau. Để trả lời tiêu đề: Lý tưởng nhất là có. Để giải quyết phần thân: Nếu bạn thấy mình kiểm tra loại và truyền thủ công, có lẽ bạn không tận dụng chính xác hệ thống loại của mình. Bạn có thể cho chúng tôi biết cụ thể hơn làm thế nào bạn thấy mình trong tình huống này?
MetaFight

9
Câu hỏi này có vẻ đơn giản, nhưng phát triển khá rộng khi bạn bắt đầu nghĩ về nó. Các câu hỏi xuất hiện trong đầu tôi: Bạn có mong muốn thêm các hành vi mới vào các tài khoản hiện có hoặc tạo các loại tài khoản với các hành vi khác nhau không? Có phải tất cả các loại tài khoản đều có hành vi tương tự nhau hoặc có sự khác biệt lớn giữa chúng không? Mã ví dụ có biết về tất cả các loại tài khoản hay chỉ giao diện IAccount cơ bản không?
Euphoric

Câu trả lời:


7

Với lời cảnh báo rằng tốt nhất là tránh downcasting nếu có thể, thì hình thức đầu tiên là thích hợp hơn vì hai lý do:

  1. bạn đang để hệ thống loại làm việc cho bạn. Trong ví dụ đầu tiên, nếu isđám cháy thì downcast chắc chắn sẽ hoạt động. Ở dạng thứ hai, bạn cần đảm bảo rằng chỉ các đối tượng triển khai IResetsPasswordtrả về true cho thuộc tính đó
  2. lớp cơ sở mong manh. Bạn cần thêm một thuộc tính vào lớp / giao diện cơ sở cho mọi giao diện bạn muốn thêm. Điều này là cồng kềnh và dễ bị lỗi.

Điều đó không có nghĩa là bạn không thể cải thiện những vấn đề này. Ví dụ, bạn có thể có lớp cơ sở có một bộ giao diện được xuất bản mà bạn có thể kiểm tra bao gồm. Nhưng bạn thực sự chỉ thực hiện thủ công một phần của hệ thống loại hiện có dự phòng.

BTW sở thích tự nhiên của tôi là thành phần trên thừa kế nhưng chủ yếu là liên quan đến nhà nước. Kế thừa giao diện không phải là xấu, thường. Ngoài ra, nếu bạn thấy mình đang thực hiện hệ thống phân cấp kiểu người nghèo, tốt hơn hết bạn nên sử dụng hệ thống phân cấp đã có sẵn vì trình biên dịch sẽ giúp bạn.


Tôi cũng rất thích gõ thành phần. +1 để làm cho tôi thấy rằng ví dụ cụ thể này không tận dụng nó đúng cách. Mặc dù, tôi sẽ nói rằng việc gõ tính toán (như kế thừa truyền thống) bị hạn chế hơn khi các khả năng khác nhau rất nhiều (trái ngược với cách chúng được thực hiện). Do đó, cách tiếp cận giao thoa, giải quyết một vấn đề khác với cách gõ thành phần hoặc kế thừa tiêu chuẩn.
TheCatWhisperer

bạn có thể đơn giản hóa việc kiểm tra như vậy: (account as IResettable)?.ResetPassword();hoặcvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Berin Loritsch

89

Các khả năng của một đối tượng có nên được xác định riêng bởi các giao diện mà nó thực hiện không?

Một khả năng đối tượng không nên được xác định ở tất cả.

Khách hàng sử dụng một đối tượng không nên yêu cầu biết bất cứ điều gì về cách thức hoạt động của nó. Khách hàng chỉ nên biết những điều nó có thể bảo đối tượng làm. Những gì đối tượng làm, một khi nó được nói, không phải là vấn đề của khách hàng.

Vì vậy hơn là

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

hoặc là

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

xem xét

account.ResetPassword();

hoặc là

account.ResetPassword(authority);

bởi vì accountđã biết nếu điều này sẽ làm việc Tại sao lại hỏi nó? Chỉ cần nói với nó những gì bạn muốn và để nó làm bất cứ điều gì nó sẽ làm.

Hãy tưởng tượng điều này được thực hiện theo cách mà khách hàng không quan tâm nếu nó hoạt động hay không bởi vì đó là một vấn đề gì đó. Công việc của khách hàng chỉ là cố gắng. Bây giờ nó đã được thực hiện và nó có những thứ khác để giải quyết. Phong cách này có nhiều tên nhưng cái tên tôi thích nhất là nói, đừng hỏi .

Thật hấp dẫn khi viết cho khách hàng nghĩ rằng bạn phải theo dõi mọi thứ và do đó kéo về phía bạn mọi thứ bạn nghĩ bạn cần biết. Khi bạn làm điều đó bạn biến các đối tượng từ trong ra ngoài. Giá trị sự thiếu hiểu biết của bạn. Đẩy các chi tiết đi và để các đối tượng đối phó với chúng. Bạn càng ít biết càng tốt.


54
Đa hình chỉ hoạt động khi tôi không biết hoặc quan tâm chính xác những gì tôi đang nói chuyện. Vì vậy, cách tôi đối phó với tình huống đó là cẩn thận tránh nó.
candied_orange

7
Nếu khách hàng thực sự cần phải biết nếu nỗ lực thành công, phương thức có thể trả về một boolean. if (!account.AttemptPasswordReset()) /* attempt failed */;Bằng cách này, khách hàng biết kết quả nhưng tránh được một số vấn đề với logic quyết định trong câu hỏi. Nếu nỗ lực không đồng bộ, bạn sẽ vượt qua cuộc gọi lại.
Radiodef

5
@DavidZ Cả hai chúng tôi đều nói tiếng Anh. Vì vậy, tôi có thể nói với bạn để nâng cao nhận xét này hai lần. Điều đó có nghĩa là bạn có khả năng làm điều đó? Tôi có cần biết nếu bạn có thể trước khi tôi có thể bảo bạn làm điều đó không?
candied_orange

5
@QPaysTaxes cho tất cả những gì bạn biết một trình xử lý lỗi đã được truyền vào accountkhi nó được xây dựng. Cửa sổ bật lên, ghi nhật ký, bản in và cảnh báo âm thanh có thể xảy ra tại thời điểm này. Công việc của khách hàng là nói "làm ngay". Nó không phải làm nhiều hơn thế. Bạn không phải làm mọi thứ ở một nơi.
candied_orange

6
@QPaysTaxes Nó có thể được xử lý ở nơi khác và theo một số cách khác nhau, nó không cần thiết cho cuộc trò chuyện này và sẽ chỉ làm vẩn đục nước.
Tom.Bowen89

17

Lý lịch

Kế thừa là một công cụ mạnh mẽ phục vụ mục đích trong lập trình hướng đối tượng. Tuy nhiên, nó không giải quyết mọi vấn đề một cách thanh lịch: đôi khi, các giải pháp khác tốt hơn.

Nếu bạn nghĩ lại về các lớp khoa học máy tính ban đầu của mình (giả sử bạn có bằng CS), bạn có thể nhớ một giáo sư đưa cho bạn một đoạn văn nói rõ khách hàng muốn phần mềm làm gì. Công việc của bạn là đọc đoạn văn, xác định các tác nhân và hành động, và đưa ra một phác thảo sơ bộ về các lớp và phương thức là gì. Sẽ có một số người dẫn đường trong đó có vẻ như họ quan trọng, nhưng không phải. Có một khả năng rất thực tế của các yêu cầu giải thích sai là tốt.

Đây là một kỹ năng quan trọng mà ngay cả những người có kinh nghiệm nhất trong chúng ta cũng mắc phải: xác định đúng yêu cầu và dịch chúng sang ngôn ngữ máy.

Câu hỏi của bạn

Dựa trên những gì bạn đã viết, tôi nghĩ bạn có thể hiểu sai trường hợp chung của các hành động tùy chọn mà các lớp có thể thực hiện. Vâng, tôi biết mã của bạn chỉ là một ví dụ và bạn quan tâm đến trường hợp chung. Tuy nhiên, có vẻ như bạn muốn biết làm thế nào để xử lý các tình huống mà một số phân nhóm của một đối tượng có thể thực hiện một hành động, nhưng phân nhóm khác không thể.

Chỉ vì một đối tượng như accountcó một loại tài khoản không có nghĩa là chuyển thành một loại trong ngôn ngữ OO. "Loại" trong ngôn ngữ của con người không phải lúc nào cũng có nghĩa là "lớp". Trong ngữ cảnh của một tài khoản, "loại" có thể tương quan chặt chẽ hơn với "bộ quyền". Bạn muốn sử dụng tài khoản người dùng để thực hiện một hành động, nhưng hành động đó có thể hoặc không thể được thực hiện bởi tài khoản đó. Thay vì sử dụng tính kế thừa, tôi sẽ sử dụng mã thông báo đại biểu hoặc bảo mật.

Giải pháp của tôi

Hãy xem xét một lớp tài khoản có một số hành động tùy chọn mà nó có thể thực hiện. Thay vì xác định "có thể thực hiện hành động X" thông qua kế thừa, tại sao tài khoản không trả về một đối tượng ủy nhiệm (trình khôi phục mật khẩu, người gửi biểu mẫu, v.v.) hoặc mã thông báo truy cập?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

Lợi ích cho tùy chọn cuối cùng là nếu tôi muốn sử dụng thông tin đăng nhập tài khoản của mình để đặt lại mật khẩu của người khác thì sao?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Không chỉ hệ thống linh hoạt hơn, tôi không chỉ loại bỏ các loại phôi, mà thiết kế còn biểu cảm hơn . Tôi có thể đọc nó và dễ dàng hiểu những gì nó đang làm và làm thế nào nó có thể được sử dụng.


TL; DR: điều này đọc giống như một vấn đề XY . Nói chung, khi phải đối mặt với hai lựa chọn không tối ưu, đáng để lùi lại một bước và suy nghĩ "tôi thực sự đang cố gắng đạt được điều gì ở đây? Tôi thực sự nên suy nghĩ về cách làm cho kiểu chữ bớt xấu xí hơn, hay tôi nên tìm cách để loại bỏ hoàn toàn typecast? "


1
+1 cho thiết kế có mùi, ngay cả khi bạn không sử dụng cụm từ.
Jared Smith

Còn trường hợp mật khẩu đặt lại có các đối số hoàn toàn khác nhau tùy thuộc vào loại thì sao? ResetPassword (pswd) so với ResetPasswordToRandom ()
TheCatWhisperer

1
@TheCatWhisperer Ví dụ đầu tiên là "thay đổi mật khẩu" là một hành động khác. Ví dụ thứ hai là những gì tôi mô tả trong câu trả lời này.

Tại sao không account.getPasswordResetter().resetPassword();.
AnoE

1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. dễ dàng. Bạn tạo một đối tượng miền Tài khoản cho tài khoản khác và sử dụng tài khoản đó. Các lớp tĩnh có vấn đề đối với kiểm thử đơn vị (theo định nghĩa phương pháp đó sẽ cần quyền truy cập vào một số lưu trữ, vì vậy bây giờ bạn không thể dễ dàng kiểm tra đơn vị bất kỳ mã nào chạm vào lớp đó nữa) và tốt nhất nên tránh. Tùy chọn trả lại một số giao diện khác thường là một ý tưởng tốt nhưng không thực sự giải quyết được vấn đề tiềm ẩn (bạn chỉ chuyển vấn đề sang giao diện khác).
Voo

1

Bill Venner nói rằng cách tiếp cận của bạn là hoàn toàn tốt ; chuyển đến phần có tên 'Khi nào nên sử dụng thể hiện'. Bạn đang xem thường một loại / giao diện cụ thể chỉ thực hiện hành vi cụ thể đó để bạn hoàn toàn có quyền chọn lọc về nó.

Tuy nhiên, nếu bạn muốn sử dụng một phương pháp khác, có nhiều cách để cạo một yak.

Có cách tiếp cận đa hình; bạn có thể lập luận rằng tất cả các tài khoản đều có mật khẩu, do đó, tất cả các tài khoản ít nhất có thể cố gắng đặt lại mật khẩu. Điều này có nghĩa là, trong lớp tài khoản cơ sở, chúng ta nên có một resetPassword()phương thức mà tất cả các tài khoản thực hiện nhưng theo cách riêng của chúng. Câu hỏi là phương pháp đó nên hành xử như thế nào khi không có khả năng.

Nó có thể trả về một khoảng trống và âm thầm hoàn thành cho dù nó có đặt lại mật khẩu hay không, có thể chịu trách nhiệm in thông báo bên trong nếu nó không đặt lại mật khẩu. Không phải là ý tưởng tốt nhất.

Nó có thể trả về một boolean cho biết liệu thiết lập lại có thành công hay không. Bật tính năng boolean đó, chúng tôi có thể liên quan đến việc đặt lại mật khẩu không thành công.

Nó có thể trả về một Chuỗi cho biết kết quả của lần thử đặt lại mật khẩu. Chuỗi có thể cung cấp thêm chi tiết về lý do tại sao thiết lập lại thất bại và có thể là đầu ra.

Nó có thể trả về một đối tượng ResetResult truyền tải nhiều chi tiết hơn và kết hợp tất cả các phần tử trả về trước đó.

Nó có thể trả về một khoảng trống và thay vào đó sẽ ném một ngoại lệ nếu bạn cố gắng thiết lập lại tài khoản không có khả năng đó (đừng làm điều này vì sử dụng xử lý ngoại lệ để kiểm soát luồng thông thường là một thực tế tồi tệ vì nhiều lý do).

Có một canResetPassword()phương thức khớp có vẻ không phải là điều tồi tệ nhất trên thế giới vì về mặt khái niệm, đó là một khả năng tĩnh được tích hợp sẵn cho lớp khi nó được viết. Tuy nhiên, đây là một manh mối về lý do tại sao phương pháp tiếp cận là một ý tưởng tồi, vì nó cho thấy khả năng này là động và canResetPassword()có thể thay đổi, điều này cũng tạo ra khả năng bổ sung rằng nó có thể thay đổi giữa việc xin phép và thực hiện cuộc gọi. Như đã đề cập ở nơi khác, nói thay vì xin phép.

Thành phần trên thừa kế có thể là một tùy chọn: bạn có thể có một passwordResettertrường cuối cùng (hoặc một getter tương đương) và (các) lớp tương đương mà bạn có thể kiểm tra null trước khi gọi nó. Mặc dù nó hoạt động hơi giống như xin phép, nhưng tính hữu hạn có thể tránh mọi tính chất năng động được suy luận.

Bạn có thể nghĩ để đưa chức năng vào lớp của chính nó, có thể lấy tài khoản làm tham số và hành động theo nó (ví dụ resetter.reset(account)), mặc dù điều này cũng thường là thông lệ xấu (một cách tương tự phổ biến là một người bán hàng truy cập vào ví của bạn để lấy tiền mặt).

Trong các ngôn ngữ có khả năng, bạn có thể sử dụng mixin hoặc các đặc điểm, tuy nhiên, bạn kết thúc ở vị trí bạn bắt đầu nơi bạn có thể kiểm tra sự tồn tại của các khả năng đó.


Không phải tất cả các tài khoản đều có thể có mật khẩu (theo quan điểm của hệ thống cụ thể này). Ví dụ: tài khoản của tôi là bên thứ 3 ... google, facebook, ect. Không phải nói rằng đây không phải là một câu trả lời tuyệt vời!
TheCatWhisperer

0

Đối với ví dụ cụ thể này về " có thể đặt lại mật khẩu ", tôi khuyên bạn nên sử dụng thành phần trên kế thừa (trong trường hợp này là kế thừa giao diện / hợp đồng). Bởi vì, bằng cách này:

class Foo : IResetsPassword {
    //...
}

Bạn ngay lập tức xác định (tại thời gian biên dịch) rằng lớp của bạn ' có thể đặt lại mật khẩu '. Nhưng, nếu trong kịch bản của bạn, sự hiện diện của khả năng là có điều kiện và phụ thuộc vào những thứ khác, thì bạn không thể chỉ định những thứ đó vào thời gian biên dịch nữa. Sau đó, tôi đề nghị làm điều này:

class Foo {

    PasswordResetter passwordResetter;

}

Bây giờ, tại thời gian chạy, bạn có thể kiểm tra nếu myFoo.passwordResetter != nulltrước khi thực hiện thao tác này. Nếu bạn muốn tách rời nhiều thứ hơn nữa (và bạn dự định thêm nhiều khả năng khác), bạn có thể:

class Foo {
    //... foo stuff
}

class PasswordResetOperation {
    bool Execute(Foo foo) { ... }
}

class SendMailOperation {
    bool Execute(Foo foo) { ... }
}

//...and you follow this pattern for each new capability...

CẬP NHẬT

Sau khi tôi đọc một số câu trả lời và nhận xét khác từ OP, tôi hiểu câu hỏi không phải là về giải pháp sáng tác. Vì vậy, tôi nghĩ rằng câu hỏi là về cách xác định tốt hơn khả năng của các đối tượng nói chung, trong một kịch bản như dưới đây:

class BaseAccount {
    //...
}
class GuestAccount : BaseAccount {
    //...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
    //...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
    //...
}

//Capabilities

interface IMyPasswordReset {
    bool ResetPassword();
}

interface IPasswordReset {
    bool ResetPassword(UserAccount userAcc);
}

interface IEditPosts {
    bool EditPost(long postId, ...);
}

interface ISendMail {
    bool SendMail(string from, string to, ...);
}

Bây giờ, tôi sẽ cố gắng phân tích tất cả các tùy chọn được đề cập:

Ví dụ thứ hai của OP:

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Giả sử mã này đang nhận một số lớp tài khoản cơ sở (ví dụ: BaseAccounttrong ví dụ của tôi); Điều này thật tệ vì nó chèn booleans vào lớp cơ sở, làm ô nhiễm nó với mã không có ý nghĩa gì cả.

Ví dụ đầu tiên của OP:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Để trả lời câu hỏi, điều này phù hợp hơn so với tùy chọn trước đó, nhưng tùy thuộc vào việc thực hiện, nó sẽ phá vỡ nguyên tắc rắn L và có thể các kiểm tra như thế này sẽ được truyền qua mã và khiến việc bảo trì thêm khó khăn hơn.

Anieder của CandiedOrange:

account.ResetPassword(authority);

Nếu ResetPasswordphương thức này được chèn vào BaseAccountlớp, thì nó cũng gây ô nhiễm lớp cơ sở với mã không phù hợp, như trong ví dụ thứ hai của OP

Câu trả lời của Người tuyết:

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Đây là một giải pháp tốt, nhưng nó cho rằng các khả năng là động (và có thể thay đổi theo thời gian). Tuy nhiên, sau khi tôi đọc một số bình luận từ OP, tôi đoán cuộc nói chuyện ở đây liên quan đến tính đa hình và các lớp được xác định tĩnh (mặc dù ví dụ về các tài khoản trực giác chỉ ra kịch bản động). EG: trong AccountManagerví dụ này , kiểm tra cho phép sẽ là truy vấn DB; trong câu hỏi OP, kiểm tra là cố gắng đúc các đối tượng.

Một đề nghị khác từ tôi:

Sử dụng mẫu Phương thức mẫu để phân nhánh cấp cao. Hệ thống phân cấp lớp được đề cập được giữ nguyên như vậy; chúng ta chỉ tạo các trình xử lý thích hợp hơn cho các đối tượng, để tránh các phôi và các thuộc tính / phương thức không phù hợp làm biến đổi lớp cơ sở.

//Template method
class BaseAccountOperation {

    BaseAccount account;

    void Execute() {

        //... some processing

        TryResetPassword();

        //... some processing

        TrySendMail();

        //... some processing
    }

    void TryResetPassword() {
        Print("Not allowed to reset password with this account type!");
    }

    void TrySendMail() {
        Print("Not allowed to reset password with this account type!");
    }
}

class UserAccountOperation : BaseAccountOperation {

    UserAccount userAccount;

    void TryResetPassword() {
        account.ResetPassword(...);
    }

}

class AdminAccountOperation : BaseAccountOperation {

    AdminAccount adminAccount;

    override void TryResetPassword() {
        account.ResetPassword(...);
    }

    void TrySendMail() {
        account.SendMail(...);
    }
}

Bạn có thể liên kết hoạt động với lớp tài khoản phù hợp bằng cách sử dụng từ điển / hashtable hoặc thực hiện các hoạt động trong thời gian chạy bằng phương pháp tiện ích mở rộng, sử dụng dynamictừ khóa hoặc tùy chọn cuối cùng chỉ sử dụng một lần truyền để chuyển đối tượng tài khoản sang hoạt động (trong trường hợp này số lượng phôi chỉ là một, khi bắt đầu hoạt động).


1
Nó có hoạt động không khi luôn luôn thực hiện phương thức "Execute ()" nhưng đôi khi nó không làm gì cả? Nó trả về một bool, vì vậy tôi đoán điều đó có nghĩa là nó đã làm điều đó hay không. Điều đó ném lại cho khách hàng: "Tôi đã cố gắng đặt lại mật khẩu, nhưng điều đó không xảy ra (vì bất kỳ lý do gì), vì vậy bây giờ tôi nên ..."

4
Có các phương thức "canX ()" và "doX ()" là một phản mẫu: nếu một giao diện phơi bày phương thức "doX ()", tốt hơn là có thể thực hiện X ngay cả khi thực hiện X có nghĩa là không có đối tượng (ví dụ: mẫu đối tượng null ). Không bao giờ nên sử dụng giao diện người dùng để tham gia vào khớp nối tạm thời (gọi phương thức A trước phương thức B) vì điều đó quá dễ để hiểu sai và phá vỡ mọi thứ.

1
Có giao diện không có liên quan đến việc sử dụng thành phần trên thừa kế. Họ chỉ đại diện cho một chức năng có sẵn. Làm thế nào chức năng đó xảy ra để chơi độc lập với việc sử dụng giao diện. Những gì bạn đã làm ở đây là sử dụng một triển khai đặc biệt của giao diện mà không có bất kỳ lợi ích nào.
Miyamoto Akira

Thú vị: câu trả lời được bình chọn hàng đầu nói về cơ bản những gì bình luận của tôi ở trên nói. Một số ngày bạn chỉ gặp may mắn.

@nocomprende - vâng, tôi đã cập nhật câu trả lời của mình theo một số bình luận. Cảm ơn các phản hồi :)
Emerson Cardoso

0

Cả hai tùy chọn bạn đã trình bày đều là OO tốt. Nếu bạn đang viết nếu các câu lệnh xoay quanh loại đối tượng, rất có thể bạn đang làm sai OO (có trường hợp ngoại lệ, đây không phải là câu trả lời.) Đây là câu trả lời OO đơn giản cho câu hỏi của bạn (có thể không hợp lệ C #):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

Tôi đã sửa đổi ví dụ này để phù hợp với những gì mã ban đầu đang làm. Dựa trên một số ý kiến, có vẻ như mọi người đang nôn nao về việc liệu đây có phải là mã cụ thể 'đúng' ở đây không. Đó không phải là điểm của ví dụ này. Toàn bộ quá tải đa hình về cơ bản là để thực hiện có điều kiện các triển khai logic khác nhau dựa trên loại đối tượng. Những gì bạn đang làm trong cả hai ví dụ là gây nhiễu bằng tay những gì ngôn ngữ của bạn mang lại cho bạn như một tính năng. Tóm lại, bạn có thể loại bỏ các loại phụ và đặt khả năng đặt lại làm thuộc tính boolean của loại Tài khoản (bỏ qua các tính năng khác của loại phụ.)

Nếu không có cái nhìn rộng hơn về thiết kế, không thể biết liệu đây có phải là một giải pháp tốt cho hệ thống cụ thể của bạn hay không. Thật đơn giản và nếu nó hoạt động cho những gì bạn đang làm, có thể bạn sẽ không bao giờ cần phải suy nghĩ nhiều về nó nữa trừ khi ai đó không kiểm tra CanResetPassword () trước khi gọi ResetPassword (). Bạn cũng có thể trả lại boolean hoặc thất bại trong âm thầm (không được đề xuất). Nó thực sự phụ thuộc vào các chi tiết cụ thể của thiết kế.


Không phải tất cả các tài khoản có thể được thiết lập lại. Ngoài ra, một tài khoản có thể có nhiều chức năng hơn là có thể thiết lập lại?
TheCatWhisperer

2
"Không phải tất cả các tài khoản đều có thể được thiết lập lại." Không chắc chắn nếu điều này đã được viết trước khi chỉnh sửa. Lớp thứ hai là NotResetable. "Có thể một tài khoản có nhiều chức năng hơn là có thể thiết lập lại" Tôi mong đợi như vậy nhưng dường như nó không quan trọng đối với câu hỏi.
JimmyJames

1
Đây là giải pháp đi đúng hướng, tôi nghĩ, vì nó cung cấp một giải pháp tốt đẹp trong OO. Có lẽ sẽ thay đổi tên tên giao diện thành IPasswordReseter (hoặc một cái gì đó để có hiệu lực đó), để tách biệt các giao diện. IAccount có thể được tuyên bố là hỗ trợ nhiều giao diện khác. Điều đó sẽ cung cấp cho bạn tùy chọn để có các lớp với việc triển khai sau đó được soạn thảo và sử dụng bởi ủy quyền trên các đối tượng miền. @JimmyJames, tiểu thư, trên C # cả hai lớp sẽ cần chỉ định rằng họ triển khai IAccount. Nếu không thì mã trông hơi lạ ;-)
Miyamoto Akira

1
Ngoài ra, hãy kiểm tra nhận xét từ Snowman ở một trong những câu trả lời khác về CanX () và DoX (). Tuy nhiên, không phải lúc nào cũng là một kiểu chống mẫu, vì nó có sử dụng (ví dụ như về hành vi của UI)
Miyamoto Akira

1
Câu trả lời này bắt đầu tốt: đây là OOP xấu. Thật không may, giải pháp của bạn cũng tệ như vậy bởi vì nó cũng thay đổi hệ thống loại nhưng lại ném ra một ngoại lệ. Một giải pháp tốt có vẻ khác: không có phương pháp chưa thực hiện trên một loại. .NET framework thực sự sử dụng cách tiếp cận của bạn cho một số loại nhưng đó rõ ràng là một sai lầm.
Konrad Rudolph

0

Câu trả lời

Câu trả lời hơi quan tâm của tôi cho câu hỏi của bạn là:

Các khả năng của một đối tượng nên được xác định thông qua chính chúng, chứ không phải bằng cách thực hiện một giao diện. Một ngôn ngữ được gõ tĩnh, mạnh mẽ sẽ hạn chế khả năng này, mặc dù (theo thiết kế).

Phụ lục:

Phân nhánh trên loại đối tượng là xấu xa.

Lan man

Tôi thấy rằng bạn đã không thêm c#thẻ, nhưng đang hỏi trong một object-orientedbối cảnh chung .

Những gì bạn đang mô tả là tính kỹ thuật của các ngôn ngữ được nhập / mạnh và không cụ thể về "OO". Vấn đề của bạn bắt nguồn từ những người khác, từ thực tế là bạn không thể gọi các phương thức trong các ngôn ngữ đó mà không có loại đủ hẹp trong thời gian biên dịch (thông qua định nghĩa biến, tham số giá trị phương thức / định nghĩa giá trị trả về hoặc phân tách rõ ràng). Tôi đã thực hiện cả hai biến thể của bạn trong quá khứ, bằng các loại ngôn ngữ này và cả hai đều có vẻ xấu đối với tôi những ngày này.

Trong các ngôn ngữ OO được gõ động, vấn đề này không xảy ra do một khái niệm gọi là "gõ vịt". Nói tóm lại, điều này có nghĩa là về cơ bản bạn không khai báo loại đối tượng ở bất cứ đâu (ngoại trừ khi tạo nó). Bạn gửi tin nhắn đến các đối tượng và các đối tượng xử lý các tin nhắn đó. Bạn không quan tâm đến việc đối tượng có thực sự có một phương thức của tên đó hay không. Một số ngôn ngữ thậm chí có một method_missingphương pháp chung, bắt tất cả làm cho điều này thậm chí còn linh hoạt hơn.

Vì vậy, trong một ngôn ngữ như vậy (ví dụ như Ruby), mã của bạn trở thành:

account.reset_password

Giai đoạn.

Họ accountsẽ đặt lại mật khẩu, ném ngoại lệ "bị từ chối" hoặc ném ngoại lệ "không biết cách xử lý 'reset_password'".

Nếu bạn cần phân nhánh rõ ràng vì bất kỳ lý do gì, bạn vẫn sẽ làm như vậy về khả năng chứ không phải trên lớp:

account.reset_password  if account.can? :reset_password

(Trường hợp can?chỉ là một phương thức kiểm tra xem đối tượng có thể thực thi một số phương thức hay không mà không gọi nó.)

Lưu ý rằng mặc dù sở thích cá nhân của tôi hiện đang sử dụng các ngôn ngữ được nhập động, đây chỉ là một sở thích cá nhân. Các ngôn ngữ được nhập tĩnh cũng có những thứ phù hợp với chúng, vì vậy vui lòng không xem việc lan man này như một cú đánh đối với các ngôn ngữ được nhập tĩnh ...


Tuyệt vời để có quan điểm của bạn ở đây! Chúng tôi ở phía java / C # có xu hướng quên một nhánh OOP khác tồn tại. Khi tôi nói với các đồng nghiệp của mình, JS (đối với tất cả các vấn đề của nó), theo nhiều cách, nhiều OO hơn C #, họ cười tôi!
TheCatWhisperer

Tôi yêu C #, và tôi nghĩ đó là một ngôn ngữ có mục đích chung tốt , nhưng đó là ý kiến ​​cá nhân của tôi, về mặt OO, nó khá kém. Trong cả hai tính năng và thực tiễn, nó có xu hướng khuyến khích lập trình thủ tục.
TheCatWhisperer

2
Có thể cho rằng, kiểm tra sự tồn tại của một phương thức trong ngôn ngữ gõ vịt cũng giống như kiểm tra việc thực hiện giao diện trong một giao diện được gõ tĩnh: bạn đang thực hiện kiểm tra thời gian chạy xem đối tượng có khả năng cụ thể không. Mỗi khai báo phương thức có hiệu quả là giao diện riêng của nó, chỉ được xác định bằng tên phương thức và sự tồn tại của nó không thể được sử dụng để suy ra bất kỳ thông tin nào khác về đối tượng.
IMSoP

0

Có vẻ như các lựa chọn của bạn có nguồn gốc từ các lực thúc đẩy khác nhau. Cái đầu tiên dường như là một hack được sử dụng do mô hình đối tượng kém. Nó đã chứng minh rằng sự trừu tượng của bạn là sai, vì chúng phá vỡ tính đa hình. Có lẽ nhiều hơn là nó không phá vỡ Nguyên tắc đóng mở , dẫn đến khớp nối chặt chẽ hơn.

Tùy chọn thứ hai của bạn có thể ổn từ phối cảnh OOP, nhưng kiểu truyền tải làm tôi bối rối. Nếu tài khoản của bạn cho phép bạn đặt lại mật khẩu, tại sao bạn lại cần một kiểu chữ? Vì vậy, giả sử rằng CanResetPasswordđiều khoản của bạn thể hiện một số yêu cầu kinh doanh cơ bản là một phần của sự trừu tượng hóa tài khoản, tùy chọn thứ hai của bạn là OK.

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.