ความสามารถของวัตถุควรถูกระบุโดยอินเทอร์เฟซที่ใช้หรือไม่


36

isโอเปอเรเตอร์ของ C # และโอเปอเรเตอร์ของ Java instanceofช่วยให้คุณสามารถแยกสาขาบนอินเทอร์เฟซได้

เหมาะสมหรือไม่ที่จะใช้คุณสมบัตินี้ในการแยกสาขาในระดับสูงตามความสามารถที่อินเตอร์เฟสจัดให้?

หรือคลาสฐานควรให้ตัวแปรบูลีนเพื่อให้อินเทอร์เฟซเพื่ออธิบายความสามารถของวัตถุที่มี?

ตัวอย่าง:

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

เมื่อเทียบกับ

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

มีข้อผิดพลาดใด ๆ ในการใช้งานส่วนต่อประสานเพื่อระบุความสามารถหรือไม่?


ตัวอย่างนี้เป็นเพียงตัวอย่างเท่านั้น ฉันสงสัยเกี่ยวกับแอปพลิเคชันทั่วไปที่มากขึ้น


10
ดูตัวอย่างโค้ดสองอันของคุณ อันไหนที่อ่านได้มากกว่า?
Robert Harvey

39
แค่ความคิดเห็นของฉัน แต่ฉันจะไปกับคนแรกเพียงเพราะทำให้แน่ใจว่าคุณสามารถส่งไปยังประเภทที่เหมาะสมได้อย่างปลอดภัย คนที่สองสันนิษฐานว่าจะเป็นจริงเมื่อสิ่งที่ดำเนินการCanResetPassword IResetsPasswordตอนนี้คุณกำลังสร้างคุณสมบัติกำหนดสิ่งที่ระบบประเภทควรควบคุม (และท้ายที่สุดจะเมื่อคุณ preform cast)
Becuzz

10
@nocomprende: xkcd.com/927
Robert Harvey

14
ชื่อคำถามของคุณและเนื้อหาคำถามถามสิ่งต่าง ๆ ในการตอบชื่อเรื่อง: เป็นการดีใช่ ในการจัดการกับร่างกาย: หากคุณพบว่าตัวเองกำลังตรวจสอบประเภทและส่งงานด้วยตนเองคุณอาจไม่ได้ใช้ประโยชน์จากระบบประเภทของคุณอย่างถูกต้อง คุณช่วยเล่าให้เราฟังได้มากขึ้นว่าคุณพบตัวเองในสถานการณ์นี้ได้อย่างไร?
MetaFight

9
คำถามนี้อาจดูเหมือนง่าย แต่ก็ค่อนข้างกว้างขึ้นเมื่อคุณเริ่มคิดถึงมัน คำถามที่ปรากฏในใจของฉัน: คุณคาดหวังว่าจะเพิ่มพฤติกรรมใหม่ให้กับบัญชีที่มีอยู่หรือสร้างประเภทบัญชีที่มีพฤติกรรมที่แตกต่างกันหรือไม่? บัญชีทุกประเภทมีพฤติกรรมที่คล้ายกันหรือมีความแตกต่างกันอย่างมากหรือไม่? โค้ดตัวอย่างรู้เกี่ยวกับบัญชีทุกประเภทหรือเพียงอินเทอร์เฟซพื้นฐาน IAccount หรือไม่
ร่าเริง

คำตอบ:


7

ด้วยข้อแม้ที่ดีที่สุดที่จะหลีกเลี่ยงการ downcasting ถ้าเป็นไปได้แล้วรูปแบบแรกจะดีกว่าด้วยเหตุผลสองประการ:

  1. คุณกำลังปล่อยให้ระบบประเภททำงานให้คุณ ในตัวอย่างแรกถ้าisไฟไหม้การดาวน์ไลท์จะทำงานได้อย่างแน่นอน ในรูปแบบที่สองคุณจะต้องตรวจสอบด้วยตนเองว่ามีเพียงวัตถุที่ใช้IResetsPasswordคืนค่าจริงสำหรับคุณสมบัตินั้น
  2. ชั้นฐานที่เปราะบาง คุณต้องเพิ่มคุณสมบัติให้กับคลาสพื้นฐาน / อินเตอร์เฟสสำหรับทุกอินเตอร์เฟสที่คุณต้องการเพิ่ม นี่เป็นเรื่องยุ่งยากและเกิดข้อผิดพลาดได้ง่าย

ไม่ได้หมายความว่าคุณไม่สามารถแก้ไขปัญหาเหล่านี้ได้บ้าง ตัวอย่างเช่นคุณสามารถให้คลาสพื้นฐานมีชุดอินเทอร์เฟซที่เผยแพร่ซึ่งคุณสามารถตรวจสอบการรวมระบบได้ แต่คุณเพียงแค่นำส่วนของระบบประเภทที่มีอยู่ซึ่งซ้ำซ้อนด้วยตนเอง

