คำจำกัดความที่ขัดแย้งกันสองประการของหลักการแยกส่วนต่อประสาน - ข้อใดที่ถูกต้อง?


14

เมื่ออ่านบทความบน ISP ดูเหมือนว่ามีคำจำกัดความที่ขัดแย้งกันสองอย่างของ ISP:

ตามคำจำกัดความแรก (ดู1 , 2 , 3 ) ISP ระบุว่าคลาสที่ใช้อินเทอร์เฟซไม่ควรถูกบังคับให้ใช้ฟังก์ชันที่พวกเขาไม่ต้องการ ดังนั้นอินเตอร์เฟซไขมันIFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

ควรแบ่งออกเป็นอินเตอร์เฟสขนาดเล็กISmall_1และISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

ตั้งแต่วิธีนี้ฉันMyClassสามารถที่จะใช้เพียงวิธีการที่จำเป็น ( D()และC()) โดยไม่ต้องถูกบังคับให้ยังให้การใช้งานหุ่นสำหรับA(), B()และC():

แต่ตามคำจำกัดความที่สอง (ดู1 , 2 , คำตอบโดย Nazar Merza ) ผู้ให้บริการอินเทอร์เน็ตระบุว่าMyClientวิธีการเรียกใช้MyServiceไม่ควรตระหนักถึงวิธีการMyServiceที่ไม่จำเป็น กล่าวอีกนัยหนึ่งหากMyClientต้องการเพียงแค่ฟังก์ชั่นของC()และD()แทนที่จะเป็น

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

เราควรแยกMyService'sเมธอดออกเป็นอินเตอร์เฟสเฉพาะไคลเอ็นต์ :

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

ดังนั้นด้วยคำจำกัดความเดิมเป้าหมายของ ISP คือ " ทำให้ชีวิตของคลาสที่ใช้อินเทอร์เฟซ IFat ง่ายขึ้น " ในขณะที่เป้าหมายหลังของ ISP คือ " ทำให้ชีวิตของลูกค้าเรียกวิธีการของ MyService ง่ายขึ้น "

คำจำกัดความที่แตกต่างกันสองอย่างของ ISP ใดที่ถูกต้องจริง

@MARJAN VENEMA

1

ดังนั้นเมื่อคุณจะแบ่ง IFat ออกเป็นส่วนต่อประสานที่เล็กลงวิธีการใดที่สิ้นสุดลงซึ่ง ISmallinterface ควรตัดสินใจตามความสัมพันธ์ของสมาชิก

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

ดังนั้นหากมีลูกค้าจำนวนมากที่จะต้องโทรCutGreensแต่ไม่เช่นGrillMeatนั้นถ้าเป็นไปตามรูปแบบ ISP เราควรใส่CutGreensข้างในICookเท่านั้น แต่ไม่ใช่ด้วยGrillMeatแม้ว่าทั้งสองวิธีจะเหนียวแน่นกันมาก!

2

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

โดย "การใช้คลาสที่ไม่ได้ติดตาม SRP" คุณหมายถึงคลาสที่ใช้IFatหรือคลาสที่ใช้ISmall_1/ ISmall_2? ฉันคิดว่าคุณหมายถึงชั้นเรียนที่ใช้IFat? ถ้าเป็นเช่นนั้นทำไมคุณถึงคิดว่าพวกเขาไม่ได้ติดตาม SRP

ขอบคุณ


4
เหตุใดจึงไม่สามารถมีคำจำกัดความหลายคำที่ใช้หลักการเดียวกันได้
Bobson

5
คำจำกัดความเหล่านี้ไม่ได้ขัดแย้งกัน
Mike Partridge

1
ไม่แน่นอนว่าความต้องการของลูกค้าจะไม่สำคัญกว่าความต่อเนื่องของอินเตอร์เฟซ คุณสามารถใช้วิธีนี้ "กฎ" ไปไกลและจบลงด้วยการเชื่อมต่อวิธีการเดียวทั่วสถานที่ที่ไม่มีความรู้สึกอย่างใด หยุดการติดตามกฎและเริ่มคิดถึงเป้าหมายที่สร้างกฎเหล่านี้ ด้วย "คลาสที่ไม่ได้ติดตาม SRP" ฉันไม่ได้พูดถึงคลาสที่เฉพาะเจาะจงในตัวอย่างของคุณหรือว่าพวกเขาไม่ได้ติดตาม SRP อยู่แล้ว อ่านอีกครั้ง. คำจำกัดความแรกนำไปสู่การแยกอินเทอร์เฟซถ้าอินเทอร์เฟซไม่ได้ติดตาม ISP และคลาสกำลังติดตาม SRP
Marjan Venema

2
คำจำกัดความที่สองไม่สนใจผู้ใช้งาน มันกำหนดอินเทอร์เฟซจากมุมมองของผู้โทรและไม่ได้ตั้งสมมติฐานใด ๆ เกี่ยวกับว่าผู้ใช้งานมีอยู่แล้วหรือไม่ อาจสันนิษฐานว่าเมื่อคุณติดตาม ISP และมาใช้งานอินเทอร์เฟซเหล่านั้นแน่นอนว่าคุณจะต้องติดตาม SRP เมื่อสร้างพวกเขา
Marjan Venema

