"การรวมตัวกับการสืบทอด" ละเมิด "หลักการที่แห้ง" หรือไม่?


36

ตัวอย่างเช่นลองพิจารณาฉันมีคลาสสำหรับคลาสอื่นเพื่อขยาย:

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

และคลาสย่อยบางส่วน:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

ในความเป็นจริง subclass ไม่มีวิธีการใด ๆ ที่จะแทนที่ฉันจะไม่เข้าถึง HomePage ด้วยวิธีทั่วไปเช่น: ฉันจะไม่ทำสิ่งที่ชอบ:

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

ฉันแค่ต้องการใช้หน้าล็อกอินอีกครั้ง แต่ตามhttps://stackoverflow.com/a/53354ฉันควรชอบการแต่งเพลงที่นี่เพราะฉันไม่ต้องการอินเทอร์เฟซ LoginPage ดังนั้นฉันจึงไม่ใช้การสืบทอดที่นี่:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

แต่ปัญหามาที่นี่ในเวอร์ชั่นใหม่รหัส:

public LoginPage loginPage;

ซ้ำกันเมื่อมีการเพิ่มคลาสใหม่ และหาก LoginPage ต้องการ setter และ getter จะต้องคัดลอกรหัสเพิ่มเติม:

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

ดังนั้นคำถามของฉันคือ "การรวมตัวกับการสืบทอด" ละเมิด "หลักการแบบแห้ง" หรือไม่?


13
บางครั้งคุณไม่จำเป็นต้องมีการสืบทอดหรือการจัดวางซึ่งหมายความว่าบางครั้งชั้นเดียวที่มีวัตถุไม่กี่ตัวก็ทำงานได้
Erik Eidt

23
ทำไมคุณต้องการให้ทุกหน้าเป็นหน้าเข้าสู่ระบบ? อาจมีเพียงหน้าเข้าสู่ระบบ
Mr Cochese

29
แต่ด้วยการสืบทอดคุณได้ทำซ้ำextends LoginPageทั่วสถานที่ ตรวจสอบเพื่อน!
el.pescado

2
หากคุณมีปัญหาในข้อมูลโค้ดล่าสุดจำนวนมากคุณอาจใช้ getters และ setters มากเกินไป พวกเขามักจะละเมิดการห่อหุ้ม
Brian McCutchon

4
ดังนั้นทำให้หน้าของคุณสามารถตกแต่งและทำให้คุณLoginPageเป็นมัณฑนากร ไม่ต้องทำซ้ำอีกต่อไปเพียงแค่เรียบง่ายpage = new LoginPage(new EditInfoPage())และเสร็จแล้ว หรือคุณใช้หลักการแบบเปิดปิดเพื่อสร้างโมดูลการรับรองความถูกต้องที่สามารถเพิ่มไปยังหน้าใดก็ได้แบบไดนามิก มีหลายวิธีในการจัดการกับการทำสำเนารหัสโดยส่วนใหญ่เกี่ยวข้องกับการค้นหาสิ่งที่เป็นนามธรรมใหม่ LoginPageน่าจะเป็นชื่อที่ไม่ถูกต้องสิ่งที่คุณต้องการตรวจสอบให้แน่ใจคือผู้ใช้นั้นได้รับการรับรองความถูกต้องในขณะที่เรียกดูหน้านั้นในขณะที่ถูกเปลี่ยนเส้นทางไปยังLoginPageหรือแสดงข้อความแสดงข้อผิดพลาดที่เหมาะสมหากเขาไม่ใช่
Polygnome

คำตอบ:


46

เอ้อรอคุณกังวลว่าจะทำซ้ำ

public LoginPage loginPage;

ในสองสถานที่ละเมิด DRY? โดยตรรกะนั้น

int x;

สามารถมีอยู่ได้เพียงวัตถุเดียวในฐานรหัสทั้งหมด bleh

DRY เป็นสิ่งที่ดีที่ควรระลึกไว้เสมอ นอกเหนือจาก

... extends LoginPage

กำลังทำสำเนาในทางเลือกของคุณดังนั้นแม้จะเป็นเรื่องเกี่ยวกับ DRY จะไม่เข้าใจเรื่องนี้

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

ไม่ควรติดตาม DRY อย่างสุ่มสี่สุ่มห้า หากคุณทำซ้ำเพราะการคัดลอกและวางทำได้ง่ายกว่าการคิดวิธีการที่ดีหรือชื่อคลาสคุณอาจผิด

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

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

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

แต่ใช่มันเป็นแป้นพิมพ์พิเศษมากมาย ตราบใดที่ฉันสามารถป้องกันไม่ให้ปัญหาโยโย่ของการตีกลับขึ้นและลงสแต็กมรดกที่ฉันอ่านรหัสก็คุ้มค่า

ตอนนี้ไม่ใช่ว่าฉันจะปฏิเสธที่จะใช้มรดก หนึ่งในสิ่งที่ฉันโปรดปรานคือการใช้ชื่อใหม่ยกเว้น:

public class MyLoginPageWasNull extends NullPointerException{}

int x;สามารถมีอยู่ได้ไม่เกินศูนย์ในฐานรหัส
เคอลันเบตส์

@ K.AlanBates ขอโทษมั้ย
candied_orange

... ฉันรู้ว่าคุณกำลังจะโต้กลับกับระดับพอยต์ฮ่า ๆ ไม่มีใครใส่ใจเรื่องคลาสพอยต์ ถ้าฉันเดินเข้าไปใน codebase ของคุณและดูว่าint a; int b; int x;ฉันจะดันให้คุณ 'ถูก' ลบออก
K. Alan Bates

@ K.AlanBates ว้าว เป็นเรื่องน่าเศร้าที่คุณคิดว่าพฤติกรรมแบบนั้นจะทำให้คุณเป็นนักเขียนโค้ดที่ดี ผู้เขียนโค้ดที่ดีที่สุดไม่ใช่คนที่สามารถค้นหาสิ่งที่เกี่ยวกับคนอื่น ๆ มันเป็นสิ่งที่ทำให้การพักผ่อนดีขึ้น
candied_orange

การใช้ชื่อที่ไม่มีความหมายเป็นสิ่งที่ไม่ใช่ผู้เริ่มต้นและมีแนวโน้มที่จะทำให้นักพัฒนารหัสนั้นเป็นหนี้สินมากกว่าสินทรัพย์
K. Alan Bates

127

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

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

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


27
"หลักการความรับผิดชอบเดี่ยว" ถูกนำไปใช้ในทางที่ผิดมากขึ้น "อย่าพูดซ้ำตัวเอง" อาจเข้ามาเป็นหมายเลขสองได้อย่างง่ายดาย :-)
gnasher729

28
"DRY" เป็นคำย่อที่มีประโยชน์สำหรับ "Don't Repeat Yourself" ซึ่งเป็นวลีที่จับได้แต่ไม่ใช่ชื่อของหลักการชื่อจริงของหลักการคือ "กาลครั้งเดียวเท่านั้น" ซึ่งจะเป็นชื่อลวง สำหรับหลักการที่ใช้เวลาสองสามย่อหน้าในการอธิบาย สิ่งสำคัญคือการทำความเข้าใจสองสามย่อหน้าโดยไม่จดจำตัวอักษรสามตัว D, R และ Y.
Jörg W Mittag

4
@ gnasher729 คุณรู้ไหมฉันเห็นด้วยกับคุณจริง ๆ แล้วว่า SRP อาจถูกนำไปใช้ในทางที่ผิดมากขึ้น แม้ว่าจะมีความยุติธรรมในหลาย ๆ กรณีฉันคิดว่าพวกเขามักจะใช้ร่วมกันในทางที่ผิด ทฤษฎีของฉันคือโปรแกรมเมอร์โดยทั่วไปไม่ควรเชื่อถือในการใช้ตัวย่อด้วย "ชื่อง่าย"
wasatz

17
IMHO นี่เป็นคำตอบที่ดี อย่างไรก็ตามเพื่อความเป็นธรรม: จากประสบการณ์ของฉันรูปแบบที่พบบ่อยที่สุดของการละเมิด DRY เกิดจากการเขียนโปรแกรมคัดลอกและวางรหัสซ้ำ มีคนบอกฉันหนึ่งครั้ง "เมื่อใดก็ตามที่คุณจะคัดลอก - วางโค้ดให้คิดสองครั้งถ้าไม่สามารถแยกส่วนที่ซ้ำกันลงในฟังก์ชั่นทั่วไป" - ซึ่งเป็นคำแนะนำที่ดีมากของ IMHO
Doc Brown

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

12

คำตอบสั้น ๆ : ใช่มันมี - สำหรับผู้เยาว์ระดับที่ยอมรับได้

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

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

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

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

หมายเหตุถึงตัวอย่างของคุณ: ฉันไม่รู้ว่าคุณHomePageและEditInfoPageหน้าตาของคุณเป็นอย่างไร แต่ถ้าพวกเขามีฟังก์ชั่นเข้าสู่ระบบและ a HomePage(หรือ an EditInfoPage) คือ a การ LoginPageรับมรดกอาจเป็นเครื่องมือที่ถูกต้องที่นี่ ตัวอย่างที่เป็นที่ถกเถียงกันน้อยกว่าซึ่งการจัดวางองค์ประกอบจะเป็นเครื่องมือที่ดีกว่าในลักษณะที่ชัดเจนยิ่งขึ้น

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

  • แยกส่วนที่นำกลับมาใช้ใหม่ของLoginPageลงในองค์ประกอบของตัวเอง

  • นำองค์ประกอบนั้นกลับมาใช้ซ้ำHomePageและEditInfoPageใช้วิธีเดียวกันกับที่ใช้ตอนนี้LoginPage

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

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