BTW ความชอบตามธรรมชาติของฉันคือการจัดองค์ประกอบการสืบทอด แต่ส่วนใหญ่เป็นเรื่องที่เกี่ยวข้องกับรัฐ การสืบทอดส่วนต่อประสานนั้นไม่ได้เลวร้ายนัก นอกจากนี้หากคุณพบว่าตัวเองใช้ลำดับชั้นของชายยากจนคุณควรใช้สิ่งที่มีอยู่แล้วเพราะคอมไพเลอร์จะช่วยคุณ


ฉันชอบการพิมพ์แบบเรียงความมาก +1 ที่ทำให้ฉันเห็นว่าตัวอย่างนี้ไม่ได้ใช้ประโยชน์อย่างเหมาะสม แม้ว่าฉันจะบอกว่าการพิมพ์คอมพิวเตอร์ (เช่นการสืบทอดดั้งเดิม) มีข้อ จำกัด มากขึ้นเมื่อความสามารถแตกต่างกันอย่างมาก (เมื่อเทียบกับวิธีการใช้งาน) ดังนั้นวิธีการเชื่อมต่อซึ่งแก้ปัญหาที่แตกต่างจากการพิมพ์แบบเรียงความหรือการสืบทอดมาตรฐาน
TheCatWhisperer

คุณสามารถทำให้การตรวจสอบเป็นเรื่องง่ายขึ้นเช่นนี้: (account as IResettable)?.ResetPassword();หรือvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Berin Loritsch

89

ความสามารถของวัตถุควรถูกระบุโดยอินเทอร์เฟซที่ใช้หรือไม่

ไม่ควรระบุความสามารถของวัตถุ

ลูกค้าที่ใช้วัตถุไม่ควรต้องรู้อะไรเกี่ยวกับวิธีการทำงาน ลูกค้าควรรู้สิ่งที่สามารถบอกให้วัตถุทำ สิ่งที่วัตถุทำเมื่อได้รับแจ้งแล้วไม่ใช่ปัญหาของลูกค้า

ดังนั้นมากกว่า

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

หรือ

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

พิจารณา

account.ResetPassword();

หรือ

account.ResetPassword(authority);

เพราะaccountรู้อยู่แล้วว่าจะใช้งานได้หรือไม่ ถามทำไม เพียงแค่บอกสิ่งที่คุณต้องการและปล่อยให้มันทำสิ่งที่มันจะทำ

ลองจินตนาการว่าสิ่งนี้ทำในลักษณะที่ลูกค้าไม่สนใจว่ามันจะทำงานหรือไม่เพราะนั่นเป็นปัญหา งานของลูกค้าเป็นเพียงการพยายาม เสร็จแล้วตอนนี้และมันมีสิ่งอื่น ๆ ที่จะจัดการกับ ซึ่งรูปแบบนี้มีหลายชื่อ แต่ชื่อที่ผมชอบมากที่สุดคือการบอกไม่ได้ถาม

มันเป็นเรื่องน่าดึงดูดใจอย่างมากเมื่อเขียนลูกค้าให้คิดว่าคุณต้องติดตามทุกสิ่งและดึงสิ่งที่คุณคิดว่าจำเป็นต้องรู้มาให้คุณ เมื่อคุณทำเช่นนั้นคุณจะหันวัตถุภายในออกมา คุณค่าความเขลาของคุณ ผลักดันรายละเอียดออกไปและปล่อยให้วัตถุจัดการกับพวกเขา ยิ่งคุณรู้จักน้อยเท่าไหร่


54
ความแตกต่างใช้งานได้เฉพาะเมื่อฉันไม่รู้หรือสนใจสิ่งที่ฉันกำลังพูดถึง ดังนั้นวิธีที่ฉันจัดการกับสถานการณ์นั้นคือการหลีกเลี่ยงอย่างระมัดระวัง
candied_orange

7
หากไคลเอนต์ไม่จำเป็นต้องทราบว่าความพยายามที่ประสบความสำเร็จวิธีการอาจส่งกลับแบบบูลีน if (!account.AttemptPasswordReset()) /* attempt failed */;วิธีนี้ลูกค้าจะทราบผล แต่หลีกเลี่ยงปัญหาบางอย่างกับตรรกะการตัดสินใจในคำถาม หากความพยายามแบบอะซิงโครนัสคุณจะผ่านการติดต่อกลับ
Radiodef

5
@DavidZ เราทั้งคู่พูดภาษาอังกฤษได้ ดังนั้นฉันสามารถบอกให้คุณโหวตความคิดเห็นนี้ได้สองครั้ง นั่นหมายความว่าคุณสามารถทำได้หรือไม่ ฉันจำเป็นต้องรู้หรือไม่ถ้าคุณสามารถทำได้ก่อนที่ฉันจะบอกให้คุณทำ
candied_orange

5
@QPaysTaxes สำหรับทุกสิ่งที่คุณรู้ว่าตัวจัดการข้อผิดพลาดถูกส่งไปยังaccountเมื่อมันถูกสร้างขึ้น ณ จุดนี้ป๊อปอัปการบันทึกการพิมพ์และการเตือนด้วยเสียงอาจเกิดขึ้นได้ งานของลูกค้าคือการพูดว่า "ทำทันที" ไม่จำเป็นต้องทำมากกว่านั้น คุณไม่ต้องทำทุกอย่างในที่เดียว
candied_orange