2
คุณจะรู้ล่วงหน้าได้อย่างไรว่าลูกค้าจะมีตัวตนและวิธีการใดที่พวกเขาต้องการ? คุณทำไม่ได้ สิ่งที่คุณสามารถรู้ล่วงหน้าได้คือความสัมพันธ์ของคุณกับอินเตอร์เฟซ
Tulains Córdova

คำตอบ:


6

ทั้งสองถูกต้อง

วิธีที่ฉันอ่านวัตถุประสงค์ของ ISP (หลักการแยกส่วนต่อประสาน) คือการทำให้ส่วนต่อประสานมีขนาดเล็กและให้ความสำคัญ: สมาชิกส่วนต่อประสานทั้งหมดควรมีการเชื่อมต่อที่สูงมาก คำจำกัดความทั้งสองมีวัตถุประสงค์เพื่อหลีกเลี่ยงส่วนต่อประสาน "jack-of-all-trades-master-of-none"

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

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

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

สำหรับฉันคำจำกัดความที่สองที่ลูกค้าเป็นผู้เรียกใช้อินเทอร์เฟซเป็นวิธีที่ดีกว่าในการบรรลุเป้าหมาย

แยก

ในคำถามของคุณคุณระบุ:

เนื่องจากวิธีนี้ MyClass ของฉันสามารถใช้งานได้เฉพาะวิธีที่ต้องการ (D () และ C ()) โดยไม่ถูกบังคับให้จัดเตรียมการใช้งานจำลองสำหรับ A (), B () และ C ():

แต่นั่นคือการพลิกโลกให้กลับหัวกลับหาง

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

ดังนั้นเมื่อคุณจะแบ่งออกIFatเป็นส่วนต่อประสานที่เล็กลงวิธีการใดที่จะสิ้นสุดลงในISmallส่วนต่อประสานที่ควรตัดสินใจตามความสัมพันธ์ของสมาชิก

พิจารณาอินเทอร์เฟซนี้:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

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

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

หมายเหตุการประกาศอินเตอร์เฟส

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


1
คุณเห็นการอัปเดตที่ฉันทำหรือไม่
EdvRusj

"ผู้เรียกได้รับการพึ่งพาทันทีในตัวดำเนินการ " ... เฉพาะในกรณีที่คุณละเมิดกรมทรัพย์สินทางปัญญา (หลักการผกผันของการพึ่งพา) หากตัวแปรภายในพารามิเตอร์ของผู้โทรพารามิเตอร์ส่งคืนค่า ฯลฯ เป็นประเภทICookแทนชนิดSomeCookImplementorตามที่ได้รับคำสั่งจากกรมแล้ว 'T SomeCookImplementorต้องขึ้นอยู่กับ
Tulains Córdova

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

ฉันใช้ตัวอย่างรหัสของคุณอีกครั้งในคำถามนี้programmers.stackexchange.com/a/271142/61852ปรับปรุงให้ดีขึ้นหลังจากได้รับการยอมรับแล้ว ฉันให้เครดิตแก่คุณสำหรับตัวอย่าง
Tulains Córdova

เจ๋ง @ user61852 :) (และขอบคุณสำหรับเครดิต)
Marjan Venema

14

คุณสับสนคำว่า "ไคลเอนต์" ที่ใช้ในเอกสาร Gang of Four ที่มี "ไคลเอนต์" เหมือนกับผู้บริโภคบริการ

"ไคลเอนต์" ตามที่กำหนดโดยคำจำกัดความของ Gang of Four เป็นคลาสที่ใช้อินเทอร์เฟซ ถ้าคลาส A ใช้อินเตอร์เฟส B ดังนั้นพวกเขาบอกว่า A เป็นไคลเอ็นต์ของ B มิฉะนั้นวลี"ลูกค้าไม่ควรถูกบังคับให้ใช้อินเทอร์เฟซที่ไม่ได้ใช้"จะไม่สมเหตุสมผลเนื่องจาก "ลูกค้า" (เช่นเดียวกับผู้บริโภค) สวมใส่ ไม่ต้องใช้อะไรเลย วลีดังกล่าวเหมาะสมเมื่อคุณเห็น "ลูกค้า" เป็น "ผู้ดำเนินการ"

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

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

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

ปัญหานั้นจะปรากฏขึ้นเมื่อส่วนต่อประสานนั้นมากเกินไปและมีวิธีการที่ควรแบ่งออกเป็นหลายส่วนแทนแทนที่จะเป็นเพียงส่วนเดียว

ตัวอย่างรหัสของคุณทั้งสองตกลงOK ในกรณีที่สองคุณถือว่า "ลูกค้า" หมายถึง "คลาสที่ใช้ / เรียกบริการ / วิธีที่เสนอโดยคลาสอื่น"

ฉันไม่พบความขัดแย้งในแนวคิดที่อธิบายไว้ในลิงก์สามรายการที่คุณให้