6
@QPaysTaxes มันสามารถจัดการได้ที่อื่นและในหลาย ๆ วิธีมันไม่จำเป็นสำหรับการสนทนานี้และจะทำให้น้ำขุ่นมัวเท่านั้น
Tom.Bowen89

17

พื้นหลัง

การสืบทอดเป็นเครื่องมือที่มีประสิทธิภาพที่ทำหน้าที่ในการเขียนโปรแกรมเชิงวัตถุ อย่างไรก็ตามมันไม่ได้แก้ปัญหาทุกอย่างหรูหรา: บางครั้งการแก้ปัญหาอื่น ๆ จะดีกว่า

หากคุณคิดว่าจะกลับไปเรียนวิชาวิทยาการคอมพิวเตอร์ขั้นต้น (สมมติว่าคุณมีระดับ CS) คุณอาจจำอาจารย์ที่ให้ย่อหน้าที่ระบุสิ่งที่ลูกค้าต้องการให้ซอฟต์แวร์ทำ งานของคุณคือการอ่านย่อหน้าระบุนักแสดงและการกระทำและออกมาพร้อมกับคร่าวๆคร่าวๆของสิ่งที่ชั้นเรียนและวิธีการ จะมีผู้นำในที่ที่ดูเหมือนว่าพวกเขาจะมีความสำคัญ แต่ไม่ใช่ มีความเป็นไปได้ที่แท้จริงของข้อกำหนดการตีความที่ผิดเช่นกัน

นี่เป็นทักษะที่สำคัญที่แม้แต่คนที่มีประสบการณ์มากที่สุดของเราก็ผิดพลาด: ระบุความต้องการและแปลมันเป็นภาษาเครื่องอย่างถูกต้อง

คำถามของคุณ

จากสิ่งที่คุณเขียนฉันคิดว่าคุณอาจเข้าใจผิดเกี่ยวกับกรณีทั่วไปของการกระทำที่เป็นตัวเลือกที่ชั้นเรียนสามารถทำได้ ใช่ฉันรู้ว่ารหัสของคุณเป็นเพียงตัวอย่างและคุณสนใจในกรณีทั่วไป อย่างไรก็ตามดูเหมือนว่าคุณต้องการทราบวิธีจัดการกับสถานการณ์ที่ชนิดย่อยของวัตถุบางอย่างสามารถดำเนินการได้ แต่ชนิดย่อยอื่นไม่สามารถทำได้

เพียงเพราะวัตถุเช่นaccountมีบัญชีประเภทไม่ได้หมายความว่าแปลเป็นประเภทในภาษา OO "ประเภท" ในภาษามนุษย์ไม่ได้หมายถึง "คลาส" เสมอไป ในบริบทของบัญชี "ประเภท" อาจมีความสัมพันธ์ใกล้ชิดกับ "ชุดสิทธิ์" มากขึ้น คุณต้องการใช้บัญชีผู้ใช้เพื่อดำเนินการ แต่การกระทำนั้นอาจหรืออาจไม่สามารถทำได้โดยบัญชีนั้น แทนที่จะใช้การสืบทอดฉันจะใช้โทเค็นของผู้รับมอบสิทธิ์หรือความปลอดภัย

โซลูชันของฉัน

พิจารณาคลาสบัญชีที่มีการดำเนินการทางเลือกหลายอย่างที่สามารถทำได้ แทนที่จะกำหนด "สามารถดำเนินการ X" ผ่านการสืบทอดทำไมบัญชีไม่ส่งคืนออบเจ็กผู้แทน (รีเซ็ตรหัสผ่านผู้ส่งแบบฟอร์มเป็นต้น) หรือโทเค็นการเข้าถึง

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

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

ประโยชน์ที่ได้รับจากตัวเลือกสุดท้ายจะเกิดอะไรขึ้นถ้าฉันต้องการใช้ข้อมูลรับรองบัญชีของฉันเพื่อรีเซ็ตรหัสผ่านของผู้อื่น

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

ไม่เพียง แต่เป็นระบบที่มีความยืดหยุ่นมากขึ้นไม่เพียงฉันได้ปลดเปลื้องชนิดที่ถูกนำออก แต่การออกแบบมากขึ้นที่แสดงออก ฉันสามารถอ่านและเข้าใจสิ่งที่กำลังทำอยู่และสามารถนำไปใช้ได้อย่างง่ายดาย


TL; DR: นี้อ่านเหมือนปัญหา XY โดยทั่วไปเมื่อต้องเผชิญกับสองตัวเลือกที่ก่อให้เกิดผลลัพธ์มันเป็นมูลค่าการกลับขั้นตอนและความคิด "สิ่งที่ฉันจริงๆพยายามที่จะบรรลุที่นี่? ควรผมจะคิดเกี่ยวกับวิธีที่จะทำให้ Typecasting น่าเกลียดน้อยลงหรือฉันควรมองหาวิธีการ ลบ typecast ทั้งหมดหรือไม่ "


1
+1 สำหรับการออกแบบมีกลิ่นแม้ว่าคุณจะไม่ได้ใช้วลี
Jared Smith

กรณีใดบ้างที่รหัสผ่านรีเซ็ตมีอาร์กิวเมนต์ที่ต่างกันโดยสิ้นเชิงขึ้นอยู่กับประเภท ResetPassword (pswd) vs ResetPasswordToRandom ()
TheCatWhisperer

1
@TheCatWhisperer ตัวอย่างแรกคือ "เปลี่ยนรหัสผ่าน" ซึ่งเป็นการกระทำที่แตกต่าง ตัวอย่างที่สองคือสิ่งที่ฉันอธิบายในคำตอบนี้

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?.. ง่าย คุณสร้างวัตถุโดเมนบัญชีสำหรับบัญชีอื่นและใช้อันนั้น คลาสสแตติกเป็นปัญหาสำหรับการทดสอบหน่วย (โดยวิธีนั้นจะต้องมีการเข้าถึงหน่วยความจำบางส่วนดังนั้นในตอนนี้คุณไม่สามารถทดสอบโค้ดใด ๆ ที่สัมผัสกับคลาสนั้นได้อย่างง่ายดาย) ตัวเลือกในการคืนอินเทอร์เฟซอื่นมักเป็นความคิดที่ดี แต่ไม่ได้จัดการกับปัญหาพื้นฐาน (คุณเพิ่งย้ายปัญหาไปยังอินเทอร์เฟซอื่น)
Voo

1

Bill Venner กล่าวว่าวิธีการของคุณดีมาก ข้ามไปที่ส่วนชื่อ 'เวลาใช้งาน instanceof' คุณกำลังดาวน์สตรีมไปยังประเภท / อินเทอร์เฟซเฉพาะที่ใช้เฉพาะพฤติกรรมนั้นเพื่อให้คุณมีสิทธิ์เลือกอย่างแน่นอน

อย่างไรก็ตามหากคุณต้องการใช้วิธีการอื่นมีหลายวิธีในการโกนจามรี

มีวิธีการที่หลากหลาย; คุณสามารถยืนยันว่าบัญชีทั้งหมดมีรหัสผ่านดังนั้นอย่างน้อยทุกบัญชีควรพยายามรีเซ็ตรหัสผ่าน ซึ่งหมายความว่าในชั้นบัญชีพื้นฐานเราควรมีresetPassword()วิธีการที่บัญชีทั้งหมดใช้ แต่ในทางของตนเอง คำถามคือวิธีการที่ควรปฏิบัติเมื่อไม่มีความสามารถ

มันอาจส่งคืนโมฆะและดำเนินการอย่างเงียบ ๆ ไม่ว่าจะรีเซ็ตรหัสผ่านหรือไม่ก็ตามอาจรับผิดชอบในการพิมพ์ข้อความภายในหากไม่ได้รีเซ็ตรหัสผ่าน ไม่ใช่ความคิดที่ดีที่สุด

มันสามารถส่งคืนบูลีนเพื่อระบุว่าการรีเซ็ตสำเร็จหรือไม่ เมื่อเปิดบูลีนนั้นเราสามารถบอกได้ว่าการรีเซ็ตรหัสผ่านล้มเหลว

มันสามารถส่งคืนสตริงที่ระบุผลลัพธ์ของความพยายามรีเซ็ตรหัสผ่าน สตริงสามารถให้รายละเอียดเพิ่มเติมเกี่ยวกับสาเหตุที่การรีเซ็ตล้มเหลวและอาจเป็นเอาต์พุต

มันสามารถส่งคืนวัตถุ ResetResult ที่ถ่ายทอดรายละเอียดเพิ่มเติมและรวมองค์ประกอบการส่งคืนก่อนหน้าทั้งหมด

มันอาจส่งคืนโมฆะและแทนที่จะส่งข้อยกเว้นหากคุณลองรีเซ็ตบัญชีที่ไม่มีความสามารถนั้น (อย่าทำเช่นนี้เพราะการใช้การจัดการข้อยกเว้นสำหรับการควบคุมโฟลว์ปกติเป็นการปฏิบัติที่ไม่ถูกต้องด้วยเหตุผลหลายประการ)

การมีcanResetPassword()วิธีการจับคู่อาจดูเหมือนไม่ได้เป็นสิ่งที่แย่ที่สุดในโลกเพราะมันเป็นความสามารถแบบคงที่ในตัวเมื่อเขียน นี่เป็นเงื่อนงำว่าทำไมวิธีการของวิธีการจึงเป็นความคิดที่ไม่ดี แต่เนื่องจากมันแสดงให้เห็นว่าความสามารถนั้นเป็นแบบไดนามิกและcanResetPassword()อาจเปลี่ยนแปลงซึ่งจะสร้างความเป็นไปได้เพิ่มเติมที่อาจเปลี่ยนไประหว่างการขออนุญาต ดังที่กล่าวไว้ที่อื่นบอกแทนที่จะขออนุญาต