เพียงแค่ให้ชัดเจนว่า"ลูกค้า" เป็นผู้ดำเนินการในการพูดคุยที่มั่นคง


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

1
@EdvRusj คำตอบของฉันขึ้นอยู่กับเอกสารในเว็บไซต์ Object Mentor (องค์กร Bob Bob) เขียนโดย Martin เมื่อเขาอยู่ใน Gang of Four ที่มีชื่อเสียง อย่างที่คุณรู้ว่า Gnag of Four เป็นกลุ่มวิศวกรซอฟต์แวร์รวมถึง Martin ซึ่งเป็นตัวย่อของ SOLID ได้ระบุและจัดทำเอกสารหลักการต่างๆ docs.google.co.th/a/cleancoder.com/file/d/…
Tulains Córdova

ดังนั้นคุณไม่เห็นด้วยกับ @pdr และทำให้คุณพบคำจำกัดความแรกของ ISP (ดูโพสต์ต้นฉบับของฉัน) เห็นด้วยมากขึ้น?
EdvRusj

@EdvRusj ฉันคิดว่าทั้งคู่ถูกต้อง แต่อันที่สองเพิ่มความสับสนที่ไม่จำเป็นโดยใช้คำเปรียบเทียบไคลเอ็นต์ / เซิร์ฟเวอร์ ถ้าฉันต้องเลือกอย่างใดอย่างหนึ่งฉันจะไปกับแก๊งอย่างเป็นทางการของหนึ่งซึ่งเป็นครั้งแรก แต่สิ่งที่สำคัญคือการลดการมีเพศสัมพันธ์และการพึ่งพาที่ไม่จำเป็นซึ่งเป็นจิตวิญญาณของหลักการที่มั่นคง ไม่สำคัญว่าอันไหนถูก สิ่งสำคัญคือคุณควรแยกอินเทอร์เฟซตามพฤติกรรม นั่นคือทั้งหมดที่ แต่เมื่อมีข้อสงสัยให้ไปที่แหล่งต้นฉบับ
Tulains Córdova

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

5

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

คำจำกัดความแรกเชื่อมโยงอย่างแน่นแฟ้นกับ LSP มากขึ้น


3
ใน ISP ลูกค้าไม่ควรถูกบังคับให้ใช้องค์ประกอบอินเทอร์เฟซผู้บริโภคที่พวกเขาไม่ได้ใช้ ใน LSP บริการไม่ควรถูกบังคับให้ใช้วิธี D เนื่องจากรหัสการโทรต้องใช้วิธี A พวกเขาไม่ได้ขัดแย้ง แต่เป็นส่วนเสริม
pdr

2
@EdvRusj วัตถุที่ใช้ InterfaceA ที่ ClientA เรียกจริง ๆ แล้วอาจเป็นวัตถุเดียวกันที่ใช้ InterfaceB ที่ไคลเอ็นต์ B ต้องการในกรณีที่ไม่ค่อยพบลูกค้าต้องการเห็นวัตถุเดียวกันเป็นคลาสที่แตกต่างกันรหัสจะไม่ มักจะ "สัมผัส" คุณจะมองว่ามันเป็น A สำหรับจุดประสงค์เดียวและ B สำหรับวัตถุประสงค์อื่น
Amy Blankenship

1
@EdvRusj: อาจช่วยได้ถ้าคุณคิดใหม่เกี่ยวกับคำจำกัดความของอินเทอร์เฟซที่นี่ ไม่ใช่อินเทอร์เฟซในเงื่อนไข C # / Java เสมอ คุณสามารถมีบริการที่ซับซ้อนซึ่งมีคลาสที่เรียบง่ายล้อมรอบเช่นไคลเอนต์ A ใช้ wrapper คลาส AX เป็น "อินเตอร์เฟส" กับ service X ดังนั้นเมื่อคุณเปลี่ยน X ในแบบที่มีผลต่อ A และ AX คุณจะไม่ บังคับให้ส่งผลกระทบต่อ BX และ B
สาธารณรัฐประชาธิปไตยประชาชนลาว

1
@EdvRusj: มันจะแม่นยำกว่าที่จะบอกว่า A และ B ไม่สนใจว่าพวกเขากำลังโทร X หรืออีกคนกำลังโทร Y และอีกคนกำลังโทร Z นั่นเป็นจุดพื้นฐานของ ISP เพื่อให้คุณสามารถเลือกการใช้งานที่คุณต้องการและเปลี่ยนใจในภายหลัง ISP ไม่ชอบเส้นทางเดียวหรืออย่างอื่น แต่ LSP และ SRP อาจทำเช่นนั้น
pdr

1
@EdvRusj ไม่ไคลเอนต์ A จะสามารถแทนที่ Service X ด้วย Service y ซึ่งทั้งสองอย่างนี้จะใช้อินเตอร์เฟส AX X และ / หรือ Y อาจใช้อินเทอร์เฟซอื่น ๆ แต่เมื่อลูกค้าเรียกพวกเขาว่า AX มันจะไม่สนใจอินเทอร์เฟซอื่น ๆ เหล่านั้น
Amy Blankenship
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.