การจัดองค์ประกอบเหนือการสืบทอดอาจเป็นตัวเลือก: คุณสามารถมีpasswordResetterฟิลด์สุดท้าย(หรือทะเยอทะยานที่เทียบเท่า) และคลาสเทียบเท่า (es) ที่คุณสามารถตรวจสอบ null ก่อนที่จะเรียกมัน ในขณะที่มันทำหน้าที่เหมือนการขออนุญาต แต่สุดท้ายก็อาจหลีกเลี่ยงลักษณะแบบไดนามิกที่อนุมานได้

คุณอาจคิดว่าการแบ่งหน้าที่การใช้งานออกเป็นคลาสของตัวเองซึ่งอาจใช้บัญชีเป็นพารามิเตอร์และดำเนินการกับมัน (เช่นresetter.reset(account)) แม้ว่ามันจะเป็นวิธีปฏิบัติที่ไม่ดีเช่นกัน

ในภาษาที่มีความสามารถคุณอาจใช้มิกซ์อินหรือคุณลักษณะอย่างไรก็ตามคุณจะอยู่ในตำแหน่งที่คุณเริ่มต้นที่คุณอาจตรวจสอบความสามารถเหล่านั้น


บางบัญชีอาจมีรหัสผ่าน (จากมุมมองของระบบนี้โดยเฉพาะ) เช่นบัญชีของฉันเป็นบุคคลที่สาม ... google, facebook, ect อย่าบอกว่านี่ไม่ใช่คำตอบที่ยอดเยี่ยม!
TheCatWhisperer

0

สำหรับตัวอย่างที่เฉพาะเจาะจงของ " สามารถรีเซ็ตรหัสผ่าน " ฉันขอแนะนำให้ใช้การแต่งเพลงมากกว่าการสืบทอด (ในกรณีนี้การสืบทอดของอินเทอร์เฟซ / สัญญา) เพราะโดยทำสิ่งนี้:

class Foo : IResetsPassword {
    //...
}

คุณระบุทันที (ในเวลารวบรวม) ที่ชั้นเรียนของคุณ ' สามารถรีเซ็ตรหัสผ่าน ' แต่ถ้าในสถานการณ์ของคุณการปรากฏตัวของความสามารถนั้นมีเงื่อนไขและขึ้นอยู่กับสิ่งอื่น ๆ คุณจะไม่สามารถระบุสิ่งต่าง ๆ ในเวลารวบรวมได้อีกต่อไป จากนั้นฉันแนะนำให้ทำสิ่งนี้:

class Foo {

    PasswordResetter passwordResetter;

}

ตอนนี้ที่รันไทม์คุณสามารถตรวจสอบmyFoo.passwordResetter != nullก่อนดำเนินการนี้ หากคุณต้องการแยกชิ้นส่วนให้มากขึ้น (และคุณวางแผนที่จะเพิ่มความสามารถอื่น ๆ อีกมากมาย) คุณสามารถ:

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...

UPDATE

หลังจากที่ฉันอ่านคำตอบและความคิดเห็นอื่น ๆ จาก OP ฉันเข้าใจว่าคำถามไม่ได้เกี่ยวกับการแต่งเพลงประกอบ ดังนั้นฉันคิดว่าคำถามเกี่ยวกับวิธีการระบุความสามารถของวัตถุโดยทั่วไปดีขึ้นในสถานการณ์ดังนี้

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, ...);
}

ตอนนี้ฉันจะพยายามวิเคราะห์ตัวเลือกทั้งหมดที่กล่าวถึง:

ตัวอย่างที่สอง OP:

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

สมมติว่ารหัสนี้กำลังรับคลาสบัญชีพื้นฐานบางอย่าง (เช่น: BaseAccountในตัวอย่างของฉัน); สิ่งนี้ไม่ดีเนื่องจากมันเป็นการแทรกบูลีนในคลาสฐานทำให้เกิดมลพิษกับรหัสที่ทำให้ไม่มีเหตุผลที่จะอยู่ที่นั่น

OP ตัวอย่างแรก:

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

เพื่อตอบคำถามนี้มีความเหมาะสมมากกว่าตัวเลือกก่อนหน้านี้ แต่ขึ้นอยู่กับการใช้งานมันจะทำลายหลักการ L ของของแข็งและอาจตรวจสอบเช่นนี้จะแพร่กระจายผ่านรหัสและทำให้การบำรุงรักษาเพิ่มเติมยากขึ้น

แอนเซอร์ของ CandiedOrange:

account.ResetPassword(authority);

หากResetPasswordวิธีนี้ถูกแทรกในBaseAccountชั้นเรียนแล้วมันยังก่อมลพิษชั้นฐานด้วยรหัสที่ไม่เหมาะสมเช่นในตัวอย่างที่สองของ OP

คำตอบของ Snowman:

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

นี่เป็นวิธีแก้ปัญหาที่ดี แต่ก็ถือว่าความสามารถนั้นเป็นแบบไดนามิก (และอาจเปลี่ยนแปลงตลอดเวลา) อย่างไรก็ตามหลังจากที่ฉันอ่านความคิดเห็นต่าง ๆ จาก OP ฉันเดาว่าการพูดคุยที่นี่เกี่ยวข้องกับ polymorphism และคลาสที่กำหนดไว้แบบสแตติก EG: ในAccountManagerตัวอย่างนี้การตรวจสอบการอนุญาตจะเป็นการสืบค้นไปยัง DB; ในคำถาม OP การตรวจสอบคือความพยายามในการหล่อวัตถุ

ข้อเสนอแนะอื่นจากฉัน:

ใช้รูปแบบวิธีการเทมเพลตสำหรับการแยกสาขาระดับสูง ลำดับชั้นของคลาสที่กล่าวถึงจะถูกเก็บไว้เหมือนเดิม เราสร้างตัวจัดการที่เหมาะสมมากขึ้นสำหรับวัตถุเพื่อหลีกเลี่ยงการปลดเปลื้องและคุณสมบัติ / วิธีการที่ไม่เหมาะสมในการสร้างคลาสพื้นฐาน

//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(...);
    }
}

คุณสามารถผูกการดำเนินการกับคลาสบัญชีที่เหมาะสมโดยใช้พจนานุกรม / hashtable หรือทำการดำเนินการแบบรันไทม์โดยใช้วิธีการขยายใช้dynamicคำสำคัญหรือเป็นตัวเลือกสุดท้ายใช้เพี้ยนเพียงครั้งเดียวเพื่อส่งวัตถุบัญชีไปยังการดำเนินการ (ใน กรณีนี้จำนวนการปลดเปลื้องจะมีเพียงครั้งเดียวเท่านั้นในตอนเริ่มต้นของการปฏิบัติการ)


1
มันจะทำงานเพื่อใช้วิธี "Execute ()" เพียง แต่บางครั้งก็ไม่ได้ทำอะไรเลยหรือไม่? มันคืนบูลดังนั้นฉันเดาว่านั่นหมายความว่าทำหรือไม่ มันโยนกลับไปที่ลูกค้า: "ฉันพยายามรีเซ็ตรหัสผ่าน แต่มันไม่ได้เกิดขึ้น (ด้วยเหตุผลอะไรก็ตาม) ดังนั้นตอนนี้ฉันควร ... "

4
การมีเมธอด "canX ()" และ "doX ()" เป็น antipattern: หากอินเตอร์เฟสแสดงวิธี "doX ()" มันจะดีกว่าที่จะทำ X แม้ว่าการทำ X หมายถึงไม่มี op (เช่นรูปแบบวัตถุ null ) ไม่ควรรับผิดชอบต่อผู้ใช้อินเทอร์เฟซเพื่อมีส่วนร่วมในการมีเพศสัมพันธ์ชั่วคราว (เรียกใช้เมธอด A ก่อนเมธอด B) เพราะมันง่ายเกินไปที่จะผิดและทำลายสิ่งต่าง ๆ

1
การมีส่วนต่อประสานไม่มีส่วนช่วยในการใช้องค์ประกอบเหนือการสืบทอด พวกเขาเพียงแค่แสดงฟังก์ชั่นที่มีอยู่ การทำงานที่เกิดขึ้นเมื่อเล่นนั้นไม่ขึ้นอยู่กับการใช้อินเทอร์เฟซ สิ่งที่คุณทำที่นี่คือการใช้งาน Ad-hoc ของอินเทอร์เฟซโดยไม่มีผลประโยชน์ใด ๆ
Miyamoto Akira

สิ่งที่น่าสนใจ: คำตอบที่ได้รับคะแนนสูงสุดบอกว่าโดยทั่วไปแล้วสิ่งที่ความคิดเห็นของฉันกล่าวไว้ข้างต้นนั้น บางวันคุณเพิ่งได้รับโชค

@nocomprende - ใช่ฉันได้อัปเดตคำตอบตามความคิดเห็นหลายประการ ขอขอบคุณสำหรับการตอบ :)
เมอร์สัน Cardoso

0

ตัวเลือกที่คุณได้นำเสนอนั้นไม่ดีเลย OO หากคุณเขียนว่าคำแถลงเกี่ยวกับประเภทของวัตถุเป็นไปได้มากว่าคุณทำผิด OO (มีข้อยกเว้นนี่ไม่ใช่คำตอบ) นี่คือคำตอบ OO ที่ง่ายสำหรับคำถามของคุณ (อาจไม่ถูกต้อง 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!");}
  }

ฉันได้แก้ไขตัวอย่างนี้เพื่อให้ตรงกับสิ่งที่รหัสต้นฉบับกำลังทำอยู่ จากความคิดเห็นบางส่วนดูเหมือนว่าผู้คนเริ่มสับสนกันว่านี่เป็นรหัสเฉพาะที่ถูกต้องหรือไม่ที่นี่ นั่นไม่ใช่จุดของตัวอย่างนี้ การโอเวอร์โหลดแบบ Polymorphic ทั้งหมดนั้นสำคัญอย่างยิ่งที่จะดำเนินการด้านตรรกะแบบต่าง ๆ ตามเงื่อนไขตามประเภทของวัตถุ สิ่งที่คุณทำในทั้งสองตัวอย่างคือการใช้ภาษามือของคุณเป็นคุณลักษณะ สรุปคุณสามารถกำจัดประเภทย่อยและนำความสามารถในการรีเซ็ตเป็นคุณสมบัติบูลีนของประเภทบัญชี (ไม่สนใจคุณสมบัติอื่น ๆ ของประเภทย่อย)

หากไม่มีมุมมองที่กว้างขึ้นของการออกแบบเป็นไปไม่ได้ที่จะบอกได้ว่านี่เป็นทางออกที่ดีสำหรับระบบของคุณโดยเฉพาะหรือไม่ มันง่ายและถ้ามันใช้งานได้กับสิ่งที่คุณกำลังทำอยู่คุณจะไม่ต้องคิดมากเกี่ยวกับเรื่องนี้อีกเว้นแต่จะมีใครตรวจสอบ CanResetPassword () ก่อนที่จะโทรไปที่ ResetPassword () คุณสามารถส่งคืนบูลีนหรือล้มเหลวอย่างเงียบ ๆ (ไม่แนะนำ) มันขึ้นอยู่กับลักษณะเฉพาะของการออกแบบ


ไม่สามารถรีเซ็ตบัญชีได้ทั้งหมด นอกจากนี้บัญชีอาจมีฟังก์ชันการทำงานมากกว่าการรีเซ็ตได้หรือไม่
TheCatWhisperer

2
"ไม่สามารถรีเซ็ตบัญชีได้ทั้งหมด" ไม่แน่ใจว่าสิ่งนี้ถูกเขียนก่อนแก้ไขหรือไม่ NotResetableระดับที่สองคือ "บัญชีอาจมีฟังก์ชั่นมากกว่าตั้งค่าได้" ฉันคาดหวัง แต่ดูเหมือนจะไม่สำคัญสำหรับคำถามในมือ
JimmyJames

1
นี่คือทางออกไปในทิศทางที่ถูกต้องฉันคิดว่ามันเป็นทางออกที่ดีใน OO อาจเปลี่ยนชื่ออินเทอร์เฟซชื่อเป็น IPasswordReseter (หรือบางสิ่งบางอย่างเพื่อผลกระทบนั้น) เพื่อแยกอินเทอร์เฟซ IAccount สามารถประกาศได้ว่ารองรับหลาย ๆ อินเตอร์เฟส ที่จะให้ตัวเลือกแก่คุณเพื่อให้มีคลาสที่มีการนำไปใช้งานซึ่งประกอบด้วยและใช้งานโดยการมอบหมายบนวัตถุโดเมน @JimmyJames, nitpick เล็กน้อยบน C # ทั้งสองคลาสจะต้องระบุว่าพวกเขาใช้ IAccount มิฉะนั้นโค้ดจะดูแปลก ๆ ;-)
Miyamoto Akira

1
ตรวจสอบความคิดเห็นจาก Snowman ด้วยหนึ่งในคำตอบอื่น ๆ เกี่ยวกับ CanX () และ DoX () ไม่ได้เป็นรูปแบบการต่อต้านเสมอไปเช่นเดียวกับที่ใช้ (ตัวอย่างเช่นพฤติกรรม UI)
Miyamoto Akira

1
คำตอบนี้เริ่มต้นได้ดี: OOP ไม่ดี แต่น่าเสียดายที่การแก้ปัญหาของคุณเป็นเพียงเป็นเลวเพราะมันยัง subverts ระบบการพิมพ์ แต่พ่นยกเว้น ทางออกที่ดีนั้นดูแตกต่าง: ไม่มีวิธีการที่ไม่ได้ใช้กับประเภท กรอบงาน. NET ใช้วิธีการของคุณสำหรับบางประเภท แต่นั่นเป็นความผิดพลาดอย่างไม่น่าสงสัย
Konrad Rudolph

0

ตอบ

คำตอบที่ตรงไปตรงมาสำหรับคำถามของคุณคือ:

ควรระบุความสามารถของวัตถุด้วยตนเองไม่ใช่โดยการนำอินเตอร์เฟสไปใช้ ภาษาที่มีการพิมพ์อย่างแน่นหนาจะจำกัดความเป็นไปได้นี้ (โดยการออกแบบ)

ภาคผนวก:

การแตกแขนงกับชนิดของวัตถุนั้นชั่วร้าย

ท่องเที่ยว

ฉันเห็นว่าคุณไม่ได้เพิ่มc#แท็ก แต่กำลังถามในobject-orientedบริบททั่วไป

สิ่งที่คุณกำลังอธิบายคือเทคนิคของภาษาที่พิมพ์แบบคงที่ / แข็งแรงและไม่ใช่เฉพาะของ "OO" ปัญหาของคุณนั้นเกิดจากการที่คุณไม่สามารถเรียกเมธอดในภาษาเหล่านั้นได้โดยที่ไม่มีประเภทที่แคบพอสมควรในเวลารวบรวม (ไม่ว่าจะผ่านการกำหนดตัวแปร, พารามิเตอร์เมธอด / การกำหนดค่าส่งคืน ฉันได้ทำสายพันธุ์ของคุณทั้งสองในอดีตในภาษาเหล่านี้และทั้งคู่ก็ดูน่าเกลียดสำหรับฉันในวันนี้

ในภาษา OO ที่พิมพ์แบบไดนามิกปัญหานี้จะไม่เกิดขึ้นเนื่องจากแนวคิดที่เรียกว่า "การพิมพ์เป็ด" กล่าวโดยย่อคือหมายความว่าคุณไม่ได้ประกาศประเภทของวัตถุใด ๆ (ยกเว้นเมื่อสร้างมันขึ้นมา) คุณส่งข้อความไปยังวัตถุและวัตถุจัดการข้อความเหล่านั้น คุณไม่สนใจว่าวัตถุนั้นมีวิธีชื่อจริงหรือไม่ บางภาษามีวิธีการทั่วไปที่จับได้method_missingซึ่งทำให้สิ่งนี้ยืดหยุ่นยิ่งขึ้น

ดังนั้นในภาษาดังกล่าว (เช่นทับทิม) รหัสของคุณจะกลายเป็น:

account.reset_password

ระยะเวลา

accountอาจจะตั้งค่ารหัสผ่านโยน "ปฏิเสธ" ข้อยกเว้นหรือโยน "ไม่ทราบวิธีการที่จะจัดการกับ 'reset_password" ข้อยกเว้น

หากคุณต้องการแยกสาขาอย่างชัดเจนไม่ว่าด้วยเหตุผลใดคุณจะยังคงทำเช่นนั้นกับความสามารถไม่ใช่ในชั้นเรียน:

account.reset_password  if account.can? :reset_password

(ซึ่งcan?เป็นเพียงวิธีการที่ตรวจสอบว่าวัตถุสามารถดำเนินการวิธีการบางอย่างโดยไม่ต้องเรียกมัน)

โปรดทราบว่าในขณะที่การตั้งค่าส่วนตัวของฉันอยู่ในภาษาที่พิมพ์แบบไดนามิก แต่นี่เป็นเพียงการตั้งค่าส่วนตัว ภาษาที่พิมพ์แบบคงที่มีสิ่งต่าง ๆ ที่เกิดขึ้นกับพวกเขาเช่นกันดังนั้นโปรดอย่าใช้คำเยินยอนี้เพื่อทุบตีกับภาษาที่พิมพ์แบบคงที่ ...


ดีใจที่ได้มีมุมมองของคุณที่นี่! พวกเราในด้าน java / C # มักจะลืมสาขา OOP ที่แตกต่างกันอยู่ เมื่อฉันบอกเพื่อนร่วมงานของฉัน JS (สำหรับปัญหาทั้งหมด) คือในหลาย ๆ ทาง OO มากกว่า C # พวกเขาหัวเราะเยาะฉัน!
TheCatWhisperer

ฉันรัก C # และฉันคิดว่ามันเป็นภาษาที่ใช้งานทั่วไปได้ดีแต่มันเป็นความเห็นส่วนตัวของฉันว่าในแง่ของ OO มันค่อนข้างแย่ ในทั้งคุณสมบัติและการปฏิบัติมันมีแนวโน้มที่จะสนับสนุนการเขียนโปรแกรมตามขั้นตอน
TheCatWhisperer

2
การทดสอบการมีอยู่ของวิธีการในภาษาที่พิมพ์ด้วยเป็ดนั้นเหมือนกับการทดสอบการใช้งานอินเทอร์เฟซในแบบที่พิมพ์แบบคงที่: คุณกำลังทำการตรวจสอบแบบรันไทม์ว่าวัตถุนั้นมีความสามารถเฉพาะหรือไม่ ทุกวิธีการประกาศอย่างมีประสิทธิภาพเป็นอินเทอร์เฟซของตัวเองระบุโดยชื่อวิธีเท่านั้นและการดำรงอยู่ของมันไม่สามารถใช้เพื่ออนุมานข้อมูลอื่น ๆ เกี่ยวกับวัตถุ
IMSoP

0

ดูเหมือนว่าทางเลือกของคุณนั้นมาจากแรงจูงใจที่แตกต่างกัน คนแรกดูเหมือนจะเป็นแฮ็คที่ใช้เนื่องจากการสร้างแบบจำลองวัตถุที่ไม่ดี มันแสดงให้เห็นว่า abstractions ของคุณผิดเพราะแตกต่าง อาจเป็นไปได้มากกว่าการแยกหลักการปิดแบบเปิดซึ่งส่งผลให้มีเพศสัมพันธ์ที่เข้มงวดมากขึ้น

ตัวเลือกที่สองของคุณอาจไม่เป็นไรจากมุมมองของ OOP แต่การคัดเลือกนักแสดงแบบนั้นทำให้ฉันสับสน หากบัญชีของคุณให้คุณตั้งรหัสผ่านใหม่ทำไมคุณต้องพิมพ์ตัวอักษร ดังนั้นสมมติว่าCanResetPasswordประโยคของคุณแสดงถึงความต้องการทางธุรกิจขั้นพื้นฐานที่เป็นส่วนหนึ่งของการลบบัญชีตัวเลือกที่สองของคุณก็โอเค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.